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
|
the Guild emptying Junction, then a real Harkonnen invasion. The Emperor path
|
||||||
uses an actual paid auction to fund the original twenty-counter departure,
|
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
|
then a real Guild invasion of empty Kaitain. No force, cash or occupation
|
||||||
history is assigned. Basic current-sole receipts are exercised; Advanced fresh
|
history is assigned. Basic current-sole and Advanced retained-qualification
|
||||||
entry is exercised, not an Advanced retention/full-game certification.
|
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
|
Actual390px controls redirect the Emperor invoice, then the Guild occupier
|
||||||
keeps3 through the original allocation control, then the original Harkonnen
|
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
|
Rules implementation continues; comprehensive assurance and administration
|
||||||
remain deferred until all rules are implemented.
|
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.**
|
**Actual occupied Homeworld / Skills composition — 13 October.**
|
||||||
The earlier marked-done split-recipient task had only proved plain Homeworld
|
The earlier marked-done split-recipient task had only proved plain Homeworld
|
||||||
funding; it did not finish the ruled occupied allocation. That missing composition
|
funding; it did not finish the ruled occupied allocation. That missing composition
|
||||||
|
|||||||
@@ -1,5 +1,27 @@
|
|||||||
# Dune implementation status
|
# 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
|
## 13 October 2026 — actual occupied income and Leader Skills
|
||||||
|
|
||||||
Corrected the previous task completion claim:9b83b61 proved plain Homeworld
|
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);
|
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);
|
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 { territory } from '../game/board';
|
||||||
import { homeworldShipmentChoice } from '../game/homeworld-shipment-options';
|
import { homeworldShipmentChoice } from '../game/homeworld-shipment-options';
|
||||||
import { quoteStableHomeworldOccupation } from '../game/homeworld-stable-occupation';
|
import { quoteStableHomeworldOccupation } from '../game/homeworld-stable-occupation';
|
||||||
|
import { homeworldContext } from '../game/homeworld-game';
|
||||||
|
import { homeworldForceGroups } from '../game/homeworld-custody';
|
||||||
import {
|
import {
|
||||||
assertClassicHomeworldSkillsCustody, classicHomeworldSkillsPlayer,
|
assertClassicHomeworldSkillsCustody, classicHomeworldSkillsPlayer,
|
||||||
createClassicHomeworldSkillsSetup, nextClassicHomeworldSkillsStep,
|
createClassicHomeworldSkillsSetup, nextClassicHomeworldSkillsStep,
|
||||||
@@ -88,22 +90,23 @@ export type OccupiedEmperorFixture = {
|
|||||||
lot: string; bonus: string; karama: string; physical: string[]; actions: ClassicHomeworldSkillsStep[];
|
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
|
* Guild's real bribes through Mentat -> worm/alliance -> empty Kaitain invasion
|
||||||
* -> later funded auction. No wallet, force, phase, alliance or authority edits. */
|
* -> later funded auction. No wallet, force, phase, alliance or authority edits. */
|
||||||
export function createOccupiedEmperorFixture(options: {
|
export function createOccupiedEmperorFixture(options: {
|
||||||
payerShare?: number; donorShare?: number; donor?: 'guild' | 'atreides' | 'emperor';
|
payerShare?: number; donorShare?: number; donor?: 'guild' | 'atreides' | 'emperor';
|
||||||
|
advanced?: boolean;
|
||||||
} = {}): OccupiedEmperorFixture {
|
} = {}): 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 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];
|
const setup = structuredClone(native.afterSetup), actions = [...native.actions];
|
||||||
let game = structuredClone(setup);
|
let game = structuredClone(setup);
|
||||||
const act = (actor: string, action: Action) => {
|
const act = (actor: string, action: Action) => {
|
||||||
actions.push({ actor, action: structuredClone(action) }); game = applyAction(game, actor, action);
|
actions.push({ actor, action: structuredClone(action) }); game = applyAction(game, actor, action);
|
||||||
};
|
};
|
||||||
const reach = (until: (state: Game) => boolean) => { game = advanceOccupiedEmperor(game, until, actions); };
|
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));
|
reach(g => g.phase === 3 && !!g.auction && occupiedEmperorClean(g));
|
||||||
holdKarama(game, payer);
|
holdKarama(game, payer);
|
||||||
reach(g => g.auction!.active === payer && occupiedEmperorClean(g));
|
reach(g => g.auction!.active === payer && occupiedEmperorClean(g));
|
||||||
@@ -121,7 +124,8 @@ export function createOccupiedEmperorFixture(options: {
|
|||||||
const destination = 'habbanya_ridge_sietch';
|
const destination = 'habbanya_ridge_sietch';
|
||||||
assert.ok(game.players.every(p => !Object.keys(p.forces).some(key => key.startsWith(`${destination}:`))));
|
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,
|
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));
|
reach(g => g.phase === 5 && occupiedEmperorClean(g));
|
||||||
assert.equal(occupiedEmperorPlayer(game, payee).reserves, 0);
|
assert.equal(occupiedEmperorPlayer(game, payee).reserves, 0);
|
||||||
assert.equal(occupiedEmperorPlayer(game, payee).forces[`${destination}:${territory(destination).sectors[0]}`], 20);
|
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 });
|
act(occupier, { type: 'bribe', target: payee, amount: 5 });
|
||||||
reach(occupiedEmperorClean);
|
reach(occupiedEmperorClean);
|
||||||
reach(g => g.turn === 2 && g.phase === 1 && occupiedEmperorClean(g));
|
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));
|
reach(g => !!g.nexus && !g.spiceWindow && !g.spiceResolution && occupiedEmperorClean(g));
|
||||||
act(payer, { type: 'alliance', target: donor });
|
act(payer, { type: 'alliance', target: donor });
|
||||||
act(donor, { type: 'alliance', target: payer });
|
act(donor, { type: 'alliance', target: payer });
|
||||||
|
|||||||
@@ -105,14 +105,18 @@ export type OccupiedGuildFixture = {
|
|||||||
export function createOccupiedGuildFixture(options: {
|
export function createOccupiedGuildFixture(options: {
|
||||||
payer?: string; donor?: string; occupier?: string; prep?: string;
|
payer?: string; donor?: string; occupier?: string; prep?: string;
|
||||||
payerShare?: number; donorShare?: number;
|
payerShare?: number; donorShare?: number;
|
||||||
|
advanced?: boolean;
|
||||||
} = {}): OccupiedGuildFixture {
|
} = {}): OccupiedGuildFixture {
|
||||||
const payer = options.payer ?? 'harkonnen';
|
const payer = options.payer ?? 'harkonnen';
|
||||||
const donor = options.donor ?? 'emperor';
|
const donor = options.donor ?? 'emperor';
|
||||||
const occupier = options.occupier ?? 'harkonnen';
|
const occupier = options.occupier ?? (options.advanced ? 'beneGesserit' : 'harkonnen');
|
||||||
const prep = options.prep ?? 'emperor';
|
const prep = options.prep ?? (options.advanced ? 'harkonnen' : 'emperor');
|
||||||
const { payerShare = 6, donorShare = 2 } = options;
|
const { payerShare = 6, donorShare = 2, advanced = false } = options;
|
||||||
const payee = 'guild', trainer = 'atreides';
|
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 setup = structuredClone(native.afterSetup), actions = [...native.actions];
|
||||||
const staging = [...native.staging];
|
const staging = [...native.staging];
|
||||||
let game = structuredClone(setup);
|
let game = structuredClone(setup);
|
||||||
@@ -123,7 +127,7 @@ export function createOccupiedGuildFixture(options: {
|
|||||||
const reach = (until: (state: Game) => boolean) => {
|
const reach = (until: (state: Game) => boolean) => {
|
||||||
game = advanceOccupiedGuild(game, until, actions);
|
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));
|
reach(g => g.turn === 1 && g.phase === 5 && g.active === prep && occupiedGuildClean(g));
|
||||||
const opening = occupiedGuildPlayer(game, payee).spice;
|
const opening = occupiedGuildPlayer(game, payee).spice;
|
||||||
// One real four-spice shipment income funds the native Guild's later tariff.
|
// 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 =>
|
assert.ok(game.homeworldOccupationHistory!.sources.some(source =>
|
||||||
source.event === authority.entitlement!.qualification && source.cause !== 'setup'));
|
source.event === authority.entitlement!.qualification && source.cause !== 'setup'));
|
||||||
const occupied = structuredClone(game);
|
const occupied = structuredClone(game);
|
||||||
reach(g => g.turn === 2 && g.phase === 1 && occupiedGuildClean(g));
|
const paymentTurn = game.turn + 1;
|
||||||
orderSpice(game, ['worm', 'land']);
|
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));
|
reach(g => !!g.nexus && !g.spiceWindow && !g.spiceResolution && occupiedGuildClean(g));
|
||||||
act(payer, { type: 'alliance', target: donor });
|
act(payer, { type: 'alliance', target: donor });
|
||||||
act(donor, { type: 'alliance', target: payer });
|
act(donor, { type: 'alliance', target: payer });
|
||||||
@@ -164,7 +169,7 @@ export function createOccupiedGuildFixture(options: {
|
|||||||
assert.equal(occupiedGuildPlayer(game, donor).ally, payer);
|
assert.equal(occupiedGuildPlayer(game, donor).ally, payer);
|
||||||
const alliance = structuredClone(game);
|
const alliance = structuredClone(game);
|
||||||
// The later real paid shipment to an empty Arrakis territory.
|
// 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 karama = holdKarama(game, trainer);
|
||||||
const beforePledge = structuredClone(game);
|
const beforePledge = structuredClone(game);
|
||||||
if (donorShare) act(donor, { type: 'pledgeAid', amount: donorShare });
|
if (donorShare) act(donor, { type: 'pledgeAid', amount: donorShare });
|
||||||
|
|||||||
Reference in new issue
Block a user