Support native Advanced and No-Field movement promises
This commit is contained in:
1 parent
2ae2b984c0
commit
724eabf99c
20 files changed
+762
-30
No files matched your search
+20
-4
@@ -16745,6 +16745,12 @@ function groundMovementCompletionAtMovement(game: Game, actor: string, promises:
|
||||
} catch (error) { if (!(error instanceof RuleError)) throw error; }
|
||||
}
|
||||
}
|
||||
if (player.faction === 'richese' && player.noField?.deployed && !noFieldRevealBlock(state, player)) {
|
||||
const action: Action = { type: 'revealNoField', token: player.noField.deployed.tokenId, event: player.noFieldEvent };
|
||||
try {
|
||||
yield { game: groundMovementProbe(applyActionInner(state, owner, action, 'movementPreparation'), owner), actions: [action] };
|
||||
} catch (error) { if (!(error instanceof RuleError)) throw error; }
|
||||
}
|
||||
yield* movementShipmentPreparations(state, owner, (source, actor, action) => {
|
||||
const payer = getPlayer(source, actor), share = Number(action.allyPayment);
|
||||
const cost = reserveShipmentCost({ faction: payer.faction, halfRate: shipmentHalfRate(source, payer) },
|
||||
@@ -16790,7 +16796,10 @@ function groundMovementCompletionAtMovement(game: Game, actor: string, promises:
|
||||
// Include current native range (notably CHOAM Kulon) and the card's
|
||||
// explicit range-three option. This only prunes impossible geometry;
|
||||
// the actual declaration still validates exact speed/groups/custody.
|
||||
return Object.keys(player.forces).some(key => player.forces[key] > 0 && splitLocation(key).territory === origin &&
|
||||
const marker = player.faction === 'richese' ? player.noField?.deployed : null;
|
||||
const sources = [...Object.keys(player.forces).filter(key => player.forces[key] > 0),
|
||||
...(marker ? [location(marker.location.territory, marker.location.sector)] : [])];
|
||||
return sources.some(key => splitLocation(key).territory === origin &&
|
||||
gameDistance(state, key, location(destination, sector), point => pathBlocked(state, player, point, isAdvisor(player, origin))) <= Math.max(3, movementRange(state, player, 1)));
|
||||
},
|
||||
project(state, owner, action) {
|
||||
@@ -16881,13 +16890,14 @@ function reconcileGroundMovementPromises(g: Game, actor?: string, action?: Actio
|
||||
if (!live.some(promise => promise.answer)) continue;
|
||||
if (g.pendingFremenMove?.order.player === p.id || g.pendingIxMove?.player === p.id ||
|
||||
g.pendingChoamMove?.player === p.id ||
|
||||
g.pendingShipment?.player === p.id && !!g.pendingShipment.noField ||
|
||||
g.pendingAdvisorMatching?.intent.kind === 'movement' && g.pendingAdvisorMatching.intent.order.player === p.id ||
|
||||
g.response?.kind === 'advisorFlip' && g.response.owner === p.id ||
|
||||
g.pendingAutomaticAdvisorFlips?.some(source => source.owner === p.id ||
|
||||
source.producer === 'groundMovement' && source.mover === p.id)) continue;
|
||||
const early = g.phase < 5 || sourcePhase < 5;
|
||||
const voluntary = actor === p.id && action?.type !== 'passResponse' && (!early || !!action &&
|
||||
(['card', 'bribe', 'pledgeAid', 'emperorGift', 'bid', 'richeseGift', 'revive', 'reviveLeader', 'reviveKwisatz', 'emperorRevival', 'decision'].includes(action.type) ||
|
||||
(['card', 'bribe', 'pledgeAid', 'emperorGift', 'bid', 'richeseGift', 'revealNoField', 'revive', 'reviveLeader', 'reviveKwisatz', 'emperorRevival', 'decision'].includes(action.type) ||
|
||||
action.type === 'richeseBid' && typeof action.amount === 'number' && action.amount > 0 ||
|
||||
action.type === 'ready' && [2, 4].includes(sourcePhase)));
|
||||
const busy = !!(g.response || g.decision || g.phaseOpening || g.pendingTreacheryDiscard ||
|
||||
@@ -30755,8 +30765,9 @@ function offerShipment(g: Game, shipment: PendingShipment, matchingApproved = fa
|
||||
} else if (!openGuildRate(g, shipment)) commitShipment(g, shipment);
|
||||
}
|
||||
function commitShipment(g: Game, shipment: PendingShipment, settlement?: {recipient: string | null}, movementPreparation = false) {
|
||||
if (movementPreparation) requireRule(recoverableUnsupportedShipment(shipment),
|
||||
'Movement preparation requires an ordinary physical reserve shipment.');
|
||||
if (movementPreparation) requireRule(recoverableUnsupportedShipment(shipment) ||
|
||||
shipment.noField && !shipment.richesePair && recoverableUnsupportedShipment({ ...shipment, noField: undefined }),
|
||||
'Movement preparation requires an ordinary physical or owned No-Field shipment.');
|
||||
if (shipment.source === 'ambassador') {
|
||||
commitAmbassadorShipment(g, shipment);
|
||||
return;
|
||||
@@ -36705,6 +36716,11 @@ function applyActionInner(
|
||||
// unsupported arrival combinations before saving its private intent.
|
||||
validateShipmentArrival(g, shipment);
|
||||
checkShipmentIncomeFunding(g, p, cost, allyPayment);
|
||||
if (liveMovementPromises(g.movementPromises ?? [], p.id, g.turn).some(promise => promise.answer)) {
|
||||
const afterShipment = structuredClone(g);
|
||||
commitShipment(afterShipment, shipment, undefined, true);
|
||||
reconcileGroundMovementPromises(afterShipment, p.id, action, g.phase);
|
||||
}
|
||||
if (richesePair) recordRichesePairShipment(g, shipment);
|
||||
g.pendingShipment = shipment;
|
||||
g.response = { kind: 'richeseNoField', owner: p.id, passed: [],
|
||||
|
||||
@@ -49,10 +49,15 @@ export function findMovementCompletion(source: Game, actor: string,
|
||||
// or Box reuse. Tanks are only an optimistic bound; the adapter still
|
||||
// validates every owned card and never inspects unknown Box faces here.
|
||||
const recovery = player.hand.some(card => card.effect === 'ghola' || card.effect === 'nullentropyBox') ? player.tanks : 0;
|
||||
if (live.some(promise => promise.answer && promise.minimum > physical + (player.shipped ? 0 : player.reserves + recovery))) return null;
|
||||
const marker = player.faction === 'richese' ? player.noField?.deployed : null;
|
||||
const markerValue = marker ? player.noField!.tokens.find(token => token.id === marker.tokenId)?.value ?? 0 : 0;
|
||||
const revealable = Math.min(markerValue, player.reserves + recovery);
|
||||
if (live.some(promise => promise.answer && promise.minimum > physical +
|
||||
(player.shipped ? revealable : player.reserves + recovery))) return null;
|
||||
const key = JSON.stringify([player.forces, player.elites, player.advisors, player.moved, player.shipped, player.reserves, player.tanks, player.hand.map(card => card.id),
|
||||
player.hajrPlays, player.hajrWindow, player.ornithopterExtraMoves, player.spice, game.choamMovement,
|
||||
player.fremenMovementBlocked, player.advisorFlipMovementBlocked, game.advisorMatchingDenied,
|
||||
player.fremenMovementBlocked, player.ixMovementBlocked, player.advisorFlipMovementBlocked, game.advisorMatchingDenied,
|
||||
player.noField, player.noFieldEvent,
|
||||
game.advisorPreparation,
|
||||
game.aid, game.karamaShipping, game.guildRateBlocked, game.shipmentPromises,
|
||||
game.discard.map(card => card.id), game.movementDiscardKnowledge?.[actor], game.pendingNullentropy,
|
||||
@@ -61,11 +66,12 @@ export function findMovementCompletion(source: Game, actor: string,
|
||||
if (failed.has(key)) return null;
|
||||
const destinations = gameTerritories(game).filter(destination => !destination.id.startsWith('homeworld:'));
|
||||
destinations.sort((a, b) => Number(live.some(p => p.answer && p.territory === b.id)) -
|
||||
Number(live.some(p => p.answer && p.territory === a.id)));
|
||||
if (needsMove && !live.some(promise => promise.answer && promise.minimum > physical) &&
|
||||
Number(live.some(p => p.answer && p.territory === a.id)) ||
|
||||
(marker ? Number(['arrakeen', 'carthag'].includes(b.id)) - Number(['arrakeen', 'carthag'].includes(a.id)) : 0));
|
||||
if (needsMove && (marker || !live.some(promise => promise.answer && promise.minimum > physical)) &&
|
||||
!game.pendingNullentropy && player.moved < adapter.allowance(game, player)) {
|
||||
const origins = [...new Set(Object.keys(player.forces).filter(key => player.forces[key] > 0)
|
||||
.map(key => splitLocation(key).territory))];
|
||||
const origins = [...new Set([...Object.keys(player.forces).filter(key => player.forces[key] > 0)
|
||||
.map(key => splitLocation(key).territory), ...(marker ? [marker.location.territory] : [])])];
|
||||
const card = !game.ornithopter ? player.hand.find(card => card.effect === 'ornithopter') : null;
|
||||
const modes: Partial<Action>[] = game.ornithopter
|
||||
? [{ ornithopterEvent: game.ornithopter.event }]
|
||||
@@ -82,15 +88,18 @@ export function findMovementCompletion(source: Game, actor: string,
|
||||
if (!adapter.reachable(game, player, origin, destination.id, sector)) continue;
|
||||
const keys = Object.keys(player.forces).filter(key => player.forces[key] > 0 && splitLocation(key).territory === origin);
|
||||
const total = keys.reduce((sum, key) => sum + player.forces[key], 0);
|
||||
const amounts = Array.from({ length: total }, (_, i) => i + 1);
|
||||
const carriesMarker = marker?.location.territory === origin && !!player.noFieldEvent;
|
||||
const amounts = [...(carriesMarker ? [0] : []), ...Array.from({ length: total }, (_, i) => i + 1)];
|
||||
amounts.sort((a, b) => Number(live.some(p => p.answer && p.territory === destination.id && b >= p.minimum)) -
|
||||
Number(live.some(p => p.answer && p.territory === destination.id && a >= p.minimum)) || a - b);
|
||||
for (const amount of amounts) {
|
||||
const event = { territory: destination.id, amount };
|
||||
if (!movementEventAllowed(live, event)) continue;
|
||||
if (!mayMoveAgain && live.some(promise => promise.answer && !movementMatches(promise, event))) continue;
|
||||
for (const group of groups(player, keys, amount)) for (const mode of modes) for (const stance of stances) {
|
||||
const action = { ...group, ...mode, ...stance, territory: destination.id, sector };
|
||||
const markers: Partial<Action>[] = carriesMarker
|
||||
? [...(amount ? [{}] : []), { noField: marker!.tokenId, event: player.noFieldEvent }] : [{}];
|
||||
for (const group of groups(player, keys, amount)) for (const mode of modes) for (const stance of stances) for (const selected of markers) {
|
||||
const action = { ...group, ...mode, ...stance, ...selected, territory: destination.id, sector };
|
||||
const next = adapter.project(game, actor, action);
|
||||
if (!next) continue;
|
||||
const settled = settleMovementEvent(pending, actor, game.turn, next.event);
|
||||
|
||||
@@ -84,11 +84,10 @@ export function finishMovementPromises<T extends MovementPromise>(
|
||||
/** Prototype profile/timing only. Actual route feasibility stays authoritative
|
||||
* in the engine. Truthtrance and an ordinary active Ornithopter may remain. */
|
||||
export function movementPromiseModeSupported(g: Game): boolean {
|
||||
// Basic retains its native-family/early-phase prototype. Advanced starts
|
||||
// with classic factions during Movement; earlier forecasts and expansion
|
||||
// preparations are separate scope, not implied by this admission.
|
||||
// Basic retains early-phase admission. Advanced native families use the same
|
||||
// declared roster during Movement; earlier forecasts/modules stay separate.
|
||||
return (g.advanced === false ||
|
||||
g.advanced === true && g.phase === 5 && g.expansions.length === 0) &&
|
||||
g.advanced === true && g.phase === 5) &&
|
||||
g.players.length >= 2 && g.players.length <= 6 &&
|
||||
g.expansions.every(id => ['ix', 'choam', 'ecaz'].includes(id)) &&
|
||||
new Set(g.expansions).size === g.expansions.length &&
|
||||
|
||||
@@ -21,7 +21,17 @@ export function* movementShipmentPreparations(game: Game, actor: string,
|
||||
gameDistance(game, `${destination.id}:${from}`, `${target.id}:${to}`))))) : 0]));
|
||||
destinations.sort((a, b) => present(b.id) - present(a.id) ||
|
||||
Number(b.type === 'stronghold') - Number(a.type === 'stronghold') || proximity.get(a.id)! - proximity.get(b.id)!);
|
||||
for (const destination of destinations) for (const sector of destination.sectors)
|
||||
for (const destination of destinations) for (const sector of destination.sectors) {
|
||||
if (player.faction === 'richese' && player.noField && !player.noField.deployed && player.noFieldEvent) {
|
||||
for (const token of player.noField.tokens.filter(token => token.id !== player.noField!.lastShipped)) {
|
||||
for (let allyPayment = Math.min(2, game.aid[player.ally ?? '']?.amount ?? 0); allyPayment >= 0; allyPayment--) {
|
||||
const action: Action = { type: 'ship', territory: destination.id, sector, amount: 1, elite: 0,
|
||||
allyPayment, noField: token.id, event: player.noFieldEvent };
|
||||
const next = project(game, actor, action);
|
||||
if (next) yield { game: next, actions: [action] };
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let amount = 1; amount <= player.reserves; amount++) {
|
||||
const special = player.elites?.reserves ?? 0;
|
||||
for (let elite = Math.max(0, amount - (player.reserves - special)); elite <= Math.min(amount, special); elite++) {
|
||||
@@ -36,4 +46,5 @@ export function* movementShipmentPreparations(game: Game, actor: string,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
-7
@@ -3003,9 +3003,9 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
category: 'Cards',
|
||||
coverage: 'Partial',
|
||||
developmentStage: 'Prototyped',
|
||||
summary: 'Bind one future qualifying physical ground movement before a supported Basic or classic Advanced respondent finishes their current-round turn.',
|
||||
summary: 'Bind one future qualifying physical ground movement before a supported Basic or Advanced respondent with declared native families finishes their current-round turn.',
|
||||
steps: [
|
||||
'Choose Bind ground movement this turn, a printed territory and a minimum of one to twenty physical forces. Basic with declared native families supports clean phases0–4 before Movement. During Movement ask an active player before or after shipment, or a still-unused waiting respondent. Finish pending decisions first. Classic Advanced without modules also supports its current Movement phase. Advanced earlier phases/expansions, optional modules, later rounds and wider owned preparations remain unfinished.',
|
||||
'Choose Bind ground movement this turn, a printed territory and a minimum of one to twenty physical forces. Basic with declared native families supports clean phases0–4 before Movement. During Movement ask an active player before or after shipment, or a still-unused waiting respondent. Finish pending decisions first. Advanced with declared native families and no modules supports its current Movement phase. Earlier Advanced phases, optional modules, later rounds and wider owned preparations remain unfinished.',
|
||||
'Yes requires one qualifying ground movement from now until that player’s Shipment and Movement turn ends, while possible. Smaller groups do not add together and earlier moves do not count. Shipment, HMS relocation and Ambassador relocation do not fulfill this statement; a concealed No-Field marker itself is not a physical on-board force counter.',
|
||||
'A nonmatching move leaves Yes pending when an owned later route remains. One matching physical commitment fulfills it before card disposal and later Intrusion, Ambassador or Terror effects. You are promising the move, not survival afterward. No forbids every later matching move, including Hajr and both Ornithopter groups, until the actual turn closes.',
|
||||
'Ordinary reserve shipment may prepare or reinforce a later ground group using its actual cost, native allocation and pledged payment split. Shipment itself neither fulfills movement Yes nor violates movement No. Joint guidance honors a required shipment before End; an already declared reserve or native Guild transport keeps its exact source, destination and funding while responses finish.',
|
||||
@@ -3015,16 +3015,17 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
'Ghola returns up to five eligible forces to reserves using the physical card and current typed allowance, without using normal revival slots. It does not fulfill ground movement: an unused affordable shipment and later matching move remain necessary. A known spent Ghola can be recovered and replayed through Box. Possible Tleilaxu income remains conditional and supplies no forecast cash.',
|
||||
'A paid Box search retains its full private choice and original event. A guided choice preserves Yes; an incompatible selection rejects before shuffle and keeps that paid search available without another fee. No-only promises retain ordinary selection before movement can end. The existing provisional atomic full-hand exchange remains.',
|
||||
'Voluntary actions cannot erase the last required route. Native counter responses finish before impossibility is decided. An opposing loss releases only incompatible answers, retaining No constraints and still-possible Yes answers in accepted order. This owned-route and release priority is a provisional software composition of the general current-turn rule, not a publisher movement-specific FAQ.',
|
||||
'Classic Advanced counts each physical elite once and retains Hajr’s immediate post-movement timing. Owned native advisor preparation and an eligible arrival fighter flip may prepare city range; the original Karama response still decides the real advantage. Matching/Fremen/conversion counters keep Yes pending until transfer or completed impossibility. A mandatory response pass cannot evade the obligation or deadlock release.',
|
||||
'Native Advanced counts each physical elite once and retains Hajr’s immediate post-movement timing. Owned native advisor preparation and an eligible arrival fighter flip may prepare city range; the original Karama response still decides the real advantage. Matching/Fremen/conversion counters keep Yes pending until transfer or completed impossibility. A mandatory response pass cannot evade the obligation or deadlock release.',
|
||||
'Richese may prepare the route with its actual one-force-price No-Field shipment, voluntary reserve-capped reveal, marker-only city positioning or a physical group carrying the token. Neither shipment nor reveal fulfills movement, and the concealed marker adds zero to the promised minimum. Native Karama, exact token events, no-repeat history and Hajr timing remain. Own optional revelation cannot evade the last completion; a completed opposing prevention may release it.',
|
||||
'Persistent answer and next-step controls do not time out. All four profiles use the same private legal guidance. Broader Truthtrance questions, complete modes, full combinations and final acceptance remain unfinished.',
|
||||
],
|
||||
related: ['card-truthtrance', 'truthtrance-shipment', 'movement', 'implementation-checklist'],
|
||||
checklist: [
|
||||
{ area: 'Implementation', status: 'Partial', detail: 'Classic Advanced phase5 now reuses the typed route witness, immediate Hajr timing and owned advisor preparation/arrival flips with real counters. Basic declared native families join classic phases0–4. Exact Richese own/donor invoices, full submitted-ally Silent locks and already-paid boundaries prepare the owned turn without speculative bought cards/income. Fixed non-CHOAM preopening Charity and shared ordinary force-revival accounting preserve real native counter decisions. Mandatory Ix phase5 opening retains the future turn/reset. Held Ghola and known/paid Box recovery now add exact force returns and later shipment; normal revival allowances stay separate and native income is not precredited. CHOAM own unresolved opening, later rounds, wider preparations/modules remain unfinished.', evidence: ['game/ghola-force-preparation.ts', 'game/early-auction-funding.ts', 'game/movement-promises.ts', 'game/movement-promise-search.ts', 'game/movement-shipment-preparation.ts', 'game/movement-discard-knowledge.ts', 'game/truthtrance.ts', 'game/engine.ts'] },
|
||||
{ area: 'Player controls', status: 'Partial', detail: 'Classic Advanced questions and persistent guidance now connect an actual legal movement callback; native advisor preparation has a named action. Physical Ghola force-return guidance now precedes actual reserve shipment and ground movement, with income/disposal controls retaining priority. Persistent busy-safe guidance includes current Charity, Revival, Ready, ordinary bid-pass/payment and event-bearing native Once Around pass/Silent zero. Foreign pending funding cannot force a speculative positive bid or expose future movement. Actual native pass/zero handlers preserve unread obligations. Existing waiting/card controls remain; browser/visual acceptance stays open.', evidence: ['tests/truth-movement-advanced-controls.test.tsx', 'components/movement-claim-fields.tsx', 'components/movement-promises.tsx', 'components/shipment-promises.tsx', 'components/truthtrance.tsx', 'components/nullentropy-box.tsx', 'components/game-table.tsx', 'tests/truth-movement-waiting-controls.test.tsx', 'tests/truth-movement-early-controls.test.tsx', 'tests/truth-movement-native-early-controls.test.tsx', 'tests/truth-movement-ghola-controls.test.tsx'] },
|
||||
{ area: 'AI', status: 'Partial', detail: 'All four existing profiles finish a genuine classic Advanced movement question legally without strategy changes. Eight earlier existing-profile held-Ghola/known-Box runs accept40 proposals plus24 separate native continuations. Physical return, exact paid recovery, unused shipment and qualifying ground move use the same private legal guidance. Earlier native funding paths remain. Minimal legal participation only; no strategy tuning or calibration.', evidence: ['game/bots.ts', 'tests/truth-movement-ghola-engine.test.ts', 'tests/truth-movement-native-early-engine.test.ts'] },
|
||||
{ area: 'Implementation', status: 'Partial', detail: 'Declared native Advanced phase5 now reuses the typed route witness, immediate Hajr timing and owned advisor preparation/arrival flips with real counters. Owned No-Field shipment/reveal/marker movement uses native events, reserve caps and physical-only fulfillment. Basic declared native families join classic phases0–4. Exact Richese own/donor invoices, full submitted-ally Silent locks and already-paid boundaries prepare the owned turn without speculative bought cards/income. Fixed non-CHOAM preopening Charity and shared ordinary force-revival accounting preserve real native counter decisions. Mandatory Ix phase5 opening retains the future turn/reset. Held Ghola and known/paid Box recovery now add exact force returns and later shipment; normal revival allowances stay separate and native income is not precredited. CHOAM own unresolved opening, later rounds, wider preparations/modules remain unfinished.', evidence: ['game/ghola-force-preparation.ts', 'game/early-auction-funding.ts', 'game/movement-promises.ts', 'game/movement-promise-search.ts', 'game/movement-shipment-preparation.ts', 'game/movement-discard-knowledge.ts', 'game/truthtrance.ts', 'game/engine.ts'] },
|
||||
{ area: 'Player controls', status: 'Partial', detail: 'Native No-Field preparation now has distinct persistent reveal/shipment/marker/mixed labels and actual reveal callbacks. Classic Advanced questions and persistent guidance now connect an actual legal movement callback; native advisor preparation has a named action. Physical Ghola force-return guidance now precedes actual reserve shipment and ground movement, with income/disposal controls retaining priority. Persistent busy-safe guidance includes current Charity, Revival, Ready, ordinary bid-pass/payment and event-bearing native Once Around pass/Silent zero. Foreign pending funding cannot force a speculative positive bid or expose future movement. Actual native pass/zero handlers preserve unread obligations. Existing waiting/card controls remain; browser/visual acceptance stays open.', evidence: ['tests/truth-movement-advanced-controls.test.tsx', 'components/movement-claim-fields.tsx', 'components/movement-promises.tsx', 'components/shipment-promises.tsx', 'components/truthtrance.tsx', 'components/nullentropy-box.tsx', 'components/game-table.tsx', 'tests/truth-movement-waiting-controls.test.tsx', 'tests/truth-movement-early-controls.test.tsx', 'tests/truth-movement-native-early-controls.test.tsx', 'tests/truth-movement-ghola-controls.test.tsx'] },
|
||||
{ area: 'AI', status: 'Partial', detail: 'All four existing profiles use actual native Advanced Richese reveal/physical movement legally. All four existing profiles finish a genuine classic Advanced movement question legally without strategy changes. Eight earlier existing-profile held-Ghola/known-Box runs accept40 proposals plus24 separate native continuations. Physical return, exact paid recovery, unused shipment and qualifying ground move use the same private legal guidance. Earlier native funding paths remain. Minimal legal participation only; no strategy tuning or calibration.', evidence: ['game/bots.ts', 'tests/truth-movement-ghola-engine.test.ts', 'tests/truth-movement-native-early-engine.test.ts'] },
|
||||
{ area: 'Documentation', status: 'Implemented', detail: 'General FAQ authority, existential single-event meaning, explicit software composition, source selection and unfinished scope are documented.', evidence: ['docs/TRUTHTRANCE_MOVEMENT_PROMISES.md', 'docs/RULE_DECISIONS.md'] },
|
||||
{ area: 'Verification', status: 'Partial', detail: '25 October classic Advanced:26 files238/238, types/lint/build and four actual-control/GameTable SSR journeys, including a real guidance callback, pass. Genuine23/27-action Atreides/Fremen sources earn original setup/cards, paid/free shipment and actual Karama auction. BG acquisition/stance/reserve overlays are explicitly controlled. Hajr, typed counter outcomes, preparation/matching and city-range arrival flips pass. Earlier22-file182/182 Ghola evidence follows. Original held/Box prefixes42/104/75/151 use native setup, actual cache purchase, paid desert shipments, Storm casualties and settled bribes without Advanced city income. Eight minimal-profile runs accept40 proposals plus24 separate native continuations. Controlled acquisitions/losses/knowledge and counter-enabled income disclose staging. Ghola discard relevance and a fixture field collision are repaired before final frozen proof. Earlier counts remain historical. Full modes, wider preparations, browser/deployed and comprehensive assurance remain open.', evidence: ['tests/truth-movement-advanced-engine.test.ts', 'tests/truth-movement-advanced-controls.test.tsx', 'tests/fixture-truth-movement-advanced.ts', 'tests/truth-movement-waiting-engine.test.ts', 'tests/truth-movement-waiting-controls.test.tsx', 'tests/fixture-truth-movement-waiting.ts', 'tests/fixture-truth-movement-waiting-box.ts', 'tests/fixture-hajr-replay.ts', 'tests/truth-movement-early-engine.test.ts', 'tests/truth-movement-early-controls.test.tsx', 'tests/fixture-truth-movement-early.ts', 'tests/early-auction-funding.test.ts', 'tests/truth-movement-native-early-engine.test.ts', 'tests/truth-movement-native-early-controls.test.tsx', 'tests/fixture-truth-movement-native-early.ts', 'tests/fixture-truth-movement-ghola.ts', 'tests/truth-movement-ghola-engine.test.ts', 'tests/truth-movement-ghola-controls.test.tsx', 'tests/movement-discard-knowledge.test.ts', 'docs/TRUTHTRANCE_MOVEMENT_PROMISES.md'] },
|
||||
{ area: 'Verification', status: 'Partial', detail: '25 October native No-Field/Ixian:24 files163/163 pass; two fixture-only lint repairs then pass3 files15/15 consumers, types/lint/build and six native control/GameTable SSR journeys. Hashes retain all application/other files. Original Richese Basic24/Advanced27 and Ixian43 source actions earn cards, payments and typed groups. Controlled cap/card/force/earlier-clock boundaries stay labelled. Earlier classic Advanced:26 files238/238, types/lint/build and four actual-control/GameTable SSR journeys, including a real guidance callback, pass. Genuine23/27-action Atreides/Fremen sources earn original setup/cards, paid/free shipment and actual Karama auction. BG acquisition/stance/reserve overlays are explicitly controlled. Hajr, typed counter outcomes, preparation/matching and city-range arrival flips pass. Earlier22-file182/182 Ghola evidence follows. Original held/Box prefixes42/104/75/151 use native setup, actual cache purchase, paid desert shipments, Storm casualties and settled bribes without Advanced city income. Eight minimal-profile runs accept40 proposals plus24 separate native continuations. Controlled acquisitions/losses/knowledge and counter-enabled income disclose staging. Ghola discard relevance and a fixture field collision are repaired before final frozen proof. Earlier counts remain historical. Full modes, wider preparations, browser/deployed and comprehensive assurance remain open.', evidence: ['tests/truth-movement-no-field-engine.test.ts', 'tests/truth-movement-no-field-controls.test.tsx', 'tests/fixture-truth-movement-no-field.ts', 'tests/truth-movement-ixian-engine.test.ts', 'tests/fixture-truth-movement-ixian.ts', 'tests/truth-movement-advanced-engine.test.ts', 'tests/truth-movement-advanced-controls.test.tsx', 'tests/fixture-truth-movement-advanced.ts', 'tests/truth-movement-waiting-engine.test.ts', 'tests/truth-movement-waiting-controls.test.tsx', 'tests/fixture-truth-movement-waiting.ts', 'tests/fixture-truth-movement-waiting-box.ts', 'tests/fixture-hajr-replay.ts', 'tests/truth-movement-early-engine.test.ts', 'tests/truth-movement-early-controls.test.tsx', 'tests/fixture-truth-movement-early.ts', 'tests/early-auction-funding.test.ts', 'tests/truth-movement-native-early-engine.test.ts', 'tests/truth-movement-native-early-controls.test.tsx', 'tests/fixture-truth-movement-native-early.ts', 'tests/fixture-truth-movement-ghola.ts', 'tests/truth-movement-ghola-engine.test.ts', 'tests/truth-movement-ghola-controls.test.tsx', 'tests/movement-discard-knowledge.test.ts', 'docs/TRUTHTRANCE_MOVEMENT_PROMISES.md'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
+1
-1
@@ -209,7 +209,7 @@ function parseQuestion(g: Game, asker: string, value: unknown): TruthQuestion {
|
||||
check(Object.keys(v).sort().join(',') === 'kind,minimum,target,territory',
|
||||
'Supply one ground-movement destination, minimum and respondent.');
|
||||
check(movementPromiseTargetAvailable(g, v.target as string),
|
||||
'Ground-movement promises need a supported Basic opportunity this round or a classic Advanced Movement turn, with pending decisions finished. Optional modules and Advanced expansion preparations remain unfinished.');
|
||||
'Ground-movement promises need a supported Basic opportunity this round or an Advanced Movement turn with declared native families and pending decisions finished. Optional modules and earlier Advanced forecasts remain unfinished.');
|
||||
try { return { kind: 'movement', target: v.target as string, ...parseMovementClaim(v) }; }
|
||||
catch (error) { if (error instanceof MovementClaimError) throw new TruthError(error.message); throw error; }
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user