12 KiB
Caladan victory reinforcement: integration audit
Historical pre-integration audit. The subsequent runtime checkpoint records the implemented hook and evidence. The 26 October selected before-Face-Dance order supersedes this audit’s earlier ordering fence; the remaining historical structural recommendations do not establish further rulings.
10 September 2026. Read-only, bounded runtime audit. This identifies integration points for the high-Caladan optional reserve force; it does not establish new rules, certify the feature, or change existing saves. The source/quote owner must settle the timing and destination boundaries called out below.
The current Caladan component requires a battle win and an existing own force at that battle's territory or Homeworld. Its high threshold is six native forces. The Homeworld rule inventory identifies this as a post-battle transfer, separate from ordinary shipment.
Actual battle completion order
The following functions are in engine.ts. The ordering is observable across saved discard batches, decisions, and responses; the initial winner announcement is not the end of cleanup.
| Stage | Existing behavior | Reinforcement implication |
|---|---|---|
chooseBattle |
Creates a fresh Battle.event; validates the selected unresolved board/Homeworld battle. Voice, prescience, Stronghold powers, and later revealed-plan responses belong to this battle. |
Bind a new entitlement to this event, not territory plus turn. Multiple battles can occur in the same territory and turn. |
resolveBattle |
Uses the committed resolution quote; pays support/bounties, kills losers, and applies immediate Basic winner losses where the quote supplies them. Traitor wins preserve their winner; mutual traitors/explosions do not declare a winner. | A winner can have no surviving force. Never add a force before final winner casualties to make an otherwise eliminated winner survive. A native explosion survivor is not a battle winner. |
| Resolved context | Sets lastBattleContext with event, turn, territory, combatants, winner and result. Stages winner mandatory-discard commitments separately. Saves Homeworld casualty commitments, clears g.battle, and stages the losing/mandatory discard batch. |
This is a place to record new-event provenance, but not yet to expose an actionable reinforcement. Keep a pending marker even when cleanup suspends. |
continueResolvedBattle |
Runs after the first discard continuation. Applies the sole casualty option, exposes battleLosses, or routes a native explosion through homeworldExplosion. |
The reserve force cannot enter the committed casualty pool or change the number/type of losses. |
settleWinnerCasualties |
Applies real normal/starred casualties; clears the Homeworld loss record. A surviving Ixian Suboid and a lost Cyborg can produce ixSubstitution. |
Do not bypass the casualty or substitution continuation. Atreides cannot itself be the Ixian substitution winner, but a generic cleanup hook must retain this ordering. |
finishWinner |
Retires mandatory winner cards through winnerMandatoryDiscard, then exposes optional battleCards, or calls finishBattle. |
The new choice must not replace a mandatory discard or reuse its optional-card list. Winning Cheap Heroes/Poison Tooth can have a separate physical retirement batch. |
| Optional winning discards | The current battleCards decision handler stages battleCleanup; finishTreacheryDiscard resumes finishBattle only after retirement. |
A zero-card choice and a nonempty discard batch must reach the same reinforcement stage once. |
finishBattle aftermath |
Uses battle-aftermath-quote.ts: Moritani retention, CHOAM battle income, technology transfer, Harkonnen capture, CHOAM Auditor, Face Dance, then board continuation. | finishBattle is called repeatedly. An unchecked hook here would grant several forces for one battle. |
| Board continuation | Once the quote returns board, computes remaining battles and activates the next attacker; otherwise calls nextPhase. |
This is the narrow final interception point before another battle or Collection. Do not advance the phase while an owned choice/arrival remains. |
g.battle = null retires the old Voice, prescience, revealed plans and battle
power checks. Their completed context is historical evidence; reinforcement
must not recreate g.battle, restart beginBattlePowers, call
finishBattlePreparation, or revive a spent Voice response. Captured leaders are
returned/marked during resolution and the capture aftermath; reinforcement does
not reopen their battle eligibility.
Recommended hook and the timing boundary
The smallest structural hook is immediately before the final board branch of
finishBattle, after currentBattleAftermathQuote and its existing deterministic
steps have committed. It opens an optional owned decision or records a terminal
skip, then eventually resumes that same board suffix. This preserves every
existing casualty, discard, capture and resource response.
This placement is a recommendation about code structure, not a source ruling
that Caladan follows Face Dance. Face Dance removes the original winner's
entire remaining army and returns it to native reserves; the Tleilaxu replacement
can even be zero. If reinforcement occurs after it, the original winner has no
survivor and cannot reinforce. If sources require reinforcement before it, add a
distinct aftermath stage immediately before Face Dance, and let its completion
resume Face Dance. Do not choose between these outcomes accidentally by placing
a generic end-action observer. The winner in lastBattleContext does not change
when a Face Dancer replaces it.
The same source decision must identify when to evaluate native high population: at the original win, after casualty cleanup, or when the choice is used. Native Caladan casualties can change that side. Record the rule-defined eligibility facts; recheck current physical availability and destinations before mutation. Do not infer a new entitlement simply because a subsequent return raises Caladan.
Minimal durable contract
Use a feature-specific optional Game record; keep it outside the exact-shape
homeworlds.custody object. A practical record contains:
- Original battle event, turn, player, target combat-location ID, result and rule-defined qualifying facts. Bind these immutable fields in a canonical signature.
- A stage such as
waiting,choice,arrival,complete; a fixed grant of one ordinary force; and a signed completion/decline outcome. A completed marker must survive every repeatedfinishBattlecall for that event. - An owned decision carrying the same event. Accept only the current event and a currently offered sector/destination, or explicit decline. Do not accept a posted winner, battle result, source balance, or additional amount.
- If a transfer can cause an arrival reaction, a saved parent describing the exact board/aftermath suffix, plus original arrival provenance and completion evidence. Retire the reinforcement transfer before executing a child.
Validate the record and owned decision together in the common integrity path
used by viewGame, applyAction, and normalizeAutomaticGame. A signed record
does not by itself detect deletion: a new battle's independently committed
context needs an obligation/completion marker if deleting the pending record
must be rejected. The existing lastBattleContext.winnerDiscards versus
pendingWinnerDiscards pair illustrates why this matters.
Reject malformed original fields, contradictory stages, missing required owned choices, stale battle events, and replayed completion before any custody, log, resource, phase or response mutation. A skip because there is no surviving force must also become terminal for the event; repeated normalization cannot wait for some later action to manufacture a survivor.
Physical destinations and reactions
Use combat-location.ts and
homeworld-custody.ts, rather than an Arrakis-only
at()/p.forces test. Homeworld defenders/visitors have physical forces outside
ordinary board keys. For Atreides' source, withdraw one ordinary counter from the
actual native pool through homeworld-native-reserves.ts.
An aggregate reserve count or foreign visitor does not prove native availability.
For an Arrakis target, require the original battle territory and a legal offered sector, then withdraw and place the one counter atomically. For a foreign Homeworld target, withdraw native custody and deposit explicit visitor custody. Retain allied-Homeworld and applicable occupancy restrictions; do not silently route to a different territory. The native-Caladan target needs an explicit source/quote decision: native reserves are already the native battle army, so withdrawing and depositing into the same pool cannot create an additional physical counter. Do not increase native population as though a force came from outside custody.
Do not reuse ship or guildShip: those actions consume phase-five usage and
can assess Guild income/technology and response windows. This grant must not
change shipped, moved, spice, revival allowances, Tanks, or the already paid
battle support merely to add a reserve force.
openTerritoryEntry composes Ecaz Ambassador and Moritani Terror and expects a
supported cause plus a clear decision/response parent. It is not a generic safe
callback: it can open multiple saved children or reject competing entry effects.
The new reinforcement's shipment/movement classification, storm permission,
Spiritual Advisors, Intrusion, and Terror need the source owner's explicit
contract. Gate any unresolved affected destination before transfer. The recently
added revival return shows a durable
Ambassador chain but is a different entitlement and must not be reused by
pretending victory reinforcement was revival.
Persistence and verification seams
rooms.ts persists automatic normalization with a version CAS.
Its needsAutomaticRoomRecovery predicate must recognize any new waiting or
arrival stage that needs to advance without a user action. A deliberate human
choice should remain pending; bots use its projected legal options. Never mutate
on read or write an unfenced completion from a projection helper.
Older saves can contain an already completed lastBattleContext or a legacy
cleanup context constructed by cleanupBattleContext. Neither proves that a
new Caladan offer is owed or was previously consumed. Missing new-feature
provenance must remain legacy-compatible without granting a retrospective force.
Newly resolved battles can opt into the signed record at their real commit.
No database migration needs to rewrite old room JSON or fabricate battle history.
Focused regressions should use genuine battle resolution: ordinary and traitor wins; zero survivors; mutual traitors/native explosions; losing and winner discard retirement; before/after final casualty population; same-location second battle; Homeworld visitor custody; chosen-versus-declined single grant; stale events/corrupt or deleted queues; Face Dance ordering; and any supported arrival child. Production in-memory SQLite should race two identical decisions and two automatic workers across reload, asserting one transfer and one suffix, exact twenty-counter custody, unchanged battle losses/usage, private observer views, and zero writes for rejected corruption.
Only this document was created. No production engine, persistence code, stored room, test fixture, or release gate was changed by this audit.