diff --git a/docs/CHOAM_SUPPORT_INCOME.md b/docs/CHOAM_SUPPORT_INCOME.md new file mode 100644 index 00000000..3af1a82d --- /dev/null +++ b/docs/CHOAM_SUPPORT_INCOME.md @@ -0,0 +1,50 @@ +# CHOAM total support income + +27 October 2026. **Prototyped / Partial.** The adopted Advanced source, +supplied physical29/printed27 F, gives CHOAM half the total eligible support +spice with one final rounding down. Two other combatants supporting with3 and5 +therefore yield4, and3 plus3 yield3. Earlier per-combatant flooring underpaid +those battles. See the [source authority](RULE_DECISIONS.md#authorized-source-amendment--1-october-2026). + +This correction changes aggregation only. Each non-CHOAM side still contributes +`plan.support - plan.allyPayment`. That includes its own debit and the Arrakeen +Bank contribution. Publisher E2 physical/printed10's Arrakeen Q&A explicitly +includes the Bank's two support spice; replacing eligibility with wallet debits +would incorrectly remove that benefit. The +[publisher E2 book](https://www.gf9games.com/dune/wp-content/uploads/2021/11/CHOAM-Rulebook-low-res.pdf#page=10) +supplies that omitted detail. No combined publisher worked example for every +subsidy/funding mixture is claimed. + +CHOAM's own side remains excluded. CHOAM-funded allied support goes entirely to +the Bank under supplied G. Salusa free Sardaukar, Fremen free support, fixed +Homeworld strength and separate Banker strength spending add no paid support. +The current native pledge admits only a mutual CHOAM donor; future third-party +funding or generic discounts are not certified by this correction. Existing +Banker/Bureaucrat per-payer/per-invoice rulings remain separate. + +The shared `game/battle-resolution-quote.ts` calculation supplies the original +engine settlement. Native wallet debits, escrow, casualty/card cleanup, any +revealed-Traitor suppression, explosion income, Karama redirection and once-only +collection keep their paths. Basic has no Advanced force-payment income. +Already queued income retains its recorded receipt; no saved field, migration, +recalculation of settled payments or collection confirmation is added. + +Focused quote and actual engine payment regressions cover odd totals. Runtime +verification uses a clearly labelled controlled battle source with real native +plan, Traitor, response and market actions; it is not a fresh setup/full-game +claim. Full modes, wider compositions, comprehensive assurance and deployed +acceptance remain open. + +Frozen **4 affected files, 71/71**, types/lint/build and two controlled battle +programmes pass. 13 actual native packets and 6 GameTable SSR renders verify +one automatic4 receipt and one pending/canceled4 receipt, unchanged3/5 debits, +original pending response text and once-only closing behavior. The explicit +controlled fixture assigns phase, purses, armies and leader strength; it is not +a genuine starting-game journey. No control callbacks are operated. Quote and +engine regressions cover3+5/3+3; the existing subsidy, aid, own support, free +support, Traitor, explosion and cancellation cases remain in the related union. + +The existing server returned HTTP200 without restart. No saved-game/database operation. +Proof: `/home/quintine/.local/share/dune/checkpoints/20261027-choam-support-income-proof-v1/`; +HTML/journeys: adjacent `20261027-choam-support-income-runtime/run/`. +Full modes, broader combinations and final assurance remain open. diff --git a/docs/CURRENT_STATUS.md b/docs/CURRENT_STATUS.md index 81311a49..d09d7888 100644 --- a/docs/CURRENT_STATUS.md +++ b/docs/CURRENT_STATUS.md @@ -20,6 +20,29 @@ open mode gates. Keep identified Basic-function gaps ahead of Advanced and wider interactions. Prototype labels and final acceptance gates remain. +**CHOAM total support income — 27 October. Prototyped / Partial.** +Advanced income now adds eligible support before rounding down once:3+5 yields4 +and3+3 yields3. Original `support - allyPayment` eligibility retains Arrakeen's +Bank contribution and CHOAM-own/ally exclusions. Payments, free support, Traitor +suppression, native aftermath and Karama paths are unchanged; this is separate +from Banker/Bureaucrat per-invoice rules. Already queued receipts retain their +recorded amounts. [Source and scope](CHOAM_SUPPORT_INCOME.md). + +Frozen **4 affected files, 71/71**, types/lint/build and two controlled battle +programmes pass. 13 actual choose/plan/Traitor/response/closing packets and +6 GameTable SSR renders verify automatic4 once and cancelable4 with unchanged +combatant debits3/5. The existing unit source assigns phase/wallets/forces and +zero-strength leaders, with a conserved original Karama in one case; this is +explicitly controlled evidence, not native original setup/full-game proof. +No control callbacks are claimed. New quote/engine odd-total regressions retain +the original subsidy, aid, Traitor, explosion and free-support scenarios. + +The existing server returned HTTP200 without restart. No database or saved-game +operation. Full modes, wider compositions and final rules/recovery/privacy/ +browser/deployed acceptance remain. Proof: +`/home/quintine/.local/share/dune/checkpoints/20261027-choam-support-income-proof-v1/`; +journeys/HTML: adjacent `20261027-choam-support-income-runtime/run/`. + **Advanced Charity player order — 27 October. Prototyped / Partial.** New Advanced phases proceed from the First Player counterclockwise. Only the current seat may claim or Finish; CHOAM income and wealthy BG responses retain @@ -72,18 +95,12 @@ Proof: `/home/quintine/.local/share/dune/checkpoints/20261027-ix-starting-pool-p journeys/HTML: adjacent `20261027-ix-starting-pool-runtime/run/`. [Source contract](IX_PROTOTYPE.md#advanced-starting-draft-source-cutover--27-october-2026). -**Next source-identified functions:** CHOAM's total-support rounding -(supplied physical29 F) and Family Atomics' full timing/sector/removal contract -(physical39; the revision's stricter requirements remain Advanced-only after -bounded Basic comparison). The bounded CHOAM source comparison supports a -rounding-only correction: retain eligible `support - allyPayment`, including -Arrakeen's expressly sourced Bank contribution; preserve CHOAM-own/CHOAM-aid -exclusions and free-force support. Aggregate before flooring; unrelated -Banker/Bureaucrat per-invoice rulings stay distinct. This is next implementation, -not a passed new effect. Charity Sapho composition waits for the interaction pass. -These findings are concrete divergences, not a complete rules audit. The bounded -six-classic-faction pass found connected independent functions; optional modules, -interactions and final acceptance remain open. +**Next source-identified function:** Family Atomics' full timing/sector/removal +contract (supplied physical39). The revision's stricter requirements remain +Advanced-only after bounded Basic comparison. Charity Sapho composition remains +for the interaction pass. These findings are concrete divergences, not a complete +rules audit. The bounded six-classic-faction pass found connected independent +functions; optional modules, interactions and final acceptance remain open. **Native E3 trained capture — 27 October. Prototyped / Partial.** Fresh bare Advanced E3/all14 setups now connect Harkonnen with actual Ecaz diff --git a/docs/IMPLEMENTATION_STATUS.md b/docs/IMPLEMENTATION_STATUS.md index dd52fe04..06b718a2 100644 --- a/docs/IMPLEMENTATION_STATUS.md +++ b/docs/IMPLEMENTATION_STATUS.md @@ -1,5 +1,29 @@ # Dune implementation status +## 27 October 2026 — CHOAM aggregate support rounding + +Prototyped / Partial. Advanced income now adds eligible support before rounding down once:3+5 yields4 +and3+3 yields3. Original `support - allyPayment` eligibility retains Arrakeen's +Bank contribution and CHOAM-own/ally exclusions. Payments, free support, Traitor +suppression, native aftermath and Karama paths are unchanged; this is separate +from Banker/Bureaucrat per-invoice rules. Already queued receipts retain their +recorded amounts. [Source and scope](CHOAM_SUPPORT_INCOME.md). + +Frozen **4 affected files, 71/71**, types/lint/build and two controlled battle +programmes pass. 13 actual choose/plan/Traitor/response/closing packets and +6 GameTable SSR renders verify automatic4 once and cancelable4 with unchanged +combatant debits3/5. The existing unit source assigns phase/wallets/forces and +zero-strength leaders, with a conserved original Karama in one case; this is +explicitly controlled evidence, not native original setup/full-game proof. +No control callbacks are claimed. New quote/engine odd-total regressions retain +the original subsidy, aid, Traitor, explosion and free-support scenarios. + +The existing server returned HTTP200 without restart. No database or saved-game +operation. Full modes, wider compositions and final rules/recovery/privacy/ +browser/deployed acceptance remain. Proof: +`/home/quintine/.local/share/dune/checkpoints/20261027-choam-support-income-proof-v1/`; +journeys/HTML: adjacent `20261027-choam-support-income-runtime/run/`. + ## 27 October 2026 — Ordered Advanced Charity Prototyped / Partial. New Advanced phases proceed from the First Player counterclockwise. Only the diff --git a/docs/RULE_DECISIONS.md b/docs/RULE_DECISIONS.md index 6e4b173c..80ebe3b6 100644 --- a/docs/RULE_DECISIONS.md +++ b/docs/RULE_DECISIONS.md @@ -1,5 +1,15 @@ # Rule decision index +## CHOAM total support rounding — 27 October 2026 + +Supplied Advanced physical29/printed27 F calls for half the total eligible +force-support spice, rounded down once. Move the floor outside the existing +non-CHOAM `support - allyPayment` sum. Retain Arrakeen's publisher-confirmed Bank +contribution, CHOAM's own/ally exclusions, free support and original payment/ +Traitor/cancellation paths. This is the adopted source correction, distinct +from existing Banker/Bureaucrat per-invoice rulings. Wider hypothetical funding +and discount combinations remain unverified. [Source and scope](CHOAM_SUPPORT_INCOME.md). + ## Advanced Charity player order — 27 October 2026 Supplied physical8/printed6 expressly begins Charity with the First Player and diff --git a/game/battle-resolution-quote.ts b/game/battle-resolution-quote.ts index 8b6b7c1a..de12819f 100644 --- a/game/battle-resolution-quote.ts +++ b/game/battle-resolution-quote.ts @@ -670,16 +670,19 @@ function calculate(input: BattleResolutionInput): BattleResolutionQuote { } const choam = input.participants.find((p) => p.faction === 'choam'); // Preserve existing source-backed income, including Stronghold bank support; - // CHOAM's prepaid ally contribution is excluded from this receipt. + // CHOAM's prepaid ally contribution is excluded from this receipt. The + // adopted Advanced source rounds once after totaling the eligible support. const choamAmount = input.advanced && choam && !ac && !dc - ? [a, d].reduce( - (sum, s) => - sum + - (s.id === choam.id - ? 0 - : Math.floor((s.plan.support - (s.plan.allyPayment ?? 0)) / 2)), - 0, + ? Math.floor( + [a, d].reduce( + (sum, s) => + sum + + (s.id === choam.id + ? 0 + : s.plan.support - (s.plan.allyPayment ?? 0)), + 0, + ) / 2, ) : 0; const result = diff --git a/game/reference.ts b/game/reference.ts index bd3b0c37..a69f00bb 100644 --- a/game/reference.ts +++ b/game/reference.ts @@ -4747,16 +4747,17 @@ export const RULE_TOPICS: RuleTopic[] = [ title: 'CHOAM combat funding and force income', category: 'Advanced & expansions', coverage: 'Partial', + developmentStage: 'Prototyped', summary: 'Finance an ally’s supported forces and receive a share of other players’ combat payments.', steps: [ 'In advanced play, CHOAM may pay some or all of its ally’s force support. At the start of an allied battle, choose the amount of unspent spice to reserve for the phase. CHOAM can update this pledge during ordinary Battle controls. Only a current mutual ally may use it.', 'A Karama response can prevent allied funding for the current battle. Reserved spice stays with the pledge, but the ally must plan with its own funds. Later battles get a fresh response and may use the remaining pledge.', 'When sealing a plan, choose total spice support and optionally CHOAM’s share. With no explicit share, your own spice is used first and the pledge covers the shortfall. The chosen amounts remain reserved until resolution; CHOAM cannot withdraw a committed share. Unspent pledged spice returns at the end of Battle, after the closing market.', - 'When other players pay for battle forces, CHOAM receives half of each player’s actual payment, rounded down. Its own force payments and any spice it supplies for its ally go to the bank without generating a rebate. For example, payments of three and five from two other players produce one plus two spice for CHOAM.', + 'When other factions support battle forces, add the eligible support from both sides, then halve the total and round down once. Payments of three and five produce four spice for CHOAM. Retain the Arrakeen Bank contribution, while excluding CHOAM’s own support and the spice it supplies for its ally. Free-supported forces and separate battle-strength spending add no support spice.', 'Any revealed traitor prevents all CHOAM force-payment income from that battle. A sole traitor winner pays no support, including its pledged allied share. If both sides reveal traitors, both pay their support but CHOAM still gets no income. A lasgun–shield explosion without a traitor still pays support and generates income.', 'After casualty and winner-card choices, a separate Karama response precedes CHOAM’s income. Cancellation sends the amount to the bank and does not refund the combatants. Complete the response before post-battle technology, capture and Face Dancer rewards continue.', - 'Pledges and unrevealed payment splits stay private to their authorized viewers. Battle guides and all four AI levels account for reserved allied funds when finding legal plans. The precise ordering with later expansion powers, rounding across simultaneous payers, allied-payment interpretation and the alliance cancellation window remain under final audit. CHOAM starts stay disabled pending its remaining powers.', + 'Pledges and unrevealed payment splits stay private to their authorized viewers. Battle guides and all four AI levels account for reserved allied funds when finding legal plans. The precise ordering with later expansion powers, wider allied-payment compositions and the alliance cancellation window remain under final audit. CHOAM starts remain previews; complete faction and combination acceptance is unfinished.', ], related: [ 'faction-choam', @@ -4766,6 +4767,13 @@ export const RULE_TOPICS: RuleTopic[] = [ 'card-karama', 'card-truthtrance', ], + checklist: [ + { area: 'Implementation', status: 'Partial', detail: 'Support income floors the eligible battle total once; original support, Arrakeen contribution, allied exclusions, Traitor and aftermath rules remain.', evidence: ['game/battle-resolution-quote.ts', 'game/engine.ts'] }, + { area: 'Player controls', status: 'Partial', detail: 'Existing funding, plan and cancellation controls retain automatic uncontested income; no extra collection confirmation.', evidence: ['components/choam-battle-funding.tsx', 'components/game-table.tsx'] }, + { area: 'AI', status: 'Partial', detail: 'Existing minimal policies use shared resolution and payment rules; strategy/calibration remain deferred.', evidence: ['game/bots.ts', 'tests/choam-combat.test.ts'] }, + { area: 'Documentation', status: 'Partial', detail: 'Adopted total-rounding source and unchanged native eligible support are explicit.', evidence: ['docs/CHOAM_SUPPORT_INCOME.md'] }, + { area: 'Verification', status: 'Partial', detail: 'Focused quote/payment regressions cover odd support sums and existing exclusions; full modes, wider compositions and deployed acceptance remain.', evidence: ['tests/battle-resolution-quote.test.ts', 'tests/choam-combat.test.ts'] }, + ], }, { id: 'choam-karama', diff --git a/tests/battle-resolution-quote.test.ts b/tests/battle-resolution-quote.test.ts index 7db3c2e0..cb1fae54 100644 --- a/tests/battle-resolution-quote.test.ts +++ b/tests/battle-resolution-quote.test.ts @@ -297,6 +297,17 @@ void test('Habbanya selects ordinary and Stone ties; canceled free-support and e { normal: 2, elite: 1, paidNormal: 0, paidElite: 0 }, ]); }); +void test('CHOAM quotes half of the total other-faction support with one final floor', () => { + const g = input(); + g.participants = [...g.participants, { id: 'c', faction: 'choam' }]; + g.attacker.plan.dial = g.attacker.plan.support = 3; + g.defender.plan.dial = g.defender.plan.support = 5; + const before = structuredClone(g), q = quoteBattleResolution(g); + assert.deepEqual(q.choamIncome, { owner: 'c', amount: 4 }); + assert.deepEqual(q.payments.map(payment => payment.ownPayment), [3, 5]); + assert.deepEqual(q.payments.map(payment => payment.bankSupport), [0, 0]); + assert.deepEqual(g, before); +}); void test('funding consumes explicit current escrow and includes Stronghold bank support in CHOAM income without debiting the bank or donor twice', () => { const g = input(); g.participants = [ diff --git a/tests/choam-combat.test.ts b/tests/choam-combat.test.ts index 2e99ca25..b46a59e2 100644 --- a/tests/choam-combat.test.ts +++ b/tests/choam-combat.test.ts @@ -196,16 +196,28 @@ void test('the ally can choose CHOAM to pay despite having its own spice; forged assert.equal(result.response, null); assert.equal(player(result, 'c').spice, 7); // Six after funding, plus one from opponent; no self rebate. }); -void test('income rounds down each other player payment and is collected only once after combat choices', () => { +void test('income rounds down the combined support and is collected only once after combat choices', () => { let g = settle(plan(plan(start(fixture(false)), 'g', 3), 'e', 3)); assert.equal(g.response, null); - assert.equal(player(g, 'c').spice, 12); // No cancellation choice: income settles immediately. + assert.equal(player(g, 'c').spice, 13); // Half of 3 + 3; uncontested income settles immediately. + assert.equal(player(g, 'g').spice, 7); + assert.equal(player(g, 'e').spice, 7); g = allow(g); - assert.equal(player(g, 'c').spice, 12); + assert.equal(player(g, 'c').spice, 13); assert.equal(g.pendingChoamBattleIncome, null); assert.equal(g.decision?.kind, 'choamMarket'); g = send(g, 'c', { type: 'decision', done: true }); - assert.equal(player(g, 'c').spice, 12); + assert.equal(player(g, 'c').spice, 13); +}); +void test('two other combatants paying three and five yield four CHOAM income without changing either debit', () => { + let g = settle(plan(plan(start(fixture(false)), 'g', 3), 'e', 5)); + assert.equal(player(g, 'c').spice, 14); + assert.equal(player(g, 'g').spice, 7); + assert.equal(player(g, 'e').spice, 5); + assert.equal(g.pendingChoamBattleIncome, null); + assert.equal(g.decision?.kind, 'choamMarket'); + g = send(g, 'c', { type: 'decision', done: true }); + assert.equal(player(g, 'c').spice, 14); }); void test('Karama redirects just this battle income to the bank without refunding force payments', () => { const initial = fixture(false);