Prove Advanced occupied Bureaucrat receipts with native timing
This commit is contained in:
1 parent
304849f8d9
commit
9fb680da09
7 files changed
+102
-16
No files matched your search
@@ -133,8 +133,18 @@ same family names. The native Guild path uses paid Emperor shipping to fund
|
||||
the Guild emptying Junction, then a real Harkonnen invasion. The Emperor path
|
||||
uses an actual paid auction to fund the original twenty-counter departure,
|
||||
then a real Guild invasion of empty Kaitain. No force, cash or occupation
|
||||
history is assigned. Basic current-sole receipts are exercised; Advanced fresh
|
||||
entry is exercised, not an Advanced retention/full-game certification.
|
||||
history is assigned. Basic current-sole and Advanced retained-qualification
|
||||
receipts are exercised, not full-game or complete-module certification.
|
||||
|
||||
Advanced Emperor departure explicitly selects all native Homeworld groups:
|
||||
fifteen normal Kaitain counters and five Salusa elites. The original twenty-
|
||||
counter payment and typed delivery remain. Advanced Guild uses native
|
||||
Harkonnen preparation and a fifth BG occupier. Flexible Guild timing postpones
|
||||
the invasion to turn2; the funded receipt waits for the actual next worm and
|
||||
alliance on turn3 rather than chasing an already-past turn2 Spice phase.
|
||||
Both actual Advanced gross8 programs settle native3/occupied3/Bank2 with original
|
||||
paid8/shares intact; four meaningful allowance/diversion cases and the previous
|
||||
Basic consumers pass21/21 across both files, with types/lint.
|
||||
|
||||
Actual390px controls redirect the Emperor invoice, then the Guild occupier
|
||||
keeps3 through the original allocation control, then the original Harkonnen
|
||||
|
||||
@@ -29,6 +29,19 @@ optional skill bands, combinations and public expansion starts remain open.
|
||||
Rules implementation continues; comprehensive assurance and administration
|
||||
remain deferred until all rules are implemented.
|
||||
|
||||
**Advanced occupied Bureaucrat receipts — 13 October.**
|
||||
Actual Advanced Emperor and Guild gross8 receipts now prove native3/occupied3/
|
||||
Bank2 through retained original qualifications, with original full payer shares.
|
||||
Emperor's real twenty-counter departure uses15 normal Kaitain plus5 Salusa
|
||||
elites. Guild's native flexible timing delays foreign invasion until turn2;
|
||||
the later receipt uses a real turn3 worm/alliance, not a past-phase predicate.
|
||||
Native Harkonnen preparation and a fifth BG occupier preserve original rules.
|
||||
Initial fixture traversals chased an already-past Spice phase to the native
|
||||
turn10 Guild win; turn-relative traversal fixes the fixture without changing
|
||||
game rules. The old Basic paths remain. Four new outcome/boundary cases bring
|
||||
the two-file union to21/21; types/lint pass. No new production code, strategy,
|
||||
save/deployed, full-game or completed-mode claim follows.
|
||||
|
||||
**Actual occupied Homeworld / Skills composition — 13 October.**
|
||||
The earlier marked-done split-recipient task had only proved plain Homeworld
|
||||
funding; it did not finish the ruled occupied allocation. That missing composition
|
||||
|
||||
@@ -1,5 +1,27 @@
|
||||
# Dune implementation status
|
||||
|
||||
## 13 October 2026 — actual Advanced occupied receipt coverage
|
||||
|
||||
Native Advanced Emperor and Guild programs settle actual gross8 into native3,
|
||||
occupied3 and Bureaucrat Bank2 with original paid8/shares intact. Both preserve
|
||||
the original retained qualification from the real prior invasion.
|
||||
|
||||
Advanced Emperor departure explicitly selects every native pool: Kaitain15
|
||||
normal and Salusa5 elite, paying20 and committing20 with5 elites. Existing Basic
|
||||
twenty-normal departure remains. Advanced Guild uses native Harkonnen prep and
|
||||
a fifth BG occupier; original Guild timing remains flexible. The foreign
|
||||
invasion occurs turn2, so a literal turn2 Spice predicate was already past and
|
||||
traversed until the native turn10 Guild win. A relative next-turn boundary now
|
||||
waits for actual turn3 worm/alliance and payment; no phase, victory or wallet
|
||||
is changed. Temporary diagnostics are removed.
|
||||
|
||||
Four meaningful Advanced allowance/diversion cases assert original typed
|
||||
withdrawal, full payer debits, occupied allocation, retained source, actual next
|
||||
lot or committed shipment. Frozen two-file21/21 and types/lint pass. Actual
|
||||
native programs cover both families; the earlier390px Basic surface proves
|
||||
the unchanged controls. No production change, new strategy, saved-game audit,
|
||||
deployment, complete-game or complete-mode certification is claimed.
|
||||
|
||||
## 13 October 2026 — actual occupied income and Leader Skills
|
||||
|
||||
Corrected the previous task completion claim:9b83b61 proved plain Homeworld
|
||||
|
||||
@@ -187,3 +187,17 @@ void test('redirect spends only one phase use; decline leaves it for the next ac
|
||||
assertClassicHomeworldSkillsCustody(done);
|
||||
}
|
||||
});
|
||||
|
||||
for (const redirect of [false, true]) void test(`Advanced occupied Emperor ${redirect ? 'diversion' : 'allowance'} preserves the original two-world departure and next paid lot`, () => {
|
||||
const f = fixture({ advanced: true });
|
||||
assert.equal(player(f.setup, f.payee).elites!.reserves, 5);
|
||||
assert.equal(player(f.emptyKaitain, f.payee).reserves, 0);
|
||||
assert.equal(player(f.emptyKaitain, f.payee).elites!.reserves, 0);
|
||||
assert.equal(Object.values(player(f.emptyKaitain, f.payee).forces).reduce((a,b) => a+b, 0), 20);
|
||||
assert.equal(Object.values(player(f.emptyKaitain, f.payee).elites!.forces).reduce((a,b) => a+b, 0), 5);
|
||||
const paid = allow(pay(f)); assertPaid(f, paid);
|
||||
const split = choose(paid, redirect), done = allocate(split);
|
||||
assertOutcome(f, split, done, redirect ? 2 : 0);
|
||||
assert.equal(quoteStableHomeworldOccupation(done, 'kaitain').entitlement!.qualification,
|
||||
quoteStableHomeworldOccupation(f.occupied, 'kaitain').entitlement!.qualification);
|
||||
});
|
||||
@@ -269,3 +269,21 @@ void test('every owner bot resolves the occupied Junction offer and the native p
|
||||
outcome(f, done, 2);
|
||||
}
|
||||
});
|
||||
|
||||
for (const redirect of [false, true]) void test(`Advanced occupied Guild ${redirect ? 'diversion' : 'allowance'} follows actual flexible timing without replaying a past phase`, () => {
|
||||
const f = createOccupiedGuildFixture({ advanced: true });
|
||||
assert.ok(f.beforePledge.turn > f.occupied.turn);
|
||||
const paid = declareOccupiedGuild(f);
|
||||
const pending = allowOccupiedGuildIncome(paid);
|
||||
const done = allocateOccupiedGuild(chooseOccupiedGuild(pending, redirect));
|
||||
outcome(f, done, redirect ? 2 : 0);
|
||||
assert.deepEqual(armies(done), armies(paid));
|
||||
const receipt = done.homeworldOccupiedPercentageLedger!.receipts.at(-1)!;
|
||||
assert.equal(receipt.nativeRetained, redirect ? 3 : 4);
|
||||
assert.equal(receipt.occupiedAmount, redirect ? 3 : 4);
|
||||
assert.equal(guildShippingSource(receipt).payment.paid, 8);
|
||||
assert.deepEqual(guildShippingSource(receipt).payment.contributions,
|
||||
[{payer:f.payer,amount:6},{payer:f.donor,amount:2}]);
|
||||
assert.equal(quoteStableHomeworldOccupation(done, 'junction').entitlement!.qualification,
|
||||
quoteStableHomeworldOccupation(f.occupied, 'junction').entitlement!.qualification);
|
||||
});
|
||||
@@ -3,6 +3,8 @@ import { applyAction, viewGame, type Action, type Game } from '../game/engine';
|
||||
import { territory } from '../game/board';
|
||||
import { homeworldShipmentChoice } from '../game/homeworld-shipment-options';
|
||||
import { quoteStableHomeworldOccupation } from '../game/homeworld-stable-occupation';
|
||||
import { homeworldContext } from '../game/homeworld-game';
|
||||
import { homeworldForceGroups } from '../game/homeworld-custody';
|
||||
import {
|
||||
assertClassicHomeworldSkillsCustody, classicHomeworldSkillsPlayer,
|
||||
createClassicHomeworldSkillsSetup, nextClassicHomeworldSkillsStep,
|
||||
@@ -88,22 +90,23 @@ export type OccupiedEmperorFixture = {
|
||||
lot: string; bonus: string; karama: string; physical: string[]; actions: ClassicHomeworldSkillsStep[];
|
||||
};
|
||||
|
||||
/** Actual Basic setup -> paid ten-spice lot -> twenty-counter paid shipment ->
|
||||
/** Original native setup -> paid ten-spice lot -> twenty-counter paid shipment ->
|
||||
* Guild's real bribes through Mentat -> worm/alliance -> empty Kaitain invasion
|
||||
* -> later funded auction. No wallet, force, phase, alliance or authority edits. */
|
||||
export function createOccupiedEmperorFixture(options: {
|
||||
payerShare?: number; donorShare?: number; donor?: 'guild' | 'atreides' | 'emperor';
|
||||
advanced?: boolean;
|
||||
} = {}): OccupiedEmperorFixture {
|
||||
const { payerShare = 5, donorShare = 3, donor = 'guild' } = options;
|
||||
const { payerShare = 5, donorShare = 3, donor = 'guild', advanced = false } = options;
|
||||
const payer = 'harkonnen', payee = 'emperor', trainer = 'atreides', occupier = 'guild';
|
||||
const native = createClassicHomeworldSkillsSetup({ occupation: true });
|
||||
const native = createClassicHomeworldSkillsSetup({ occupation: true, advanced });
|
||||
const setup = structuredClone(native.afterSetup), actions = [...native.actions];
|
||||
let game = structuredClone(setup);
|
||||
const act = (actor: string, action: Action) => {
|
||||
actions.push({ actor, action: structuredClone(action) }); game = applyAction(game, actor, action);
|
||||
};
|
||||
const reach = (until: (state: Game) => boolean) => { game = advanceOccupiedEmperor(game, until, actions); };
|
||||
orderSpice(game, ['land']);
|
||||
orderSpice(game, advanced ? ['land', 'land'] : ['land']);
|
||||
reach(g => g.phase === 3 && !!g.auction && occupiedEmperorClean(g));
|
||||
holdKarama(game, payer);
|
||||
reach(g => g.auction!.active === payer && occupiedEmperorClean(g));
|
||||
@@ -121,7 +124,8 @@ export function createOccupiedEmperorFixture(options: {
|
||||
const destination = 'habbanya_ridge_sietch';
|
||||
assert.ok(game.players.every(p => !Object.keys(p.forces).some(key => key.startsWith(`${destination}:`))));
|
||||
act(payee, { type: 'ship', territory: destination, sector: territory(destination).sectors[0], amount: 20, elite,
|
||||
homeworldSources: { 'homeworld:emperor': { normal: 20 - elite, elite } } });
|
||||
homeworldSources: Object.fromEntries(homeworldForceGroups(homeworldContext(game), game.homeworlds!.custody!)
|
||||
.filter(group => group.native === payee).map(group => [group.id, group.forces[payee]])) });
|
||||
reach(g => g.phase === 5 && occupiedEmperorClean(g));
|
||||
assert.equal(occupiedEmperorPlayer(game, payee).reserves, 0);
|
||||
assert.equal(occupiedEmperorPlayer(game, payee).forces[`${destination}:${territory(destination).sectors[0]}`], 20);
|
||||
@@ -132,7 +136,7 @@ export function createOccupiedEmperorFixture(options: {
|
||||
act(occupier, { type: 'bribe', target: payee, amount: 5 });
|
||||
reach(occupiedEmperorClean);
|
||||
reach(g => g.turn === 2 && g.phase === 1 && occupiedEmperorClean(g));
|
||||
orderSpice(game, ['worm', 'land']);
|
||||
orderSpice(game, advanced ? ['worm', 'land', 'land'] : ['worm', 'land']);
|
||||
reach(g => !!g.nexus && !g.spiceWindow && !g.spiceResolution && occupiedEmperorClean(g));
|
||||
act(payer, { type: 'alliance', target: donor });
|
||||
act(donor, { type: 'alliance', target: payer });
|
||||
|
||||
@@ -105,14 +105,18 @@ export type OccupiedGuildFixture = {
|
||||
export function createOccupiedGuildFixture(options: {
|
||||
payer?: string; donor?: string; occupier?: string; prep?: string;
|
||||
payerShare?: number; donorShare?: number;
|
||||
advanced?: boolean;
|
||||
} = {}): OccupiedGuildFixture {
|
||||
const payer = options.payer ?? 'harkonnen';
|
||||
const donor = options.donor ?? 'emperor';
|
||||
const occupier = options.occupier ?? 'harkonnen';
|
||||
const prep = options.prep ?? 'emperor';
|
||||
const { payerShare = 6, donorShare = 2 } = options;
|
||||
const occupier = options.occupier ?? (options.advanced ? 'beneGesserit' : 'harkonnen');
|
||||
const prep = options.prep ?? (options.advanced ? 'harkonnen' : 'emperor');
|
||||
const { payerShare = 6, donorShare = 2, advanced = false } = options;
|
||||
const payee = 'guild', trainer = 'atreides';
|
||||
const native = createClassicHomeworldSkillsSetup({ occupation: true });
|
||||
// Use native Harkonnen preparation and a distinct BG occupier in Advanced;
|
||||
// native flexible Guild timing and every original victory rule remain intact.
|
||||
const native = createClassicHomeworldSkillsSetup({ occupation: true, advanced,
|
||||
...(advanced ? { roster: ['emperor', 'guild', 'atreides', 'harkonnen', 'beneGesserit'] as const } : {}) });
|
||||
const setup = structuredClone(native.afterSetup), actions = [...native.actions];
|
||||
const staging = [...native.staging];
|
||||
let game = structuredClone(setup);
|
||||
@@ -123,7 +127,7 @@ export function createOccupiedGuildFixture(options: {
|
||||
const reach = (until: (state: Game) => boolean) => {
|
||||
game = advanceOccupiedGuild(game, until, actions);
|
||||
};
|
||||
orderSpice(game, ['land']);
|
||||
orderSpice(game, advanced ? ['land', 'land'] : ['land']);
|
||||
reach(g => g.turn === 1 && g.phase === 5 && g.active === prep && occupiedGuildClean(g));
|
||||
const opening = occupiedGuildPlayer(game, payee).spice;
|
||||
// One real four-spice shipment income funds the native Guild's later tariff.
|
||||
@@ -155,8 +159,9 @@ export function createOccupiedGuildFixture(options: {
|
||||
assert.ok(game.homeworldOccupationHistory!.sources.some(source =>
|
||||
source.event === authority.entitlement!.qualification && source.cause !== 'setup'));
|
||||
const occupied = structuredClone(game);
|
||||
reach(g => g.turn === 2 && g.phase === 1 && occupiedGuildClean(g));
|
||||
orderSpice(game, ['worm', 'land']);
|
||||
const paymentTurn = game.turn + 1;
|
||||
reach(g => g.turn === paymentTurn && g.phase === 1 && occupiedGuildClean(g));
|
||||
orderSpice(game, advanced ? ['worm', 'land', 'land'] : ['worm', 'land']);
|
||||
reach(g => !!g.nexus && !g.spiceWindow && !g.spiceResolution && occupiedGuildClean(g));
|
||||
act(payer, { type: 'alliance', target: donor });
|
||||
act(donor, { type: 'alliance', target: payer });
|
||||
@@ -164,7 +169,7 @@ export function createOccupiedGuildFixture(options: {
|
||||
assert.equal(occupiedGuildPlayer(game, donor).ally, payer);
|
||||
const alliance = structuredClone(game);
|
||||
// The later real paid shipment to an empty Arrakis territory.
|
||||
reach(g => g.turn === 2 && g.phase === 5 && g.active === payer && occupiedGuildClean(g));
|
||||
reach(g => g.turn === paymentTurn && g.phase === 5 && g.active === payer && occupiedGuildClean(g));
|
||||
const karama = holdKarama(game, trainer);
|
||||
const beforePledge = structuredClone(game);
|
||||
if (donorShare) act(donor, { type: 'pledgeAid', amount: donorShare });
|
||||
|
||||
Reference in new issue
Block a user