Files
Dune/docs/HOMEWORLD_RESERVE_INTEGRATION_AUDIT.md
Quintine 28ba1618ba Implement Homeworld mechanics through Caladan reinforcement checkpoint
Connect typed Homeworld custody, transport, battle, income, revival and Caladan reinforcement flows with private multiplayer recovery and controls. Preserve the optimized test workflow and keep unfinished rules modes gated. Verified with 3682 offline tests, typecheck, lint, production build and 40 HTTP tests; existing saved games remain unchanged.
2026-09-10 08:12:26 +10:00

24 KiB
Raw Permalink Blame History

Homeworld reserve integration audit

Read-only engine audit, 9 September 2026. Line references identify the source examined before the coordinator's Homeworld setup/Emperor-transfer integration; function names are the durable anchors. No runtime edits or new rules are introduced here. The source contract is HOMEWORLD_RULES.md, with the broader location architecture in HOMEWORLD_RUNTIME_READINESS.md.

Authoritative custody and an important Ix correction

game/homeworld-custody.ts derives native primary forces from Player.reserves and elites.reserves, subtracting the explicitly stored Salusa normal/elite allocation. Foreign garrisons are separate. quoteHomeworldCustody changes aggregate native totals and Salusa together and returns exact before/after receipts; it does not perform the matching board/Tanks transfer or authorize the rule action. Every native withdrawal/deposit must therefore be composed with its board/Tanks delta exactly once.

Ix setup is already correct. initializeStartingForces (engine.ts:3473) sets remaining Cyborg reserves to four and places three Cyborgs among six HMS forces. place (3291) only credits a location, so the resulting four reserves plus three deployed counters are the seven physical Cyborgs. Do not subtract three from the four again. newPlayer (2076) correctly has all seven before setup; tests/ix-forces.test.ts:113–121 checks a seven-counter inventory. The new custody helper initially used an Ix maximum of four; that is the initial remaining reserve count, not the physical cap. The coordinator was notified to change that cap to seven and add a return/revival regression. Later all seven may legally be native reserves.

Real setup hook

Hook Current behavior Correct integration responsibility
newPlayer, 2072–2077; lobby faction action, 15846 Fresh reserves20; only Ix has an initial elite record, with seven. Faction changes replace the elite record. Store the module option separately. Do not freeze native pools or Salusa against a lobby roster that can still change.
start, 3403 Public Basic/base/no-expansion gate. Explicitly retain the Homeworld gate while only native setup/transfer is integrated. No implicit activation from possessing a card catalog.
initializeSetup, 3420 Sets decks/order and enters prediction stage; private traitor deal follows later. Validate the module/configuration here, but do not prematurely install a native snapshot or expose starting cards before the established prediction/traitor order.
initializeStartingForces, 3457–3497 Grants starting spice, creates faction pieces, deletes/rebuilds elite state, and applies fixed board placements. Install initial custody after these operations. Advanced Emperor's five initial Sardaukar then populate Salusa. Other native groups derive from the final remaining totals.
advanceSetup, 3566–3574 Runs the force initializer once, then waits for Fremen/BG placement before dealing Treachery. Never re-run createHomeworldCustody on every normalization or force choice: that would overwrite later saved allocations.
Fremen setup, 15907–15959 Places ten with explicit elitePlacements, subtracts selected elites, sets reserves10. Preserve the selected physical starred counters; derived native custody shrinks accordingly. Creation of the identity record must precede this choice.
BG setup, 15870–15892 Advanced starting advisor removes one native reserve. Debit its sole native pool; preserve setup order/stance.
Moritani setup, 14715–14736 Places six after the other setup conditions complete. The existing setup sentinel is reserves20. Do not alter that count merely to represent the Homeworld. Its native pool then becomes fourteen.
initializeBaseGameForAudit, 3579–3638 Strict cloned fresh base lobby without optional modules. Add the Homeworld exclusion to this existing contract. A new explicitly named fresh all-faction audit seam can exercise the shared initializer without changing public start authorization.

An installation after fixed placements is safe while the later choices remain genuine setup actions: native counts are derived, so their subsequent single-world debits need no recreation of custody. Initializing at finishSetup instead is too late for a source-correct setup view and risks treating all later reserve mutations as unobserved initialization. Do not retrofit a Salusa allocation into an arbitrary already-playing save by guessing where its Emperor forces were located.

Every existing engine reserve mutation category

The following are actual mutation sites, not every occurrence of the word reserves. Pure prerequisite helpers and projections must receive the matching source data, but must not commit these deltas themselves.

Route / exact source anchor Present mutation Required native allocation and failure example
revealPlayerNoField, 1329 Materializes Richese forces from reserves. Debit Richese's native pool only when physical forces are revealed; deploying the marker alone does not debit its hidden denomination. Never count marker plus still-native physical forces as two inventories. All reveal callers inherit this path.
commitShipment, 13107–13109 Debits selected total and elite reserves, then places physical forces; allied No-Field physical shipment shares this branch. Advanced Emperor needs exact Kaitain/Salusa source quantities, potentially combined in one shipment. An aggregate subtraction can leave salusa.elite > eliteReserves, or silently withdraw regulars from Kaitain when those regulars are actually on Salusa. Preserve source allocation through pending Guild/Karama declarations.
commitAmbassadorShipment, 7074–7076 Free Guild Ambassador selected native forces to Arrakis. Same physical source requirement even though payment and ordinary counters are absent. Emperor beneficiary may need both native pools; do not infer location from elite alone after interworld transfers.
finishGuildAmbassadorAdvisor, 7265; finishResponse BG advisor branch, 12872 Removes one BG reserve for free accompaniment. Debit BG's single native pool; source-specific destination restrictions remain separate. No new Homeworld advisor permission follows from the custody helper.
decideTerror Sneak Attack, 8347 Moritani reserve reinforcement. Debit the native pool, preserving its independent arrival continuation and zero-choice behavior.
applyGholaEffect, 10123–10128 Tanks→reserves and elite Tanks→elite reserves; increments elite usage. Deposit revived Emperor normal forces on Kaitain and Sardaukar on Salusa through the prescribed revival routing, while preserving Ghola's own quota/income semantics. Do not merely increase aggregate elite reserves and thereby derive the new Sardaukar on Kaitain.
finishRevival, 11688–11704 All ordinary force and Emperor-funded-extra revival recipients share this commit. Same typed return routing; preserve payer versus recipient, frozen price, ordinary/free counters and exact quoted Axlotl/Tleilaxu income. Leader, Auditor, KH and buyback branches do not themselves move force reserves.
executeSpecialKaramaIntent, Emperor forces, 13944–13951 Free Tanks→reserves with typed counter usage. Must use the same typed native revival deposit. The card's cost/once-per-game flag remains owned by the surrounding effect.
finishResponse CHOAM Trip to Gamont, 12269–12273 One selected board force returns to its owner's reserves, preserving elite identity. Non-revival native return needs an explicit destination contract for two-world Emperor; do not automatically classify every return as revival or increment revival counters. Keep the unresolved destination policy separate from the otherwise definite physical return.
applyActionInner, Face Dancer replacement, 15278–15294 Tleilaxu may withdraw own reserves as replacement; every defeated winner force returns from the territory to its native reserves, including its elites. Debit Tleilaxu native reserves. For an Emperor winner, the returning normal/elite group needs a source-appropriate non-revival deposit decision/contract. Normal board replacement is not shipment and must not add fees or movement counters.
applyActionInner, guildShip, 16811–16817 Paid Fremen ally reserve route debits reserves; Guild board return credits reserves; board cross-shipment uses removeGroup. Current genuine native actors have one native Homeworld, but future Junction sponsorship exposes arbitrary recipients. Retain explicit return destination and typed custody rather than treating every to:'reserves' as a universal primary-pool return.
New Emperor internal transfer Not present in the audited snapshot. One atomic withdrawal+deposit between Kaitain and Salusa; aggregate reserves and total elite reserves remain unchanged. Validate both source physical types before debit, then update movement consumption once. No shipment fee, no Heighliner grant, and no piece multiplication.

Setup writes described above are the remaining direct reserve mutations in the audited engine. Outside it, homeworld-custody.ts mutates only its detached quote results; the search found no other production module directly changing a player's reserve count. Pure helpers that return transformed custody, disaster calculations or views are not additional live commits.

game/tleilaxu-ambassador-forces.ts is a pure prospective quote, not an additional integrated Ambassador commit. Likewise the Ecaz Reinforcements/Harass & Withdraw definitions and the new Homeworld free-revival/reinforcement faces must eventually use the same native transfer boundary, but this inventory does not invent live handlers for them. Tleilaxu Ambassador entitlement questions remain separate from routing its eventual actual returns.

Basic physical identity is not an Advanced power switch

E3 explicitly uses starred Fremen identity in Basic Homeworlds. Current code normally has no Basic Fremen/Emperor elite record, so several consumers implicitly use record presence as a feature flag. Preserve physical identity without accidentally introducing Advanced benefits or restrictions.

Consumer Existing behavior / required separation
combatForces, engine.ts:3347–3381; bot fallback bots.ts:535 The descriptor sets elite strength2 without a Basic-Fremen guard. Gate benefit strength by rules mode while preserving physical counts. Ordinary Basic scoring is not universally doubled today: the separate plan gates below already keep normal Basic dial limits. The descriptor still feeds public ownForces, Stone Burner and direct casualty calculations.
validatePlan, 9766; findLegalBattlePlan, 10161; bot plans, 545; Prescience answer, 17124 Already use `advanced
Combat power queue, 9953–9959 Already offers Emperor/Fremen elite and Fremen support responses only in Advanced. Preserve that guard.
eliteRevivalRemaining, revival.ts:182 Has no game-mode argument: non-Ix tracked elites are capped at one per turn. Mode-aware eligibility is necessary before Basic physical tracking is made playable. Do not silently adopt an Advanced restriction from existence of the counter record.
Its engine callers gholaOptions:10054, applyGholaEffect:10120, Ghola/card prerequisite 10240, special Karama validation 13687, extra revival 16259, ordinary revival declaration 16466, projected revival choices 17890. All must agree with the actual commit.
Its other callers revival-cancellation.ts:101 saved/canceled revival validation; bots.ts:1250,2616,2626,2636; components/game-table.tsx:3262. Updating only the live declaration leaves restored receipts, bots or UI contradictory.
battle-resolution-quote.ts:456; kill:3296 Basic normal winners take immediate untyped dial losses, and default kill selects normal counters first. Once starred identity has future Homeworld value, this is a separate typed-casualty selection audit: do not claim identity-aware combat complete merely because the counters survive setup. Total losses and ordinary strength must remain Basic.
Fremen setup bot, bots.ts:1067 Currently omits elitePlacements and therefore leaves all stars in reserves. This is legal, though a meaningful future Homeworld policy should reason from the actual card. The human action already accepts explicit setup allocation.
place, 3291; kill, 3296; removeGroup Preserve typed location/Tanks quantities mechanically. Their physical accounting does not itself require Advanced strength. Retain it rather than discarding Basic special identity after movement or death.

Basic Emperor uses only Kaitain. Preserving its five printed counter identities is compatible with the location model but has the same mode-aware combat/revival obligations; it is not necessary to create Salusa in Basic. Ix always retains its own printed typed mechanics in both modes.

Saved commitments and speculative preparation

Pending shipments currently bind aggregate amount/elite, not a split native source. New source selections must be part of the original intent and revalidated at final allowance. Equal aggregate totals do not prove the same Homeworld still contains a promised group. This applies to ordinary shipment, Guild Ambassador shipment and any future Junction-sponsored shipment.

ordinaryDiscardSignature (engine.ts:2238) captures completed reserve/elite outcomes for Ghola, but a new Salusa allocation is not represented by old aggregate fields. Extend appropriate completed-effect/signature receipts when Homeworlds are active; a wrong secondary allocation with unchanged totals must not pass recovery. Revival cancellation and terminal preflight must quote the real typed destination without performing effects or payments twice.

findShipmentCompletion uses internal shipmentPreparation execution to simulate real Ghola preparation while suppressing only the ordinary discard frame. Its cloned native custody must change through the same helper as live execution. Otherwise an apparently legal Ghola→shipment witness can fail after the actual Ghola restores Sardaukar to a different pool. Projections may inspect public native counts and owner-controlled source options, but cannot mutate a live Homeworld or emit income while constructing a witness.

Required concrete checks for this integration

  1. Actual prediction→traitors→fixed forces→Fremen/BG/Moritani choices→starting cards preserves existing order. No custody recreation after JSON reload; no duplicate native pieces. Every seated faction receives exactly its active Homeworld locations.
  2. Ix setup retains three Cyborgs on HMS and four native; later a legal return can yield seven native Cyborgs. Full physical totals include visitors and Tanks, not just the helper's local native-plus-visitor bound.
  3. Advanced Emperor starts Kaitain15normal/Salusa5Sardaukar. Transfer normal forces to Salusa, then reject an attempted withdrawal from empty Kaitain even when aggregate reserves suffice. A reverse transfer restores the original allocation and consumes only the appropriate movement once.
  4. A mixed outbound Emperor shipment can use both locations once; pending/reloaded/canceled declarations preserve the selected source quantities. A source corruption with unchanged total reserves rejects atomically.
  5. Normal and Ghola revival credit the correct typed native pool and preserve unrelated counters/income. Non-revival card returns must not be silently forced through a revival helper with new quota or placement semantics.
  6. Basic Fremen retains all three stars across setup, movement, Tanks and revival without double strength. Check authoritative action, public descriptor, all bot levels and resumed prerequisite against the same mode-aware eligibility.
  7. Existing games with the option disabled retain their behavior and saved state. The new fresh audit seam rejects reused/dirty state; public starts remain gated until complete Homeworld gameplay is implemented.

The audit found the concrete Ix custody-cap error described above and the Basic-record/mode coupling. It does not claim that the Homeworld module is playable, choose an unresolved Emperor non-revival return destination, or infer any other new rules from an implementation convenience.