Compose selected-deck Tech and Stronghold modules

This commit is contained in:
Quintine committed 2026-10-26 11:07:56 +11:00
1 parent 82370d906f
commit 68a7870e2e
21 files changed
+1168 -50

No files matched your search

+11 -11
View File
@@ -16,7 +16,7 @@ import { movementShipmentPreparations } from './movement-shipment-preparation';
import { MovementClaimError, liveMovementPromises, movementEventAllowed, movementPromiseModeSupported, movementPromiseTargetAvailable, settleMovementEvent, finishMovementPromises, validMovementClaim, type MovementClaim, type MovementPromise } from './movement-promises';
import { findMovementCompletion, type MovementCompletion } from './movement-promise-search';
import { classicNexusModulesProfile, mixedE1E2NexusProfile, mixedE3NexusProfile, pairedE3NexusProfile, pairedNexusModulesProfile, singleE1E2NexusProfile, standaloneE3NexusProfile } from './nexus-module-profile';
import { independentDiscoveryProfile, independentNexusProfile, nexusCoreProfile, nativeFactionTechProfile } from './faction-module-profile';
import { independentDiscoveryProfile, independentNexusProfile, nexusCoreProfile, nativeFactionTechProfile, selectedFactionStrongholdProfile } from './faction-module-profile';
import { classicDiscoveryLeaderSkillsProfile, classicDiscoveryNexusProfile, homeworldDiscoveryProfile, discoveryModeSupported, discoveryModuleProfile, mixedE1E2DiscoveryNexusProfile, mixedE3DiscoveryNexusProfile, pairedDiscoveryNexusProfile, pairedE3DiscoveryNexusProfile, singleE1E2DiscoveryNexusProfile, standaloneE3DiscoveryNexusProfile } from './discovery-module-profile';
import { advancedMoritaniLeaderSkillsProfile, selectedDeckLeaderSkillsProfile, homeworldLeaderSkillsProfile, independentTreacheryLeaderSkillsProfile, classicNexusLeaderSkillsProfile, classicTechLeaderSkillsProfile, guildBetrayalNexusLeaderSkillsProfile, mixedE1E2NexusLeaderSkillsProfile, mixedE3NexusLeaderSkillsProfile, nativeDiscoveryLeaderSkillsProfile, nativeEcazLeaderSkillsProfile, nativeExpansionLeaderSkillsProfile, nativeTechLeaderSkillsProfile, ordinaryLeaderSkillModeSupported, pairedE3LeaderSkillsProfile, pairedE3NexusLeaderSkillsProfile, pairedNexusLeaderSkillsProfile, singleE1E2NexusLeaderSkillsProfile, standaloneE3NexusLeaderSkillsProfile, strongholdLeaderSkillsProfile } from './leader-skill-profile';
import { bribeTimingBlock, maximumBribe, type BribeOptions } from './bribe-options';
@@ -9836,7 +9836,7 @@ function moritaniAssassinateModeSupported(g: Game) {
(f.expansion === 'base' || g.expansions.includes(f.expansion)))) &&
(!g.nexusCards || (!!g.leaderSkills && standaloneE3NexusLeaderSkillsProfile(g))) &&
(!g.leaderSkills || advancedMoritaniLeaderSkillsProfile(g)) &&
(!g.strongholdCards || e3StrongholdFactionProfile(g) || independentCardModulesProfile(g) ||
(!g.strongholdCards || selectedFactionStrongholdProfile(g) || e3StrongholdFactionProfile(g) || independentCardModulesProfile(g) ||
(!!g.leaderSkills && strongholdLeaderSkillsProfile(g))) &&
(!g.homeworlds || g.homeworldOccupationPreview === true || ecazHomeworldProfile(g) ||
(!!g.leaderSkills && homeworldLeaderSkillsProfile(g))) &&
@@ -11841,7 +11841,7 @@ function initializeFactionExpansionsGame(state: Game, semuta = false): Game {
requireRule(!state.homeworlds && !state.nexusCards && !state.leaderSkills &&
!state.discoveryEnabled && !state.discoveries && !state.discoveryStash && !state.greatMaker &&
(!state.techTokens || nativeFactionTechProfile(state)) && !state.strongholdCards,
'The faction prototype excludes other optional modules; Tech Tokens require a supported native three-through-six-seat lobby.');
'The faction prototype excludes other optional modules; Tech Tokens require a supported selected-deck three-through-six-seat lobby.');
requireFreshFactionInventory(state);
const initialized = initializeSetupGameForAudit(state, false, false, false, false, false, false, true, false, false, semuta);
if (byFaction(initialized, 'ecaz')) initialized.ecazOccupyPreview = true;
@@ -11849,11 +11849,11 @@ function initializeFactionExpansionsGame(state: Game, semuta = false): Game {
return initialized;
}
function ecazOccupyCompositionSupported(g: Game): boolean {
// Bare native Basic uses the same physical coalition and selected decks.
// Native Basic uses the same physical coalition and selected decks.
// Family/roster checks below remain authoritative; optional configurations
// retain their separate registered profiles rather than inheriting admission.
const basicNatives = g.advanced === false &&
!g.ecazTreachery && !g.techTokens && !g.strongholdCards;
!g.ecazTreachery && (!g.techTokens || nativeFactionTechProfile(g)) && !g.strongholdCards;
if ((independentNexusProfile(g) || independentDiscoveryProfile(g)) && byFaction(g, 'ecaz')) return true;
if (g.leaderSkills && nativeEcazLeaderSkillsProfile(g)) return true;
if (g.ecazOccupyPreview === true && (mixedE3NexusProfile(g) || standaloneE3NexusProfile(g) || pairedE3NexusProfile(g)) &&
@@ -11888,7 +11888,7 @@ function ecazOccupyCompositionSupported(g: Game): boolean {
(['ecaz', 'moritani', 'ixians', 'tleilaxu', 'choam', 'richese'].includes(p.faction) &&
g.expansions.includes(faction(p.faction).expansion))) &&
!g.homeworlds && !g.nexusCards && !g.leaderSkills &&
(!g.strongholdCards || e3StrongholdFactionProfile(g) || independentCardModulesProfile(g)) &&
(!g.strongholdCards || selectedFactionStrongholdProfile(g) || e3StrongholdFactionProfile(g) || independentCardModulesProfile(g)) &&
(!g.techTokens || nativeFactionTechProfile(g) || independentCardModulesProfile(g)) &&
!g.discoveryEnabled && !g.discoveries &&
!g.discoveryStash && !g.greatMaker &&
@@ -11897,7 +11897,7 @@ function ecazOccupyCompositionSupported(g: Game): boolean {
/** Fresh native Occupy profile; Basic odd counts use the main printed rule provisionally. */
export function initializeEcazOccupyGameForAudit(state: Game): Game {
requireRule(ecazOccupyCompositionSupported(state),
'Occupy requires supported native factions and their selected decks; bare Basic native families exclude optional modules.');
'Occupy requires supported native factions and their selected decks; Basic phase modules admit Tech Tokens only.');
requireFreshBaseRuntime(state);
requireFreshFactionInventory(state);
const g = initializeSetupGameForAudit(state, false, false, false, false, false, false, true, state.ecazTreachery === true);
@@ -11917,8 +11917,8 @@ function nativeStrongholdFactionRoster(g: Game): boolean {
}
/** Shared fresh native Stronghold setup; other overlays retain their own admission. */
export function initializeStrongholdFactionsGameForAudit(state: Game): Game {
requireRule(e3StrongholdFactionProfile(state) || nativeStrongholdFactionRoster(state),
'Stronghold factions require supported Advanced native factions, classic opponents and their selected distinct decks.');
requireRule(selectedFactionStrongholdProfile(state) || e3StrongholdFactionProfile(state) || nativeStrongholdFactionRoster(state),
'Stronghold factions require supported Advanced factions and their selected distinct decks.');
requireRule(!state.homeworlds && !state.nexusCards && !state.leaderSkills &&
!state.discoveryEnabled && !state.discoveries && !state.discoveryStash && !state.greatMaker &&
(!state.techTokens || nativeFactionTechProfile(state)) && !state.ecazTreachery &&
@@ -11927,8 +11927,8 @@ export function initializeStrongholdFactionsGameForAudit(state: Game): Game {
requireFreshFactionInventory(state);
requireFreshBaseRuntime(state);
const initialized = initializeSetupGameForAudit(state, false, false, false, false, false, false, true, false, true);
if (e3StrongholdFactionProfile(initialized) && byFaction(initialized, 'moritani'))
initializeMoritaniAssassinateState(initialized);
if (byFaction(initialized, 'ecaz')) initialized.ecazOccupyPreview = true;
if (moritaniAssassinateModeSupported(initialized)) initializeMoritaniAssassinateState(initialized);
return initialized;
}
function requireFreshFactionInventory(state: Game) {
+59 -13
View File
@@ -24,6 +24,9 @@ export type FactionModuleProfile = {
nexusIxianReplacementPreview?: unknown;
nexusIxianBetrayalPreview?: unknown;
nexusHarkonnenBetrayalPreview?: unknown;
/** Native runtime stamps, not independent optional-module selections. */
ecazOccupyPreview?: unknown;
moritaniAssassinatePreview?: unknown;
};
type NexusCoreConfiguration = Partial<FactionModuleProfile> & Pick<FactionModuleProfile, 'expansions'> & {
@@ -105,19 +108,46 @@ export function independentNexusProfile(game: NexusCoreConfiguration): boolean {
return game.ecazTreachery === true && nexusCoreProfile(game);
}
/** Original E1/E2 families or standalone Ecaz/Moritani; explicit Semuta context only. */
export function nativeFactionTechProfile(game: FactionModuleProfile, semuta = false): boolean {
if (typeof game.advanced !== 'boolean' || !game.techTokens ||
game.players.length < 3 || game.players.length > 6 ||
game.expansions.length < 1 || game.expansions.length > 2 ||
(game.strongholdCards && !game.advanced) || game.leaderSkills ||
game.homeworlds || game.nexusCards || game.discoveryEnabled || game.discoveries ||
game.discoveryStash || game.greatMaker || game.ecazTreachery ||
(game.semutaPreview && (!semuta || game.semutaPreview !== true)) ||
game.advancedPreview || game.kullPreview || game.nexusKullPreview ||
game.guildBetrayalPreview || game.richeseBetrayalPreview ||
game.nexusIxianReplacementPreview || game.nexusIxianBetrayalPreview ||
game.nexusHarkonnenBetrayalPreview) return false;
/** Selected decks do not require their native factions to be seated. This is
* roster validity only; each phase module retains its own mode and seat limits. */
export function selectedPhaseModuleRoster(
game: Pick<FactionModuleProfile, 'expansions' | 'players'>,
): boolean {
if (!game || typeof game !== 'object' || !Array.isArray(game.expansions) ||
game.expansions.length < 1 || game.expansions.length > 3 ||
!game.expansions.every(id => id === 'ix' || id === 'choam' || id === 'ecaz') ||
new Set(game.expansions).size !== game.expansions.length ||
!Array.isArray(game.players) || game.players.length < 2 || game.players.length > 6) return false;
const seated = new Set<string>();
for (const player of game.players) {
if (!player || typeof player !== 'object' || Array.isArray(player) ||
typeof player.faction !== 'string' || seated.has(player.faction)) return false;
const faction = FACTIONS.find(faction => faction.id === player.faction);
if (!faction || (faction.expansion !== 'base' && !game.expansions.includes(faction.expansion))) return false;
seated.add(player.faction);
}
return true;
}
/** Keep unrelated overlays outside this phase-only permission. Semuta's caller
* additionally retains its old roster envelope below, rather than inheriting
* new selected-family admission through its explicit context flag. */
function phaseModuleContext(game: FactionModuleProfile, semuta = false): boolean {
return typeof game.advanced === 'boolean' &&
!(game.strongholdCards && !game.advanced) && !game.leaderSkills &&
!game.homeworlds && !game.nexusCards && !game.discoveryEnabled && !game.discoveries &&
!game.discoveryStash && !game.greatMaker && !game.ecazTreachery &&
(!game.semutaPreview || (semuta && game.semutaPreview === true)) &&
!game.advancedPreview && !game.kullPreview && !game.nexusKullPreview &&
!game.guildBetrayalPreview && !game.richeseBetrayalPreview &&
!game.nexusIxianReplacementPreview && !game.nexusIxianBetrayalPreview &&
!game.nexusHarkonnenBetrayalPreview;
}
/** Exact earlier E1/E2 or one-E3-native scope for existing Semuta consumers. */
function legacySemutaTechRoster(game: FactionModuleProfile): boolean {
if (!Array.isArray(game.players) || game.players.length < 3 || game.players.length > 6 ||
!Array.isArray(game.expansions) || game.expansions.length < 1 || game.expansions.length > 2) return false;
const e3 = game.expansions.length === 1 && game.expansions[0] === 'ecaz';
if (!e3) {
for (let i = 0; i < game.expansions.length; i++) {
@@ -130,6 +160,7 @@ export function nativeFactionTechProfile(game: FactionModuleProfile, semuta = fa
let harkonnen = false;
for (let i = 0; i < game.players.length; i++) {
const player = game.players[i];
if (!player || typeof player !== 'object') return false;
for (let j = 0; j < i; j++) if (player.faction === game.players[j].faction) return false;
let expansion: string | undefined;
for (const faction of FACTIONS)
@@ -147,6 +178,21 @@ export function nativeFactionTechProfile(game: FactionModuleProfile, semuta = fa
!(game.advanced && moritani && harkonnen);
}
/** Tech-only Basic/Advanced or original Advanced Strongholds at three-six seats.
* Explicit Semuta validation keeps its earlier family/native/Harkonnen scope. */
export function nativeFactionTechProfile(game: FactionModuleProfile, semuta = false): boolean {
return !!game && phaseModuleContext(game, semuta) && !!game.techTokens &&
(semuta ? legacySemutaTechRoster(game) :
selectedPhaseModuleRoster(game) && game.players.length >= 3);
}
/** Clean selected-deck Strongholds. The original initializer may create their
* unused state when absent; physical freshness and native stamps stay external. */
export function selectedFactionStrongholdProfile(game: FactionModuleProfile): boolean {
return !!game && game.advanced === true && phaseModuleContext(game) &&
selectedPhaseModuleRoster(game) && (!game.techTokens || game.players.length >= 3);
}
export type MixedE3NativeRoster = {
advanced?: boolean;
expansions: readonly string[];
+8 -8
View File
@@ -3963,10 +3963,10 @@ export const RULE_TOPICS: RuleTopic[] = [
title: 'Expansion factions with Stronghold Cards',
category: 'Advanced & expansions',
coverage: 'Partial',
developmentStage: 'Verified',
summary: 'Fresh Advanced Stronghold play connects native E1/E2 families and standalone Ecaz or Moritani through original holder-only rule handlers.',
developmentStage: 'Prototyped',
summary: 'Fresh Advanced selected-deck Stronghold play connects registered native or classic rosters through original holder-only rule handlers.',
steps: [
'This private local profile admits selected native Ixian/Tleilaxu and CHOAM/Richese families with classic opponents in unique two-to-six-seat Advanced rosters. Standalone Ecaz OR Moritani with classic opponents additionally uses the exact ecaz deck. Moritani excludes Harkonnen under its original assassination/Duke boundary. Original unused Tech Tokens may additionally be selected with three through six seats, without Leader Skills; an E3 pair, E1/E2/E3 mixtures, Basic Strongholds and unrelated overlays remain separate.',
'Fresh Advanced normal starts admit unique two-to-six-seat selected-deck rosters: paired Ecaz/Moritani, mixed E1/E2/E3, all six natives or classic-only seats with unused selected decks. Each native needs its family selected. Original unused Tech optionally requires three through six seats. Mandatory Occupy and Advanced Assassinate compose, including the existing captured-leader Harkonnen/Moritani contract. Basic Strongholds and unrelated overlays keep separate profiles; explicit Semuta retains its earlier narrower envelope.',
'CHOAM-only ordinary Treachery has 35 physical cards; Ix or both decks have47, not49, because CHOAM replaces the shared Ix cards. Native Richese’s ten-card cache remains separate. Genuine setup retains original faction offers/leaders/forces and any native Auditor/HMS, with all six Stronghold Cards initially unowned.',
'Card ownership begins only at the real first end of Mentat and is retained through the following turn. It is not assigned by a battle fixture or reset on refresh. Hidden Mobile Stronghold interior forces and its outside pointing territory stay distinct.',
'The mobile card copies an advantage of another currently controlled stronghold, not merely a retained card. A sole choice is automatic; multiple choices use the existing public pre-plan selection. Accepted choices stay bound to the same original battle through private plans and saved continuation.',
@@ -3978,11 +3978,11 @@ export const RULE_TOPICS: RuleTopic[] = [
],
related: ['stronghold-cards', 'mobile-stronghold', 'choam-combat', 'setup', 'implementation-checklist'],
checklist: [
{ area: 'Implementation', status: 'Implemented', detail: 'Fresh selected E1/E2 setup and standalone Ecaz/Moritani exact ecaz deck, actual end-Mentat ownership, holder-only Ecaz coalition funding and original Moritani loss/assassination compose existing effects. Optional original Tech requires three through six seats and awards the actual selected lead before Face Dance, without public-start or old-game changes.', evidence: ['game/engine.ts', 'game/faction-module-profile.ts', 'game/stronghold-cards.ts', 'game/battle-resolution-quote.ts'] },
{ area: 'Implementation', status: 'Implemented', detail: 'Fresh registered selected-deck rosters, including paired/mixed E3 and classic-only unused decks, use original setup and actual end-Mentat ownership. Mandatory Ecaz coalition and Moritani Assassinate retain their existing handlers. Optional Tech3–6 awards the actual selected lead before Face Dance. Clean phase admission does not widen explicit Semuta or retrofit played games.', evidence: ['game/engine.ts', 'game/faction-module-profile.ts', 'game/stronghold-cards.ts', 'game/battle-resolution-quote.ts'] },
{ area: 'Player controls', status: 'Implemented', detail: 'Original local390px controls cover HMS copy/support, real cache Stone reveal mode and partial Face Dance replacement. Standalone E3 human controls select the Ecaz holder lead, show bank2/personal1 for actual support3, seal native plans and keep the physical Shield; Moritani reveals printed Master Bewt bounty through its original inspector and choice. Original Collection and Mentat continue after refresh.', evidence: ['components/stronghold-cards.tsx', 'components/game-table.tsx'] },
{ area: 'AI', status: 'Partial', detail: 'Four minimal legal policies consume actual marker/Stone/FaceDance/copy choices; eighteen genuine paired/mixed Advanced2..6 games complete. Strategy/calibration and other combinations remain deferred.', evidence: ['game/bots.ts', 'tools/faction-games.ts'] },
{ area: 'Documentation', status: 'Partial', detail: 'Advanced family/deck/cache admission, existing six effects, original winner versus replacement and explicit guarded combinations are recorded without completing every card or faction.', evidence: ['docs/STRONGHOLD_CARDS.md', 'docs/RULE_DECISIONS.md'] },
{ area: 'Verification', status: 'Partial', detail: 'Prior E1/E2 evidence remains dated. Standalone E3 adds20 focused cases, affected native/CLI179, types/lint/build and actual controlled holder-only funding, tie/income, assassination/death/forfeiture and four legal policies. Four genuine two/three-seat E3 games complete642 accepted actions,15 JSON continuations and3 battles without rejection; no natural assassination claim. Human390px Ecaz reaches v18/Mentat with bank2/personal1, separate Collection2 and physical Ecaz2/Guild1 survivors; Moritani reaches v11/Mentat with wallet18 and one original private replacement. No new assurance/full-suite campaign or public/deployed certification.', evidence: ['tests/ecaz-stronghold-runtime.test.ts', 'tests/moritani-stronghold-runtime.test.ts', 'tests/richese-strongholds-runtime.test.ts', 'tests/tleilaxu-strongholds-runtime.test.ts'] },
{ area: 'Verification', status: 'Partial', detail: 'New selected-deck cases cover normal mixed/classic setup, actual six-native income and claims, native Harkonnen/Moritani assassination and labelled controlled Ecaz/Ixian coalition battles. Full modes remain unfinished. Prior E1/E2 evidence remains dated. Standalone E3 adds20 focused cases, affected native/CLI179, types/lint/build and actual controlled holder-only funding, tie/income, assassination/death/forfeiture and four legal policies. Four genuine two/three-seat E3 games complete642 accepted actions,15 JSON continuations and3 battles without rejection; no natural assassination claim. Human390px Ecaz reaches v18/Mentat with bank2/personal1, separate Collection2 and physical Ecaz2/Guild1 survivors; Moritani reaches v11/Mentat with wallet18 and one original private replacement. No new assurance/full-suite campaign or public/deployed certification.', evidence: ['tests/phase-module-profile.test.ts', 'tests/mixed-phase-modules-runtime.test.ts', 'tests/mixed-phase-modules-controls.test.tsx', 'tests/phase-module-ecaz-runtime.test.ts', 'tests/phase-module-moritani-runtime.test.ts', 'tests/ecaz-stronghold-runtime.test.ts', 'tests/moritani-stronghold-runtime.test.ts', 'tests/richese-strongholds-runtime.test.ts', 'tests/tleilaxu-strongholds-runtime.test.ts'] },
],
},
{
@@ -4000,7 +4000,7 @@ export const RULE_TOPICS: RuleTopic[] = [
'A battle winner takes one tech token from the defeated faction after casualties and played-card decisions. With several available tokens, the winner chooses. A winner still takes the token if no winning forces survive. Mutual destruction has no winner and transfers no token.',
'Owning all three tokens counts as one additional stronghold at victory checks. An alliance cannot combine separate token holdings to qualify: one member must own the complete set. Multiple sides reaching the victory target can share the win.',
'Token ownership and pending income are public and persist. Fresh registered selected-family all14 keeps original Tech3–6 Basic/Advanced and optional Advanced Strongholds2–6, including supported Worlds and independent cards, without an incidental Nexus or single-family E3 requirement. Original Nexus/Discovery/Semuta and role/effect guards remain. Rescue/board-spice precedes winner Tech and Face Dance. Mandatory normal Banker waits for Mentat, separate from phase-end Tech; fees/H returns are not revival/shipment. Native CHOAM opening income is not Charity. Two-player Tech and full combined acceptance remain unfinished.',
'Without Skills, fresh native E1/E2 families or standalone Ecaz OR Moritani with classic opponents may preserve unused original Tech Tokens through the local factions entry, Basic/Advanced three through six players; Advanced stronghold-factions adds the original six Stronghold Cards. Native payments, typed casualties, selected Ecaz lead rewards and Moritani original assassination remain separate. Split allied holdings never qualify as a complete set. Basic odd Occupy, Richese cache/No-Field rulings, unrelated overlays, public starts and saved-game conversion remain guarded.',
'Without other optional overlays, fresh normal selected-deck starts retain original Tech3–6 Basic/Advanced with registered paired/mixed/all-three native families or classic-only seats and unused decks. Advanced Strongholds2–6 optionally compose. Native payments, typed casualties, selected Ecaz lead rewards and existing captured-leader Moritani assassination remain separate; split allied holdings never form a complete set. Explicit Semuta keeps its earlier scope. Existing provisional Basic odd Occupy, Richese rulings and unrelated profiles are not newly decided; played games are not converted.',
'Classic/E1/E2 and original standalone/paired E3 bare independent-card previews retain Worlds, Tech3–6 and optional Advanced Strongholds2–6. All14 extends this to registered mixed selected families. Recruits changes current native rates without repeating Axlotl or advancing payout. Harass deposits and reserve fees are not free Revival or off-planet shipment. Original phase income/native custody/winner rewards remain; bare mixed-E3 World/phase and unrelated module/role guards stay separate.',
'Classic Nexus core composes original Tech3–6 Basic/Advanced and optional Advanced Strongholds2–6 with base33 or selected independent36, optionally all14. Real first-Storm owners and actual token counts set income. A paid fixed Tleilaxu force group does not become free because Recruits is active; subsequent genuine free returns accrue one Axlotl pile, not one pile per request. Pending spice cannot fund Revival before phase end. Harass returns and reserve fees are not token-triggering Revival or shipment; actual battle winners take one original opposing token.',
],
@@ -4015,8 +4015,8 @@ export const RULE_TOPICS: RuleTopic[] = [
'ix-modules',
],
checklist: [
{ area: 'Implementation', status: 'Partial', detail: 'Original first-Storm ownership, owner-count pending income, matching phase-end payout and native winner transfer remain. Classic Nexus core now admits original Tech with selected33/36 and optional14, preserving funded card shipment and fixed-versus-free Revival sources. Other module/native envelopes keep their bounds.', evidence: ['game/tech-tokens.ts', 'game/engine.ts', 'game/phase-resource-quote.ts', 'game/revival-resume.ts', 'game/faction-module-profile.ts'] },
{ area: 'Player controls', status: 'Partial', detail: 'Actual normal five-module lobby reaches original training/Storm. Native fixed-five and ordinary-free controls show original prices, physical Tanks and pending pile separately; actual phase close pays it. Stronghold and card inspectors retain their own entitlements.', evidence: ['components/game-table.tsx', 'tests/fixture-nexus-core-tech.ts'] },
{ area: 'Implementation', status: 'Partial', detail: 'Original first-Storm ownership, owner-count pending income, matching phase-end payout and native winner transfer remain. Clean selected-deck phase rosters now include paired/mixed/all-three families and classic-only unused decks through normal lobby starts. Classic Nexus core admits original Tech with selected33/36 and optional14, preserving funded card shipment and fixed-versus-free Revival sources. Other module/native envelopes keep their bounds.', evidence: ['game/tech-tokens.ts', 'game/engine.ts', 'game/phase-resource-quote.ts', 'game/revival-resume.ts', 'game/faction-module-profile.ts'] },
{ area: 'Player controls', status: 'Partial', detail: 'Normal mixed selected-deck module controls and Start reach original setup/Storm; separate existing five-module lobby reaches original training/Storm. Native fixed-five and ordinary-free controls show original prices, physical Tanks and pending pile separately; actual phase close pays it. Stronghold and card inspectors retain their own entitlements.', evidence: ['components/game-table.tsx', 'tests/fixture-nexus-core-tech.ts'] },
{ area: 'AI', status: 'Partial', detail: 'Four existing own-view policies legally claim funded card shipment, complete real Revival into phase-end payout and finish earned Stronghold/Nexus battles. No strategic calibration or fixed-choice pin.', evidence: ['game/bots.ts', 'tests/fixture-nexus-core-tech.ts', 'tests/fixture-nexus-core-strongholds.ts'] },
{ area: 'Documentation', status: 'Partial', detail: 'Printed trigger/exception, owner count, pending-versus-spendable spice, original payout, winner reward and complete-set victory remain distinct. Current classic core phase scope and inherited timing are explicit; full module/faction combinations and assurance remain open.', evidence: ['game/reference.ts', 'docs/NEXUS_CARD_RULES.md', 'docs/STRONGHOLD_CARDS.md', 'docs/RULE_DECISIONS.md'] },
{ area: 'Verification', status: 'Partial', detail: 'Original-action consumers cover funded five-for-one delivery, paid fixed5 versus free2, a second genuine free request, pending-income affordability and Harass/winner transfer. Actual all14 Revival UI and four legal policies reach payout; older broader and saved evidence remains dated.', evidence: ['tests/nexus-core-tech-runtime.test.ts', 'tests/fixture-nexus-core-tech.ts', 'tests/tech-tokens.test.ts', 'tests/nexus-module-payments-runtime.test.ts', 'tests/nexus-skills-modules-payments-runtime.test.ts'] },