Hide concealed Duke execution from Ghola guidance
Verify and publish checkpoint container / container (push) Successful in 11m22s
Verify and publish checkpoint container / container (push) Successful in 11m22s
This commit is contained in:
1 parent
4ec4f843a7
commit
5f4eeee4f1
6 files changed
+89
-7
No files matched your search
@@ -17,6 +17,17 @@ open mode gates.
|
|||||||
|
|
||||||
## Current checkpoint and work
|
## Current checkpoint and work
|
||||||
|
|
||||||
|
**Concealed Duke and Ghola guidance — 29 October. Prototyped / Partial.**
|
||||||
|
Ecaz's Ghola choices now use the same projected Duke state as ordinary revival,
|
||||||
|
so secretly executing another player's Duke captive no longer changes that
|
||||||
|
choice list. A staged physical-custody regression fails without the fix; four
|
||||||
|
related files pass 27 cases. The related cohort fixture now waits for Ecaz's
|
||||||
|
actual Advanced Revival opportunity. A separate potential disclosure in the
|
||||||
|
Ambassador acquisition availability remains open in admitted Harkonnen
|
||||||
|
profiles; the plain profile stays guarded. Wider capture assurance and deployed
|
||||||
|
verification remain open.
|
||||||
|
[Scope](DUKE_VIDAL_RULES.md#concealed-duke-ghola-guidance--29-october-2026).
|
||||||
|
|
||||||
**Caladan occupier's auction knowledge — 29 October. Prototyped / Partial.**
|
**Caladan occupier's auction knowledge — 29 October. Prototyped / Partial.**
|
||||||
An eligible occupier can answer the existing Truthtrance auction-inspection
|
An eligible occupier can answer the existing Truthtrance auction-inspection
|
||||||
fact using its actual current ordinary or Advanced Black Market inspection,
|
fact using its actual current ordinary or Advanced Black Market inspection,
|
||||||
|
|||||||
@@ -1,5 +1,24 @@
|
|||||||
# Duke Prad Vidal lifecycle
|
# Duke Prad Vidal lifecycle
|
||||||
|
|
||||||
|
## Concealed Duke Ghola guidance — 29 October 2026
|
||||||
|
|
||||||
|
**Prototyped / Partial.** Ecaz's private Ghola choice list now uses the Duke
|
||||||
|
state projected to Ecaz, just as its ordinary revival guidance does. A secret
|
||||||
|
execution of a Duke captured from another controller therefore does not change
|
||||||
|
that list before Ecaz can know the death. A publicly dead Duke remains offered;
|
||||||
|
actual Ghola actions continue to validate authoritative physical custody.
|
||||||
|
|
||||||
|
The focused test stages real Duke capture/execution transitions and a conserved
|
||||||
|
physical Ghola. It fails on the prior unprojected guidance and passes with the
|
||||||
|
fix; four related files pass 27 cases. Two older cohort journeys initially
|
||||||
|
failed on the prior code too; their fixture now waits for Ecaz's real ordered
|
||||||
|
Advanced Revival opportunity. A separate potential disclosure remains: in
|
||||||
|
admitted Advanced Harkonnen profiles, the Ambassador acquisition descriptor
|
||||||
|
uses actual Duke custody and may distinguish a secretly captured Duke from a
|
||||||
|
publicly equivalent free Duke. Plain Advanced Ecaz/Harkonnen acquisition stays
|
||||||
|
guarded until a public-safe solution is implemented. Broader capture assurance
|
||||||
|
and deployed verification remain open.
|
||||||
|
|
||||||
## Occupied Ecaz grant — 26 October 2026
|
## Occupied Ecaz grant — 26 October 2026
|
||||||
|
|
||||||
The occupied Homeworld now connects its living shared-Duke recipient choice,
|
The occupied Homeworld now connects its living shared-Duke recipient choice,
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# Rule decision index
|
# Rule decision index
|
||||||
|
|
||||||
|
## Concealed Duke and Ghola guidance — 29 October 2026
|
||||||
|
|
||||||
|
Ecaz's Ghola guidance uses its projected Duke state, matching ordinary revival;
|
||||||
|
authoritative play still validates physical custody. A distinct potential leak
|
||||||
|
in Ambassador acquisition availability remains open.
|
||||||
|
[Scope](DUKE_VIDAL_RULES.md#concealed-duke-ghola-guidance--29-october-2026).
|
||||||
|
|
||||||
## Caladan occupier's auction knowledge — 29 October 2026
|
## Caladan occupier's auction knowledge — 29 October 2026
|
||||||
|
|
||||||
The existing Truthtrance auction fact recognizes a successfully inspected
|
The existing Truthtrance auction fact recognizes a successfully inspected
|
||||||
|
|||||||
+3
-3
@@ -48669,8 +48669,8 @@ export function viewGame(state: Game, id: string, now?: number) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// A concealed capture may change the real death count without revealing it
|
// A concealed capture may change the real death count without revealing it
|
||||||
// to the native owner. Derive guidance from the same projected discs that
|
// to the native owner. Derive ordinary and Ghola revival guidance from the
|
||||||
// the seat can inspect; action validation still uses authoritative custody.
|
// same projected discs the seat can inspect; actions still use actual custody.
|
||||||
const visibleRevivalGame = me.faction === 'ecaz' && g.dukeVidal
|
const visibleRevivalGame = me.faction === 'ecaz' && g.dukeVidal
|
||||||
? { ...g, dukeVidal: { ...g.dukeVidal,
|
? { ...g, dukeVidal: { ...g.dukeVidal,
|
||||||
leader: projectLeader(g, g.dukeVidal.leader, id) } }
|
leader: projectLeader(g, g.dukeVidal.leader, id) } }
|
||||||
@@ -49704,7 +49704,7 @@ export function viewGame(state: Game, id: string, now?: number) {
|
|||||||
),
|
),
|
||||||
freeBlocked: g.revivalRules?.freeBlocked?.includes(id) ?? false,
|
freeBlocked: g.revivalRules?.freeBlocked?.includes(id) ?? false,
|
||||||
},
|
},
|
||||||
ghola: gholaOptions(g, me),
|
ghola: gholaOptions(visibleRevivalGame, me),
|
||||||
...(me.faction === 'beneGesserit' && g.advisorMatchingDenied?.length
|
...(me.faction === 'beneGesserit' && g.advisorMatchingDenied?.length
|
||||||
? {advisorMatchingDenied:g.advisorMatchingDenied} : {}),
|
? {advisorMatchingDenied:g.advisorMatchingDenied} : {}),
|
||||||
aid: {
|
aid: {
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import test from 'node:test';
|
||||||
|
import { createGame, newPlayer, viewGame } from '../game/engine';
|
||||||
|
import { acquireDuke, captureDuke, createDukeVidal, DUKE_VIDAL_ID, executeCapturedDuke } from '../game/duke-vidal';
|
||||||
|
import { newRevivalRules } from '../game/revival';
|
||||||
|
import { createNexusCards } from '../game/nexus-cards';
|
||||||
|
import { baseDeck } from '../game/cards';
|
||||||
|
import { ecazDukeRevivalBlock } from '../game/ecaz-duke-revival';
|
||||||
|
|
||||||
|
/** Staged public-equivalent custody in a supported Advanced Nexus roster. */
|
||||||
|
function fixture() {
|
||||||
|
const game = createGame('DUKEGHOLAPRIVACY', newPlayer('ec', 'Ecaz', 'ecaz'), true, ['ecaz']);
|
||||||
|
game.players.push(newPlayer('h', 'Harkonnen', 'harkonnen'), newPlayer('g', 'Guild', 'guild'));
|
||||||
|
Object.assign(game, { status:'playing', phase:0, turn:1, order:['ec','h','g'],
|
||||||
|
revivalRules:newRevivalRules(), nexusCards:{cards:createNexusCards(game.players, () => 0.5),phase:null} });
|
||||||
|
game.deck = baseDeck();
|
||||||
|
const ghola = game.deck.findIndex(card => card.effect === 'ghola');
|
||||||
|
assert.ok(ghola >= 0);
|
||||||
|
game.players[0].hand.push(game.deck.splice(ghola, 1)[0]);
|
||||||
|
const prior = acquireDuke(createDukeVidal(), 'g', 1, 'ecaz');
|
||||||
|
game.dukeVidal = captureDuke(prior, 'h', 'g', 1, 'arrakeen');
|
||||||
|
return game;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test('Ecaz Ghola options do not disclose a secretly executed foreign Duke captive', () => {
|
||||||
|
const alive = fixture(), executed = structuredClone(alive);
|
||||||
|
executed.dukeVidal = executeCapturedDuke(executed.dukeVidal!, 'h', executed.turn);
|
||||||
|
assert.ok(ecazDukeRevivalBlock(alive, 'ec'), 'Actual living captive is unavailable');
|
||||||
|
assert.equal(ecazDukeRevivalBlock(executed, 'ec'), null, 'Secret execution changes authoritative eligibility');
|
||||||
|
const before = viewGame(alive, 'ec'), after = viewGame(executed, 'ec');
|
||||||
|
assert.deepEqual(before.dukeVidal, after.dukeVidal, 'Duke custody remains publicly equivalent');
|
||||||
|
assert.deepEqual(before.ghola, after.ghola, 'Ghola options must not reveal the hidden execution');
|
||||||
|
assert.equal(after.ghola.leaders.some(leader => leader.id === DUKE_VIDAL_ID), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
void test('Ecaz is offered its publicly dead Duke for Ghola', () => {
|
||||||
|
const game = fixture();
|
||||||
|
game.dukeVidal = createDukeVidal();
|
||||||
|
game.dukeVidal.leader.dead = true;
|
||||||
|
game.dukeVidal.leader.deaths = 1;
|
||||||
|
assert.equal(viewGame(game, 'ec').ghola.leaders.some(leader => leader.id === DUKE_VIDAL_ID), true);
|
||||||
|
});
|
||||||
@@ -9,6 +9,9 @@ import {
|
|||||||
} from './fixture-nexus-duke-capture';
|
} from './fixture-nexus-duke-capture';
|
||||||
|
|
||||||
type Transition = { before: Game; after: Game; leader: string };
|
type Transition = { before: Game; after: Game; leader: string };
|
||||||
|
/** Do not stop at another faction's ordered Advanced Revival opportunity. */
|
||||||
|
const ownerRevival = (game: Game, owner: string) => game.phase === 4 && nexusDukeClean(game) &&
|
||||||
|
(!game.revivalOrder || game.revivalOrder.remaining[0] === owner);
|
||||||
export type NativeEcazCohortFixture = {
|
export type NativeEcazCohortFixture = {
|
||||||
game: Game; owner: string; captor: string; plan: NexusDukePlan; actions: NexusDukeStep[];
|
game: Game; owner: string; captor: string; plan: NexusDukePlan; actions: NexusDukeStep[];
|
||||||
deaths: Transition[]; funding: { before: Game; after: Game; amount: number };
|
deaths: Transition[]; funding: { before: Game; after: Game; amount: number };
|
||||||
@@ -73,7 +76,7 @@ export function createNativeEcazCohortFixture(): NativeEcazCohortFixture {
|
|||||||
game = stepNexusDuke(game, plan, { actor: captor,
|
game = stepNexusDuke(game, plan, { actor: captor,
|
||||||
action: { type: 'bribe', target: owner, amount } }, actions);
|
action: { type: 'bribe', target: owner, amount } }, actions);
|
||||||
const funding = { before: fundingBefore, after: structuredClone(game), amount };
|
const funding = { before: fundingBefore, after: structuredClone(game), amount };
|
||||||
game = advanceNexusDuke(game, g => g.phase === 4 && nexusDukeClean(g), plan, actions);
|
game = advanceNexusDuke(game, g => ownerRevival(g, owner), plan, actions);
|
||||||
const firstBefore = structuredClone(game);
|
const firstBefore = structuredClone(game);
|
||||||
const firstLeader = native[0];
|
const firstLeader = native[0];
|
||||||
assert.ok(viewGame(game, owner).revival.leaders.some(l => l.id === firstLeader));
|
assert.ok(viewGame(game, owner).revival.leaders.some(l => l.id === firstLeader));
|
||||||
@@ -81,7 +84,7 @@ export function createNativeEcazCohortFixture(): NativeEcazCohortFixture {
|
|||||||
action: { type: 'reviveLeader', leader: firstLeader } }, actions);
|
action: { type: 'reviveLeader', leader: firstLeader } }, actions);
|
||||||
const first = { before: firstBefore, after: structuredClone(game), leader: firstLeader };
|
const first = { before: firstBefore, after: structuredClone(game), leader: firstLeader };
|
||||||
const repeat = kill(firstLeader);
|
const repeat = kill(firstLeader);
|
||||||
game = advanceNexusDuke(game, g => g.phase === 4 && nexusDukeClean(g), plan, actions);
|
game = advanceNexusDuke(game, g => ownerRevival(g, owner), plan, actions);
|
||||||
plan.ship = {};
|
plan.ship = {};
|
||||||
assert.equal(nexusDukePlayer(game, owner).leaders.find(l => l.id === firstLeader)!.deaths, 2);
|
assert.equal(nexusDukePlayer(game, owner).leaders.find(l => l.id === firstLeader)!.deaths, 2);
|
||||||
assertNexusDukeCustody(game);
|
assertNexusDukeCustody(game);
|
||||||
@@ -119,7 +122,7 @@ export function createNativeEcazSecondCohortFixture(): NativeEcazSecondCohortFix
|
|||||||
const paid: Transition[] = [], repeats: Transition[] = [];
|
const paid: Transition[] = [], repeats: Transition[] = [];
|
||||||
let ghola!: Transition;
|
let ghola!: Transition;
|
||||||
for (let i = 0; i < 3; i++) {
|
for (let i = 0; i < 3; i++) {
|
||||||
game = advanceNexusDuke(game, g => g.phase === 4 && nexusDukeClean(g), plan, actions);
|
game = advanceNexusDuke(game, g => ownerRevival(g, owner), plan, actions);
|
||||||
const before = structuredClone(game);
|
const before = structuredClone(game);
|
||||||
game = stepNexusDuke(game, plan, { actor: owner,
|
game = stepNexusDuke(game, plan, { actor: owner,
|
||||||
action: { type: 'reviveLeader', leader: native[i] } }, actions);
|
action: { type: 'reviveLeader', leader: native[i] } }, actions);
|
||||||
@@ -138,7 +141,7 @@ export function createNativeEcazSecondCohortFixture(): NativeEcazSecondCohortFix
|
|||||||
}
|
}
|
||||||
const last = poisonDeath(game, death, native[3]);
|
const last = poisonDeath(game, death, native[3]);
|
||||||
repeats.push(last);
|
repeats.push(last);
|
||||||
game = advanceNexusDuke(last.after, g => g.phase === 4 && nexusDukeClean(g), plan, actions);
|
game = advanceNexusDuke(last.after, g => ownerRevival(g, owner), plan, actions);
|
||||||
plan.ship = {};
|
plan.ship = {};
|
||||||
assert.equal(nexusDukePlayer(game, owner).leaders.find(l => l.id === native[4])!.dead, false);
|
assert.equal(nexusDukePlayer(game, owner).leaders.find(l => l.id === native[4])!.dead, false);
|
||||||
assert.equal(game.dukeVidal!.leader.dead, true);
|
assert.equal(game.dukeVidal!.leader.dead, true);
|
||||||
|
|||||||
Reference in new issue
Block a user