diff --git a/components/discoveries.tsx b/components/discoveries.tsx index e3ed120c..ce4531a0 100644 --- a/components/discoveries.tsx +++ b/components/discoveries.tsx @@ -95,8 +95,8 @@ export function DiscoveryPanel({ game, act, busy }: Props) { })}

Inspection and reveal are optional. Next-turn free entry, Cistern income for every fighter occupant and later-turn Ornithopter movement are available in the development prototype. Jacurutu income, Testing Station storm choices and Shrine card conversion are prototyped. {game.advanced - ? 'Each Orgiz fighter occupant takes one spice per qualifying territory from the first actual foreign collector in storm order. Shared Ecaz lots wait for the actual allocation: Ecaz first, then ally, excluding zero shares and the occupant itself.' - : 'Each Orgiz fighter occupant keeps the provisional one-spice transfer per positive board deposit from the first actual foreign collector in storm order. Basic shared Ecaz allocation remains pending.'} Actual ordinary Collection receipts identify each payer; co-present forces or old wallets alone never qualify. An empty purse never creates debt. Mixed physical Jacurutu dials use a defeated-player choice before settlement. Testing Station occupants each choose once in original storm order, cumulatively. These ordering/allocation interpretations are provisional.

+ ? 'Each Orgiz fighter occupant takes one spice per qualifying territory from the first actual foreign ordinary collector in storm order.' + : 'Each Orgiz fighter occupant keeps the provisional one-spice transfer per positive board deposit from the first actual foreign ordinary collector in storm order.'} Shared Ecaz lots wait for actual allocation, then consider positive foreign recipients Ecaz first, ally second, excluding zero shares and the occupant itself. Basic counts actual positive deposits; Advanced counts the territory once. Actual ordinary receipts identify each payer; co-present forces or old wallets alone never qualify. Empty purses never create debt. Mixed Jacurutu dials use a defeated-player choice. Testing Station occupants each choose once in original storm order, cumulatively. These ordering/allocation interpretations are provisional.

); } diff --git a/docs/CURRENT_STATUS.md b/docs/CURRENT_STATUS.md index 4fa5fe32..b46344cf 100644 --- a/docs/CURRENT_STATUS.md +++ b/docs/CURRENT_STATUS.md @@ -29,7 +29,27 @@ optional skill bands, combinations and public expansion starts remain open. Rules implementation continues; comprehensive assurance and administration remain deferred until all rules are implemented. -**Canonical Orgiz Collection sources — 14 October.** +**Basic shared Orgiz settlement — 14 October.** +Both modes now wait for the original Ecaz/ally allocation before Orgiz. +`quoteSharedOrgiz` replaces the Advanced-only path: Advanced charges once per +territory; Basic counts actually reduced positive deposits in the original +saved source map for that territory. Positive foreign recipients are considered +Ecaz first, then ally, skipping zero shares, self and empty purses. Every +transfer updates available balances and checks cumulative overflow. The +original allocation event prevents replay; no saved schema is added. + +The native Basic equal split reproduced the missing payment before the fix. +Now equal/full/zero shares, acceptance timing, wrong actors and stale events +pass in both modes; multi-source/fallback/overflow boundaries also pass. +Final six-file union80/80, types/lint/build pass. Actual390px Basic Red Chasm +lot4: Use equal split changes Ecaz7/Guild8→8/11 with one transfer, no decision +and Ready restored. No horizontal overflow or browser errors. All four legal +policies independently complete that Basic split through real engine actions. +Basic's per-deposit mapping and ordering remain provisional; broader module, +rules-mode and deployed acceptance remain open. +[Contract](DISCOVERY_PROTOTYPE.md#multi-occupant-and-shared-cisternorgiz-collection--14-october-2026). + +**Earlier canonical Orgiz Collection sources (`bcd1b10`) — 14 October.** The original ordinary quote now records positive player/location/amount deposit receipts before Orgiz. Both modes use actual collectors, not co-present force inference: Basic groups by positive deposit; Advanced groups by territory. @@ -45,8 +65,8 @@ the other taking0. Face1 is selected at the actual turn-two forecast shuffle, not assigned to a later source. Final six-file union72/72; types/lint and app build pass. Actual390px Finish shipment & movement turns phase5→7, Ecaz7/Guild10→Ecaz8/Guild14 with one Orgiz transfer and Ready restored. -No horizontal overflow or browser errors. Basic shared-allocation settlement, -complete modules and deployed acceptance remain open. +No horizontal overflow or browser errors. Basic shared settlement was pending +at that checkpoint and is now connected above; full/deployed acceptance remains open. [Contract](DISCOVERY_PROTOTYPE.md#multi-occupant-and-shared-cisternorgiz-collection--14-october-2026). **Earlier Orgiz payer ordering and native proof (`bc09ac3`) — 14 October.** diff --git a/docs/DISCOVERY_PROTOTYPE.md b/docs/DISCOVERY_PROTOTYPE.md index a94ec3d6..4ccebea6 100644 --- a/docs/DISCOVERY_PROTOTYPE.md +++ b/docs/DISCOVERY_PROTOTYPE.md @@ -75,13 +75,15 @@ recipients in storm order, skipping self and empty purses: once per territory in Advanced, once per positive deposit under Basic's provisional mapping. Co-present forces and old wallets alone do not establish collection. -Advanced shared Ecaz desert lots wait for actual bilateral allocation. Each -occupant considers positive foreign recipients Ecaz first, then ally, charging -at most one spice from an available current balance. A zero share or an old -wallet alone cannot establish eligibility; the owner never pays itself. Basic -shared-allocation Orgiz settlement remains pending. Occupant/payer ordering -is a recommended provisional interpretation, not a new publisher ruling or -separately answered numerical user question. +Shared Ecaz desert lots in both modes wait for actual bilateral allocation. +Each occupant considers positive foreign recipients Ecaz first, then ally, +skipping zero shares, self and empty purses. Advanced charges once per +territory. Basic applies its provisional positive-deposit count to the original +source-map reductions for that territory, excluding unchanged, empty and +unrelated keys. Available balances track every transfer and cumulative +overflow. Original allocation events prevent replay; no saved schema is added. +Occupant/payer ordering and Basic's mapping remain provisional recommended +interpretations, not new publisher rulings or separate numerical user answers. Current native fixtures preserve original armies, wallets, hands, alliances and phases. They use real setup, token placement, paid shipments, inspection, @@ -96,13 +98,15 @@ An exhausted pile leaves the later collector ineligible even with old spice. `tests/fixture-discovery-occupied-collection.ts`, `tests/discovery-occupied-collection-runtime.test.ts`, `tests/fixture-discovery-shared-orgiz.ts` and -`tests/discovery-shared-orgiz-runtime.test.ts`. Focused helper regressions cover -original storm-order payer selection, depleted purses, advisor exclusion and -zero actual collection. The six-file related union72/72, types/lint and app -build pass. Actual390px Finish shipment & movement produces phase7, -Ecaz8/Guild14 from7/10, two actual2-spice source receipts, one Orgiz transfer, -no decision, enabled Ready, no overflow and no browser errors. Basic shared -allocation, broader combined modules and full acceptance remain open. +`tests/discovery-shared-orgiz-runtime.test.ts`. Native Basic/Advanced cases +cover equal/full/zero Ecaz shares, acceptance timing, wrong actors and stale +allocation. Focused boundaries cover multiple Basic source keys, one Advanced +territory, exhausted recipients, foreign-ally fallback and cumulative overflow. +Final six-file union80/80, types/lint and app build pass. Actual390px Basic +Use equal split credits a real Red Chasm lot4, then transfers1: Ecaz7/Guild8 +become8/11; phase7 has no decision and Ready is enabled. No overflow or browser +errors. All four minimally configured policies complete the Basic equal split +through actual engine actions. Broader combinations/full acceptance remain open. ## Mixed E1/E2 and paired E3 Nexus — 7 October 2026 @@ -763,10 +767,11 @@ positive territory Collection. Multiple collected source keys do not multiply the transfer. Canonical positive ordinary deposit receipts identify actual foreign payers in original storm order, including multiple collectors in one sector; self and empty purses are skipped. No presence-based ambiguity gate -remains. Advanced shared Ecaz desert lots wait for actual bilateral allocation, -then consider positive foreign recipients Ecaz first, ally second. Basic -shared settlement remains pending. Ordering is provisional under the -recommended-option priority; the Advanced territory trigger itself is sourced. +remains. Shared Ecaz desert lots in both modes wait for actual allocation, +then consider positive foreign recipients Ecaz first, ally second. Advanced +charges once per territory; Basic uses its provisional count of actually +reduced source deposits in that territory. Ordering and the Basic mapping are +provisional; the Advanced territory trigger itself is sourced. Basic retains its recorded provisional one-transfer-per-positive-deposit interpretation. The context requires its existing public `advanced` boolean: @@ -880,9 +885,10 @@ conversion was fabricated. Basic provisionally charges once per positive deposit. The first actual foreign collector in storm order with available spice pays; mere presence, self and zero actual collection do not qualify. Empty purses never create - debt. Advanced shared Ecaz lots wait for allocation, then consider positive - foreign recipients Ecaz first, ally second. Basic shared settlement remains - pending; occupant/payer ordering remains provisional. + debt. Shared Ecaz lots in both modes wait for allocation, then consider actual + positive foreign recipients Ecaz first, ally second. Advanced charges once + per territory; Basic counts its actually reduced positive source deposits. + Owner/payer ordering and the Basic mapping remain provisional. - **Jacurutu:** one bank spice per opposing undialed physical counter actually sent to the Tanks. Unique readings remain automatic. For different possible @@ -1028,10 +1034,10 @@ to Spice Blow. Refresh restored the same station forces and private Snooper. This is targeted control/recovery evidence, not an unmodified complete game. The 187 opening saved rooms remain preserved; no server restart was needed. -1. Connect Basic shared-allocation Orgiz settlement and refine combined - Discovery interactions. Canonical ordinary receipts now close the guessed - shared-sector payer gap. Other recommended Discovery interpretations remain - visibly provisional; the Basic positive-deposit mapping is not certified. +1. Refine combined Discovery interactions. Canonical ordinary receipts and + actual bilateral shared settlement now cover both modes without guessed + payers or withheld Basic allocation. Recommended interpretations remain + visibly provisional; focused evidence is not complete-module certification. 2. Integrate combined modules and complete games, then refine interaction, strategy and presentation coverage before opening normal start gates. 3. Use the existing prototype starter and saved-room verification for further diff --git a/docs/IMPLEMENTATION_STATUS.md b/docs/IMPLEMENTATION_STATUS.md index e9087754..a4ffe3e0 100644 --- a/docs/IMPLEMENTATION_STATUS.md +++ b/docs/IMPLEMENTATION_STATUS.md @@ -1,5 +1,33 @@ # Dune implementation status +## 14 October 2026 — Native Basic shared Ecaz Orgiz settlement + +BEFORE: a genuine Basic equal split paid the real lot but withheld Orgiz: +Ecaz9/Guild10 instead of8/11. AFTER: `quoteSharedOrgiz` replaces the +Advanced-only quote and hook. Both modes use actual bilateral receipts. +Advanced charges once per territory. Basic counts original positive source-map +reductions in that territory, excluding unchanged/empty/unrelated keys. +Positive foreign recipients are considered Ecaz first, then ally; self, zero +shares and empty purses are skipped. Every transfer updates available balances +and checks cumulative income overflow. Existing allocation events retain their +one-time ownership; no saved schema or compatibility alias is introduced. + +Genuine Basic/Advanced fixtures preserve original armies, wallets, cards, +alliance and phases. Cases cover equal/full/zero shares, acceptance timing, +wrong actor and stale replay. Pure boundaries cover multiple Basic source +deposits versus one Advanced territory, exhausted-recipient fallback, self +exclusion, zero-share old wallets and cumulative overflow without mutation. +One invalid empty-key test fixture was corrected to an actual board sector. + +Final six-file union80/80, types/lint and app build pass. Actual390×844 Basic +table: Use equal split settles the real Red Chasm lot4 and then transfers1, +changing Ecaz7/Guild8→8/11, leaving phase7 with no decision and enabled Ready. +No horizontal overflow or browser errors. Easy/Medium/Hard/Brutal each complete +that Basic split through a real legal engine action; this is not calibration. +Owned hosts/tabs/scaffolding removed; proof outside Git. Existing build +warnings remain. Ordering and Basic's positive-deposit mapping are provisional; +combined modules, full modes, assurance and deployed acceptance remain open. + ## 14 October 2026 — Canonical positive Collection receipts for Orgiz The original ordinary quote now emits `deposits` with actual positive diff --git a/docs/RULE_DECISIONS.md b/docs/RULE_DECISIONS.md index 22f2eb0a..7640eba1 100644 --- a/docs/RULE_DECISIONS.md +++ b/docs/RULE_DECISIONS.md @@ -549,7 +549,7 @@ These are unresolved in the cited records. Do not infer an answer from elapsed t | Nexus responses and composition | Betrayal private-response timing, partial revival/payment choices, Emperor fewer-than-five, fresh advisor/storm cases, Duke restoration and remaining faction-specific combinations. Tleilaxu Secret Ally force accounting, leader slot/eligibility and force-only use remain unresolved after requested publisher/designer source searches; do not infer answers from native Tleilaxu revival or Ghola. | [Common boundaries](NEXUS_CARD_RULES.md#remaining-source-questions-and-release-boundaries), [Tleilaxu source search](NEXUS_TLEILAXU_RULES.md#source-search-follow-up) and individual faction source/runtime documents | | Moritani Atomics after alliance change | The publisher's persistent −1 hand-limit text names Moritani and “your ally,” but not whether the penalty follows a later ally, remains on the original ally or affects both. Requested online source searches found no clarification; the alliance-change case remains gated. | [Terror source boundary](MORITANI_TERROR_RULES.md#material-gaps-and-ordinary-compositions) | | Moritani Sneak Attack entry classification | The printed verb is “Send”; no retrieved ruling classifies the off-planet reserve transfer as a shipment for other arrival powers. The engine now **explicitly adopts** the supported shipment composition: a positive Sneak Attack in Shipment and Movement accrues the Heighliners token once for its owner, then offers the Bene Gesserit Intrusion (Advanced, with fighters present) and the free spiritual-advisor shipment, with the committed Terror entry resolving first; a zero-force resolution accrues nothing and opens no reaction. This order is an implementation interpretation, not a publisher ruling. | [Source boundary and adopted classification](MORITANI_ASSASSINATION_SNEAK.md#adopted-shipment-classification-7-october-2026) | -| Orgiz collection | Basic retains the publisher-p13 per-blow ambiguity and provisional one-transfer-per-positive-deposit mapping. Authorized supplied Advanced rulebook physical24 says each time Collection occurs in a territory containing spice: one transfer per actual positive territory Collection per fighter occupant, never extra transfers for multiple source keys. Canonical positive ordinary deposit receipts now identify actual payers, including co-present collectors; force presence and old wallets alone do not qualify. Under choose-recommended-option priority, owners and eligible foreign ordinary payers resolve in original storm order, skipping self and empty purses. Advanced shared Ecaz lots wait for actual allocation and consider positive foreign recipients Ecaz first, ally second; Basic shared settlement remains pending. Ordering is provisional implementation interpretation, not a separate numerical user answer or publisher ruling. No debt is created. | [Advanced source cutover and Basic boundary](DISCOVERY_PROTOTYPE.md#authorized-advanced-collection-wording) | +| Orgiz collection | Basic retains the publisher-p13 per-blow ambiguity and provisional one-transfer-per-positive-deposit mapping. Authorized supplied Advanced rulebook physical24 says each time Collection occurs in a territory containing spice: one transfer per actual positive territory Collection per fighter occupant, never extra transfers for multiple source keys. Canonical ordinary deposit receipts identify actual payers, including co-present collectors; force presence and old wallets alone do not qualify. Under choose-recommended-option priority, owners and eligible foreign ordinary payers resolve in original storm order. Shared Ecaz lots in both modes wait for actual bilateral allocation, then consider positive foreign recipients Ecaz first, ally second; Advanced charges once per territory, Basic counts actually reduced positive source deposits in that territory. Self, zero shares and empty purses are skipped; balances track every transfer without debt. Ordering and the Basic mapping are provisional implementations, not separate numerical user answers or publisher rulings. | [Advanced source cutover and Basic boundary](DISCOVERY_PROTOTYPE.md#authorized-advanced-collection-wording) | | Jacurutu physical dial | Under the 14 October choose-recommendation priority, the prototype selects the defeated-player exact ordinary/elite/paid reading before settlement. Unique readings remain automatic; actual returned counters never earn income. This replaces the earlier unpaid gap without claiming a separate numerical user answer or publisher ruling. | [Recommended runtime and source](DISCOVERY_PROTOTYPE.md#recommended-physical-dial-and-cumulative-storm-choices--14-october-2026) | | Testing Station contest/timing | The 14 October recommended prototype gives each non-advisor occupant one cumulative adjustment in original storm order, after the ordinary card window and before a single traversal. Original source/base and Weather immunity stay unchanged. Ordering/stacking is explicit provisional interpretation, not a newly found publisher answer. | [Recommended runtime and source](DISCOVERY_PROTOTYPE.md#recommended-physical-dial-and-cumulative-storm-choices--14-october-2026) | | CHOAM Secret Ally battle timing | The printed after-victory inspection is optional and the Nexus identity stays secret. The connected prototype chooses a uniform use-or-continue window for every winner when Nexus is enabled and CHOAM absent; nonholders pass, while a holder may decide after the result. This privacy-preserving prompt is a product inference, not publisher text. A holder-only pause would reveal custody and a precommitment would remove the printed choice. Ordinary Karama cancellation remains unestablished by retrieved sources; no response is invented. | [After-victory inspection boundary](NEXUS_CHOAM_SECRET_ALLY.md#after-victory-inspection-audit) | diff --git a/game/discovery-collection.ts b/game/discovery-collection.ts index 30201a5e..f6c1149d 100644 --- a/game/discovery-collection.ts +++ b/game/discovery-collection.ts @@ -286,35 +286,47 @@ export function quoteDiscoveryCollection( const SHARED_COLLECTION_RELEASES: readonly AdvisorRelease[] = []; -/** Advanced's one-territory event waits for the original bilateral allocation. - * Charge the first actual positive foreign recipient (Ecaz, then ally), never - * a guessed share, the owner itself, a city award or unavailable spice. */ -export function quoteAdvancedSharedOrgiz( +/** Shared collection waits for its original bilateral allocation. Advanced + * charges once per territory; Basic retains one event per positive deposit. + * Only actual positive foreign recipients qualify; current purses cannot owe. */ +export function quoteSharedOrgiz( context: DiscoveryCollectionContext, allocation: SpiceAllocationReceipt, balances: Readonly>, + originalSpice: Readonly>, ): OrgizCollectionReceipt[] { - requireCollection(context.advanced && allocation.ecaz !== allocation.ally && + requireCollection(typeof context.advanced === 'boolean' && allocation.ecaz !== allocation.ally && context.order.includes(allocation.ecaz) && context.order.includes(allocation.ally) && whole(allocation.ecazAmount) && whole(allocation.allyAmount) && - record(balances) && context.order.every(id => whole(balances[id])), - 'Shared Orgiz needs the actual Advanced territory allocation and available balances.'); + record(balances) && context.order.every(id => whole(balances[id])) && + record(originalSpice) && Object.entries(originalSpice).every(([key,amount]) => + whole(amount) && whole(context.spice[key]) && context.spice[key] <= amount), + 'Shared Orgiz needs its actual allocation, original board deposits and available balances.'); const owners = discoveryCollectionOccupants(context,ORGIZ_PROCESSING_STATION,SHARED_COLLECTION_RELEASES); + if (!owners.length) return []; + const locations = context.advanced ? [allocation.territory] : Object.keys(originalSpice) + .filter(key => splitLocation(key).territory === allocation.territory && + originalSpice[key] > context.spice[key]).sort(); + requireCollection(locations.length > 0, + 'Shared Orgiz must retain the original positive Collection source.'); const effects: OrgizCollectionReceipt[] = []; - let ecazDebited=0, allyDebited=0; - for (const owner of owners) { - let payer: string; - if (owner.id !== allocation.ecaz && allocation.ecazAmount > 0 && - balances[allocation.ecaz] > ecazDebited) { - payer=allocation.ecaz; ecazDebited++; - } else if (owner.id !== allocation.ally && allocation.allyAmount > 0 && - balances[allocation.ally] > allyDebited) { - payer=allocation.ally; allyDebited++; - } else continue; - requireCollection(Number.isSafeInteger(balances[owner.id]+1), - 'Shared Orgiz income would overflow the spice balance.'); - effects.push({kind:'orgiz',player:owner.id,from:payer,location:allocation.territory, - amount:1,source:'player'}); - } + const available = {...balances}; + for (const location of locations) + for (const owner of owners) { + let payer: string; + if (owner.id !== allocation.ecaz && allocation.ecazAmount > 0 && + available[allocation.ecaz] > 0) + payer = allocation.ecaz; + else if (owner.id !== allocation.ally && allocation.allyAmount > 0 && + available[allocation.ally] > 0) + payer = allocation.ally; + else continue; + requireCollection(Number.isSafeInteger(available[owner.id]+1), + 'Shared Orgiz income would overflow the spice balance.'); + available[payer]--; + available[owner.id]++; + effects.push({kind:'orgiz',player:owner.id,from:payer,location, + amount:1,source:'player'}); + } return effects; } diff --git a/game/engine.ts b/game/engine.ts index 05958a29..2e6f616c 100644 --- a/game/engine.ts +++ b/game/engine.ts @@ -305,7 +305,7 @@ import { } from './board-resolution-quote'; import { quoteDiscoveryCollection, - quoteAdvancedSharedOrgiz, + quoteSharedOrgiz, DiscoveryCollectionError, type DiscoveryCollectionQuote, } from './discovery-collection'; @@ -19690,15 +19690,14 @@ function decideSharedSpice(g: Game, id: string, action: Action) { `${territory(receipt.territory).name} shared collection: Ecaz received ${receipt.ecazAmount} spice and ${faction(getPlayer(g, receipt.ally).faction).name} received ${receipt.allyAmount}, ${receipt.method === 'agreement' ? 'by agreement' : 'using the equal split with any odd spice going to the ally'}.`, { faction: 'ecaz', name: 'Shared collection' }, ); - if (g.advanced) { - const balances = Object.fromEntries(g.players.map(player => [player.id,player.spice])); - const transfers = nexusRule(() => quoteAdvancedSharedOrgiz(g,receipt,balances)); - for (const transfer of transfers) { - getPlayer(g,transfer.from).spice -= transfer.amount; - getPlayer(g,transfer.player).spice += transfer.amount; - log(g, `${faction(getPlayer(g,transfer.player).faction).name} took ${transfer.amount} spice from ${faction(getPlayer(g,transfer.from).faction).name} through Orgiz Processing Station after the original shared territory allocation.`, - {faction:getPlayer(g,transfer.player).faction,name:'Orgiz Processing Station'}); - } + const balances = Object.fromEntries(g.players.map(player => [player.id,player.spice])); + const transfers = nexusRule(() => quoteSharedOrgiz(g,receipt,balances,pending.sourceSpice)); + for (const transfer of transfers) { + getPlayer(g,transfer.from).spice -= transfer.amount; + getPlayer(g,transfer.player).spice += transfer.amount; + recordFremenCollection(g,transfer.player,`orgiz:shared:${transfer.location}:${transfer.player}`,transfer.amount); + log(g, `${faction(getPlayer(g,transfer.player).faction).name} took ${transfer.amount} spice from ${faction(getPlayer(g,transfer.from).faction).name} through Orgiz Processing Station after the original shared territory allocation.`, + {faction:getPlayer(g,transfer.player).faction,name:'Orgiz Processing Station'}); } } else log( diff --git a/game/reference.ts b/game/reference.ts index 82d1a409..c50896fb 100644 --- a/game/reference.ts +++ b/game/reference.ts @@ -4226,7 +4226,7 @@ export const RULE_TOPICS: RuleTopic[] = [ 'The five revealed location tokens become separate, initially empty territories inside their surroundings. A private peek does not open the location. Normal ground movement enters through the surrounding territory, shipment uses stronghold prices, at most two factions may occupy it, and forces inside are protected from storms and sandworms. Only Jacurutu Sietch counts toward stronghold victory.', 'Native typed forces enter as their actual ordinary and elite groups, never as passengers from the mobile stronghold or the concealed value of a No-Field. Richese may ship or move a concealed marker into an actually revealed nested location at sector zero; it remains one effective presence until its original reveal materializes available reserves. CHOAM Card Stash uses its five-card limit, drawing first and choosing any held discard afterward.', 'At the start of the next turn, before the storm and mobile stronghold movement, each eligible faction may move any positive subset of its non-advisor groups from the surrounding territory into the newly revealed location. The signed choice preserves ordinary and elite forces across sectors, normal source storm and occupancy restrictions, and costs no spice, shipment or normal movement. The printed text gives no sequence and does not restate the source-storm restriction; processing tokens in reveal order and factions in storm order while excluding storm sources are source inferences.', - 'During Collection, after advisor releases, every Cistern fighter occupant receives two bank spice. Orgiz occupants resolve in original storm order using canonical positive ordinary deposit receipts, not force presence or old wallets. Advanced charges once per territory; Basic provisionally charges once per positive deposit. The first actual foreign collector in storm order with available spice pays; self and zero actual collection do not qualify. Advanced shared Ecaz desert lots wait for their real split, then consider positive foreign recipients Ecaz first, ally second. Basic shared settlement remains pending. Payer/occupant ordering is provisional; empty purses never create debt.', + 'After advisor releases, every Cistern fighter occupant receives two bank spice. Orgiz occupants resolve in original storm order using actual ordinary deposit receipts, not mere force presence or old wallets. Advanced charges once per territory; Basic provisionally charges once per positive deposit. The first actual foreign ordinary collector in storm order with available spice pays. Shared Ecaz lots in both modes wait for the actual split, then consider positive foreign recipients Ecaz first, ally second; Advanced counts the territory once and Basic counts actually reduced source deposits. Self, zero shares and empty purses do not qualify. Balances track each transfer without debt; ordering and Basic mapping remain provisional.', 'Jacurutu pays one bank spice for each opposing undialed physical force actually sent to the Tanks. Where an Advanced dial admits different physical counts, the defeated player chooses an exact ordinary/elite/paid reading before support, casualties or rewards settle; unique readings remain automatic. This choice timing is a visibly provisional implementation interpretation. Original Harass returns bind their physical dial and actual withdrawal/retreat counters do not earn spice.', 'Each non-advisor Testing Station occupant may decrease ordinary storm movement by one, keep it or increase it by one. The provisional order is the original storm order, once per owner, with cumulative adjustments on current distance. Choose after the ordinary card window; traversal/protection starts only after all choices. Weather Control is unaffected. Original natural source/base and existing sole-frame history remain authoritative; legacy movement without a source is not invented.', 'A current non-advisor Shrine occupant may play a physical Truthtrance card as Karama, or a physical Karama as Truthtrance, within the implemented timing and effect paths. The printed identity and actual card custody remain unchanged; a committed Truthtrance conversion survives departure. This does not create missing Karama powers or settle their pending rules.', @@ -4236,7 +4236,7 @@ export const RULE_TOPICS: RuleTopic[] = [ checklist: [ { area: 'Implementation', status: 'Partial', - detail: 'Original classic and supported native Skills/Nexus/Discovery entries retain their selected modules and rule controls. Mixed Jacurutu uses an exact defeated-player physical reading before native settlement. Testing Station queues signed cumulative storm-order choices before one traversal. Cistern pays each fighter occupant; Orgiz consumes actual positive ordinary deposit receipts in original storm order, grouping by territory in Advanced and deposit in Basic without guessing co-present payers or creating debt. Advanced shared lots wait for bilateral allocation; Basic shared settlement and broader mode acceptance remain open.', + detail: 'Original classic and supported native Skills/Nexus/Discovery entries retain their selected modules and controls. Jacurutu uses an exact defeated-player reading before native settlement; Testing Station queues cumulative choices before one traversal. Cistern pays every fighter occupant. Orgiz uses actual ordinary receipts and bilateral shared allocations in both modes, grouping by territory in Advanced and positive source deposit in Basic without guessed payers or debt. Available balances track all transfers and cumulative overflow. Broader mode/module acceptance remains open.', evidence: ['game/discovery-module-profile.ts', 'game/richese-no-field.ts', 'game/discoveries.ts', 'game/discovery-actions.ts', 'game/discovery-entry.ts', 'game/discovery-collection.ts', 'game/board-resolution-quote.ts', 'game/discovery-flight.ts', 'game/discovery-battle.ts', 'game/discovery-storm.ts', 'game/shrine.ts', 'game/great-maker.ts', 'game/board.ts', 'game/engine.ts'], }, { @@ -4251,12 +4251,12 @@ export const RULE_TOPICS: RuleTopic[] = [ }, { area: 'Documentation', status: 'Partial', - detail: 'Sourced identities and native/classic-skill boundaries are recorded. Advanced Orgiz follows territory Collection and actual ordinary receipts; Basic retains its provisional positive-deposit mapping. Recommended Discovery occupant/payer ordering, Jacurutu allocation and cumulative Testing Station choices are visible interpretations, not publisher rulings. Great Maker, entry/source-storm inferences and Basic shared Orgiz settlement remain open.', + detail: 'Sourced identities and native/classic-skill boundaries are recorded. Advanced Orgiz uses territory Collection; Basic retains its provisional deposit mapping, including actual source reductions after bilateral shared settlement. Recommended occupant/payer ordering, Jacurutu allocation and cumulative Testing Station choices are visible interpretations, not publisher rulings. Great Maker and entry/source-storm inferences remain explicit; full acceptance stays open.', evidence: ['docs/DISCOVERY_COMPONENTS.md', 'docs/DISCOVERY_PROTOTYPE.md'], }, { area: 'Verification', status: 'Partial', - detail: 'Current canonical Collection batch passes72/72 in6 related files, types/lint and build. Genuine Basic/Advanced co-present Sihaya Ridge collectors take2 each or exhaust the6-spice pile in actual storm order; zero actual collection cannot qualify through old wallets. Actual390px Finish shipment & movement produces phase7, Ecaz8/Guild14 from7/10 and one Orgiz transfer with Ready restored, no overflow or errors. Earlier shared-allocation proof passes27/27 and Jacurutu/Testing Station proof104/104, each source-bound. Basic shared settlement, combinations, assurance, strategy and deployed acceptance remain open.', + detail: 'Current Collection batch passes80/80 in6 related files, types/lint and build. Genuine Basic/Advanced cases cover shared equal/full/zero splits, acceptance timing, wrong actors and stale events; helper boundaries cover multiple deposits, exhausted recipients, self/zero-share exclusion and cumulative overflow. Actual390px Basic Red Chasm lot4 changes Ecaz7/Guild8 to8/11 with one transfer and Ready restored, no overflow or errors. Four legal policies complete that Basic allocation via actual actions. Earlier canonical-source and Jacurutu/Station evidence remains source-bound; combinations, assurance, strategy and deployed acceptance remain open.', evidence: ['tests/mixed-nexus-modules-runtime.test.ts', 'tests/paired-e3-nexus-modules-runtime.test.ts', 'tests/sample-custody.test.ts', 'tests/ambassador-terror-overlap-engine.test.ts', 'tests/e3-nexus-ecaz-runtime.test.ts', 'tests/e3-nexus-moritani-runtime.test.ts', 'tests/single-nexus-e1-runtime.test.ts', 'tests/single-nexus-e2-runtime.test.ts', 'tests/homeworld-paired-nexus-runtime.test.ts', 'tests/homeworld-standalone-nexus-runtime.test.ts', 'tests/fixture-jacurutu-mixed-dial.ts', 'tests/jacurutu-mixed-dial-runtime.test.ts', 'tests/fixture-testing-station-contest.ts', 'tests/testing-station-contest-runtime.test.ts', 'tests/fixture-discovery-occupied-collection.ts', 'tests/discovery-occupied-collection-runtime.test.ts', 'tests/fixture-discovery-shared-orgiz.ts', 'tests/discovery-shared-orgiz-runtime.test.ts', diff --git a/tests/discovery-collection.test.ts b/tests/discovery-collection.test.ts index 32b9b57b..e7701a1b 100644 --- a/tests/discovery-collection.test.ts +++ b/tests/discovery-collection.test.ts @@ -13,6 +13,7 @@ import { ORGIZ_PROCESSING_STATION, DiscoveryCollectionError, quoteDiscoveryCollection, + quoteSharedOrgiz, type DiscoveryCollectionContext, type DiscoveryCollectionQuote, } from '../game/discovery-collection'; @@ -676,3 +677,58 @@ void test('two Orgiz owners resolve available collector spice in original storm assert.equal(balance(discovery,'h'),0); } }); + +function sharedOrgizBoundary(advanced:boolean) { + const game=fixture(advanced,[['a','atreides'],['e','ecaz'],['g','guild']]); + reveal(game,ORGIZ_PROCESSING_STATION); + game.players[0].forces={[`${ORGIZ_PROCESSING_STATION}:0`]:1}; + game.players[1].forces={'hagga_basin:12':1}; + game.players[2].forces={'hagga_basin:13':1,'red_chasm:7':1}; + game.players[1].ally='g';game.players[2].ally='e'; + game.spice={'hagga_basin:12':2,'hagga_basin:13':2,'imperial_basin:9':0,'red_chasm:7':3}; + const ordinary=quoteSpiceCollection(game); + return {game,context:{...game,spice:ordinary.spice},originalSpice:game.spice, + allocation:{territory:'hagga_basin',ecaz:'e',ally:'g',ecazAmount:2,allyAmount:2,method:'equal' as const}}; +} + +void test('shared Orgiz counts actual positive source deposits in Basic, one territory in Advanced, excluding unrelated and empty keys',()=>{ + for(const advanced of [false,true]) { + const f=sharedOrgizBoundary(advanced); + const before=structuredClone(f); + const effects=quoteSharedOrgiz(f.context,f.allocation,{a:0,e:2,g:4},f.originalSpice); + assert.deepEqual(effects.map(effect=>[effect.player,effect.from,effect.location]), + advanced?[['a','e','hagga_basin']]: + [['a','e','hagga_basin:12'],['a','e','hagga_basin:13']]); + assert.deepEqual(f,before); + } +}); + +void test('Basic shared Orgiz exhausts the first recipient then considers the actual ally, never self',()=>{ + const f=sharedOrgizBoundary(false); + f.context.players[2].forces[`${ORGIZ_PROCESSING_STATION}:0`]=1; + const allocation={...f.allocation,ecazAmount:1,allyAmount:3,method:'agreement' as const}; + const effects=quoteSharedOrgiz(f.context,allocation,{a:0,e:1,g:5},f.originalSpice); + assert.deepEqual(effects.map(effect=>[effect.player,effect.from,effect.location]), + [['a','e','hagga_basin:12'],['a','g','hagga_basin:13']]); +}); + +void test('Basic shared Orgiz skips a zero-share Ecaz wallet and charges only the positive foreign ally',()=>{ + const f=sharedOrgizBoundary(false); + const allocation={...f.allocation,ecazAmount:0,allyAmount:4,method:'agreement' as const}; + const balances={a:0,e:20,g:6},before=structuredClone(balances); + const effects=quoteSharedOrgiz(f.context,allocation,balances,f.originalSpice); + assert.deepEqual(effects.map(effect=>effect.from),['g','g']); + assert.deepEqual(balances,before); +}); + +void test('shared Orgiz checks cumulative income overflow without mutating either purse or original source',()=>{ + const f=sharedOrgizBoundary(false); + const allocation={...f.allocation,ecazAmount:4,allyAmount:0,method:'agreement' as const}; + const balances={a:Number.MAX_SAFE_INTEGER-1,e:4,g:2}; + const before=structuredClone({f,balances}); + assert.throws(()=>quoteSharedOrgiz(f.context,allocation,balances,f.originalSpice),DiscoveryCollectionError); + assert.deepEqual({f,balances},before); + f.context.advanced=true; + assert.deepEqual(quoteSharedOrgiz(f.context,allocation,balances,f.originalSpice), + [{kind:'orgiz',player:'a',from:'e',location:'hagga_basin',amount:1,source:'player'}]); +}); diff --git a/tests/discovery-shared-orgiz-runtime.test.ts b/tests/discovery-shared-orgiz-runtime.test.ts index 2debda7b..5827d3a0 100644 --- a/tests/discovery-shared-orgiz-runtime.test.ts +++ b/tests/discovery-shared-orgiz-runtime.test.ts @@ -7,9 +7,10 @@ import {prepareSharedOrgizCollection,settleSharedOrgiz} from './fixture-discover const wallets=(game:Game)=>Object.fromEntries(game.players.map(p=>[p.id,p.spice])); const transfers=(game:Game)=>game.log.filter(entry=>entry.automatic?.name==='Orgiz Processing Station'); +for(const advanced of [false,true]) for(const split of ['equal','all-ecaz','all-guild'] as const) { - void test(`Advanced native shared Collection: ${split} respects the actual foreign recipient and sole Orgiz owner`,()=>{ - const position=prepareSharedOrgizCollection(); + void test(`${advanced?'Advanced':'Basic'} native shared Collection: ${split} respects the actual foreign recipient and sole Orgiz owner`,()=>{ + const position=prepareSharedOrgizCollection(advanced); const original=structuredClone(position.game); const before=wallets(original); const choice=split==='equal'?{kind:'equal' as const}: @@ -35,8 +36,9 @@ for(const split of ['equal','all-ecaz','all-guild'] as const) { }); } -void test('Native shared Orgiz waits for ally acceptance and rejects the wrong actor without changing wallets',()=>{ - const position=prepareSharedOrgizCollection(); +for(const advanced of [false,true]) +void test(`${advanced?'Advanced':'Basic'} shared Orgiz waits for ally acceptance and rejects the wrong actor without changing wallets`,()=>{ + const position=prepareSharedOrgizCollection(advanced); const before=structuredClone(position.game); const event=before.ecazCollection!.event; const action={type:'decision' as const,event,allocation:{kind:'propose' as const,ecazShare:position.lot.amount}}; diff --git a/tests/fixture-discovery-shared-orgiz.ts b/tests/fixture-discovery-shared-orgiz.ts index 2c6f0f41..8af4086b 100644 --- a/tests/fixture-discovery-shared-orgiz.ts +++ b/tests/fixture-discovery-shared-orgiz.ts @@ -69,8 +69,8 @@ function prepareOrgizCollectors(advanced:boolean,terrainType:'sand'|'rock',exhau } /** Desert collectors enter the original shared allocation before any credit. */ -export function prepareSharedOrgizCollection() { - const position=prepareOrgizCollectors(true,'sand'); +export function prepareSharedOrgizCollection(advanced=true) { + const position=prepareOrgizCollectors(advanced,'sand'); const game=applyAction(position.game,position.finalAction.actor,position.finalAction.action); assert.equal(game.decision?.kind,'ecazSpice'); const pending=game.ecazCollection!.allocation!;