Adopt authorized Advanced Moritani normal-reveal forfeiture

This commit is contained in:
Quintine committed 2026-10-02 18:55:40 +10:00
1 parent b89e93a67f
commit 934b2ee4d7
9 files changed
+96 -19

No files matched your search

+13
View File
@@ -25,6 +25,19 @@ authorization and deployment/publication safeguards remain intact. Use the
[bounded context index](DEVELOPMENT.md#context-index) and
[feedback/proof schedule](DEVELOPMENT.md#feedback-and-proof-schedule).
**Authorized Advanced Moritani forfeiture:** the supplied PDF physical35 resolves
the old duration question: a normal own-Traitor-Card reveal forfeits Assassinate
Leaders for the rest of the game, across turns and opponents. The persisted
normal-call flag already enforced that scope; guidance, public history and the
log now name the sourced forfeiture. Basic, exceptional leader custody and the
private-step UX preview gates are unchanged. A permanent engine regression
exercises a genuine turn advance plus a real later loss with no opportunity and
the owner-facing forfeiture reason. See
[the cutover](MORITANI_ASSASSINATE_LEADERS.md#authority-and-advanced-duration-cutover)
and [authorized source](RULE_DECISIONS.md#authorized-source-amendment--1-october-2026).
Final types/lint, coherent assassination/guide38/38, build and390px Prototyped
guide pass; four changed-region local links/anchors are valid.
**Independent cards + Advanced Occupy — bounded Development Verified:** fresh
explicit CLI `--ecaz-treachery` adds all three physical cards36/50/38/50.
Own Ecaz Harass returns only its floor count while original mandatory ceil
+27
View File
@@ -1,5 +1,32 @@
# Dune implementation status
## 2 October 2026 — authorized Advanced Moritani normal-reveal forfeiture
The user-authorized supplied revision, physical p35 Assassinate Leaders D,
resolves the previously open duration: Moritani must not have revealed its
Traitor Card normally **this game**. A normal own-card reveal therefore
forfeits the ability for the rest of the game, across later turns and all
opposing factions, regardless of that battle's result.
The persisted `normalTraitorCall` flag already enforced exactly that scope, so
no state-machine change was needed. Owner-facing blocked text, the public
reveal log and internal guidance now name the sourced forfeiture instead of an
unresolved-duration guard. The change is source alignment, not a newly located
GF9 erratum or a Basic ruling; exceptional leader custody and the independent
private-step UX/public-preview gates are unchanged.
An exact normal-reveal case passes. A new permanent engine regression advances a
genuine turn, swaps to a still-living opposing faction and stages a real later
loss: the flag persists, no assassination opportunity or decision appears and
the owner sees the game-long forfeiture reason. Throwaway native smoke confirmed
the same real-reveal and turn-2 persistence. No database reset, new assurance
campaign or preview-gate change.
Final `check:quick` types/lint, coherent assassination/guide38/38, build and
390px Partial / Development: Prototyped guide pass; four changed-region local
links/anchors are valid. The throwaway driver is removed and the verifier
worker is closed.
## 2 October 2026 — independent Treachery cards with Advanced Occupy
Existing `ecaz-occupy` now accepts explicit fresh CLI `--ecaz-treachery`,
+17 -9
View File
@@ -4,12 +4,13 @@
preview. Normal game-start and publication gates remain closed. This is the
battle-loss ability, separate from the random Assassination Terror token.
## Authority and unresolved interpretation
## Authority and Advanced duration cutover
The [GF9 Ecaz & Moritani rulebook](https://www.gf9games.com/dune/wp-content/uploads/EcazMoritani-Rulebook-LOWRES.pdf),
pp. 6, 14 and 16, supplies the ability, captured-own-leader answer and Karama table.
Independent source review found no official clarification of the disputed duration
below. The runtime uses the following undisputed sequence:
The historical publisher review found no official duration clarification.
The user-authorized revised Advanced PDF now supplies an explicit game-long
condition, recorded below. The runtime retains the following sequence:
1. Native Advanced Moritani loses a normal battle against a surviving opposing
leader disc, with no traitor called.
@@ -23,12 +24,19 @@ below. The runtime uses the following undisputed sequence:
private physical replacement. Use the advantage once per opposing faction.
Karama has no effect on this ability.
The printed statement that a normal traitor reveal loses this advantage does not
specify a battle, faction or game duration. The prototype records the normal call
and explicitly guards further use; it does not label that guard as a settled
permanent forfeiture. The user has been asked whether to keep that interaction
gated or choose a duration. Previously normally revealed cards and exceptional
leader custody also remain outside this first runtime contract.
**Authorized Advanced cutover — 2 October 2026:** the supplied
`UNOFFICIAL_Revised_Dune_Rulebook_v.2.3_web.pdf`, physical p35 / logical p33,
Assassinate Leaders D condition(d), says Moritani must **“not yet [have]
revealed your Traitor Card normally this game.”** A normal own-card reveal
therefore forfeits the advantage for the rest of this game, across later turns
and all opposing factions. The existing persisted normal-call flag already
enforced this scope; owner guidance and public reveal history now describe
the sourced forfeiture rather than an unresolved-duration guard.
This resolves the old duration question under the authorized Advanced source,
not as a newly located GF9 erratum. Basic, exceptional leader interactions and
the independent private-step UX/public-preview gates remain unchanged. See
[source identity and precedence](RULE_DECISIONS.md#authorized-source-amendment--1-october-2026).
The captured-own-leader answer preserves original faction identity: a Moritani
leader used by Harkonnen does not become a Harkonnen-faction assassination card.
+15
View File
@@ -121,6 +121,21 @@ See [combined-card boundaries](ECAZ_OCCUPY_RULES.md#independent-cards-with-combi
Comprehensive recovery/privacy/custody review follows all rules rather than
becoming another prototype assurance wave.
## Advanced Moritani normal-reveal duration cutover — 2 October 2026
The authorized supplied revision resolves the previously open duration:
**Assassinate Leaders D, physical PDF page 35**, requires that Moritani has
**not yet revealed its Traitor Card normally this game**. A normal own-card
reveal therefore forfeits Assassinate Leaders for the rest of the game, across
later turns and every opposing faction, whether that battle was won or lost.
This supersedes the historical “normal call, unresolved duration” guard as
source resolution, not a newly located GF9 erratum or a Basic ruling. The
persisted normal-call flag already enforced game scope; owner guidance, public
reveal history and the log now name the sourced forfeiture. Exceptional leader
custody and the independent private-step UX/public-preview gates are unchanged.
See [the cutover and Advanced sequence](MORITANI_ASSASSINATE_LEADERS.md#authority-and-advanced-duration-cutover).
## Recorded contracts and implementation boundaries
| Topic | Decision to preserve | Detailed authority and evidence |
+2 -2
View File
@@ -7581,7 +7581,7 @@ function projectedMoritaniAssassinate(g: Game, viewer: string) {
const own = viewer === state.owner;
const choices = pending && own ? nexusRule(() => moritaniAssassinateChoices(moritaniAssassinateContext(g,pending))) : null;
return {owner:state.owner,blocked:own && state.normalTraitorCall
? 'Assassinate Leaders after a normal traitor call awaits clarification of the printed loss-of-advantage rule.' : null,
? 'Moritani forfeited Assassinate Leaders for the rest of this game after revealing its Traitor Card normally.' : null,
pending:pending ? {event:pending.event,opponent:pending.opponent,territory:pending.territory,
cards:choices?.cards ?? [],blocked:choices?.blocked ?? null} : null,
history:state.opportunities.filter(r => r.stage === 'revealed' || r.stage === 'replaced').map(r => ({
@@ -20400,7 +20400,7 @@ function resolveBattle(g: Game, retreat?: DiplomatRetreatSelection) {
?.name ?? identity);
log(g, `${holder.name} revealed ${name} as a traitor.`);
if (holder.id === g.moritaniAssassinate?.owner)
log(g, `${holder.name} called a traitor normally. Further Assassinate Leaders use awaits clarification of the printed loss-of-advantage rule.`, {faction:'moritani',name:'Assassinate Leaders boundary'});
log(g, `${holder.name} revealed a Traitor Card normally and forfeited Assassinate Leaders for the rest of this game.`, {faction:'moritani',name:'Assassinate Leaders forfeited'});
}
const dead = (l: Leader | undefined) => {
if (l) {
+2 -1
View File
@@ -18,6 +18,7 @@ export type MoritaniAssassinateReceipt = {
export type MoritaniAssassinateState = {
version: 1;
owner: string;
/** Authorized Advanced source: normal reveal forfeits this ability for the game. */
normalTraitorCall: boolean;
opportunities: MoritaniAssassinateReceipt[];
};
@@ -291,7 +292,7 @@ export function moritaniAssassinateChoices(
return stopped('This Assassinate opportunity has already ended.');
if (state.normalTraitorCall)
return stopped(
'Assassinate after an ordinary Moritani traitor call awaits its duration ruling.',
'Moritani forfeited Assassinate Leaders for the rest of this game after revealing its Traitor Card normally.',
);
if (
state.opportunities.some(
+2 -2
View File
@@ -2997,7 +2997,7 @@ export const RULE_TOPICS: RuleTopic[] = [
'After you lose a battle, this advanced advantage is available only if the opposing leader disc survived and no Traitor was called.',
'You may reveal a Traitor Card for that opposing faction, but it must name a different leader from the one you just fought. If that named leader is not in the Tanks, kill it and collect spice for its value.',
'An already-dead named leader remains a legal reveal, awarding no death or spice. During Mentat Pause, set the revealed card aside face up as a marker and draw a new Traitor Card. You can use this advantage only once against each faction in the game.',
'The printed rule says a normal traitor reveal loses this advantage, but its duration remains unresolved. The preview guards further assassination after a normal call. Karama has no effect against Assassinate Leaders.',
'The authorized revised Advanced PDF, physical page35, requires that you have not revealed your Traitor Card normally this game. A normal own-card reveal therefore forfeits Assassinate Leaders for the rest of this game, across turns and opponents. Existing state preserves that forfeiture; Karama has no effect against the ability. This is the authorized source, not a new GF9 erratum.',
'If Harkonnen has captured your own leader and you hold its Traitor Card, you may call that traitor normally; you may not use Assassinate Leaders on that basis.',
'Terror Assassination chooses a random leader when its token is triggered. It does not use this battle-loss condition, different-leader Traitor Card or once-per-faction allowance. Moritani starts remain unavailable in Basic and Advanced.',
],
@@ -3005,7 +3005,7 @@ export const RULE_TOPICS: RuleTopic[] = [
{area:'Implementation',status:'Partial',detail:'Explicit Advanced Moritani/base-opponent preview before any normal traitor call; native disc deaths, printed bounty, dead-target zero bounty and exact Mentat replacement.',evidence:['game/moritani-assassinate.ts','game/engine.ts']},
{area:'Player controls',status:'Partial',detail:'Uniform private choice/decline, named target and bounty, public face-up markers and readable inspection; normal activation awaits private-step UX decision.',evidence:['components/moritani-assassinate.tsx','components/game-table.tsx']},
{area:'AI',status:'Partial',detail:'All four profiles choose from the owner-only quote or decline; no hidden rival data or strength-calibration claim.',evidence:['game/bots.ts','tests/moritani-assassinate-engine.test.ts']},
{area:'Documentation',status:'Partial',detail:'Official authority, dead-target conditional, normal-call ambiguity, exceptional custody and optional-module boundaries remain explicit.',evidence:['docs/MORITANI_ASSASSINATE_LEADERS.md']},
{area:'Documentation',status:'Partial',detail:'Printed effect and authorized Advanced game-long normal-reveal forfeiture are explicit; exceptional leader custody, private-step UX and optional-module boundaries remain open.',evidence:['docs/MORITANI_ASSASSINATE_LEADERS.md','docs/RULE_DECISIONS.md']},
{area:'Verification',status:'Partial',detail:'Rules, controls, genuine-setup staged battles, private views, authenticated recovery, concurrent writes and physical replacement chains; full faction acceptance remains open.',evidence:['tests/moritani-assassinate-quote.test.ts','tests/moritani-assassinate-engine.test.ts','tests/moritani-assassinate-controls.test.tsx','tests/moritani-assassinate-recovery.test.ts']},
],
related: [
+15 -2
View File
@@ -85,17 +85,30 @@ void test('stale, other-seat, same-leader, dishonest mixed and duplicate assassi
assassinationRejects(mentat,'m',{type:'decision',event,card:'guild-1'});
});
void test('a normal Moritani traitor call records an explicit unresolved guard and cannot create an assassination opportunity',()=>{
void test('a normal Moritani traitor reveal forfeits assassination and cannot create an opportunity',()=>{
const staged=stageAssassinationBattle(assassinationGame(),'guild-0');
const done=resolveAssassinationBattle(staged,{normalCall:true});
assert.equal(done.moritaniAssassinate!.normalTraitorCall,true);
assert.equal(done.moritaniAssassinateCallEvents!.length,1);
assert.equal(done.moritaniAssassinate!.opportunities.length,0);
assert.match(viewGame(done,'m').moritaniAssassinate!.blocked!,/clarification/);
const corrupt=structuredClone(done);corrupt.moritaniAssassinate!.normalTraitorCall=false;
assert.throws(()=>viewGame(corrupt,'m'),/original event/);
});
void test('a sourced normal-reveal forfeiture persists across turns and denies a later genuine loss',()=>{
const first=assassinationToMentat(resolveAssassinationBattle(stageAssassinationBattle(assassinationGame(['m','g','e']),'guild-0'),{normalCall:true}));
assert.equal(first.moritaniAssassinate!.normalTraitorCall,true);assert.equal(first.moritaniAssassinate!.opportunities.length,0);
assert.match(viewGame(first,'m').moritaniAssassinate!.blocked!,/rest of this game/);
first.turn++;
first.players=[first.players[0],first.players[2],first.players[1]];
const second=resolveAssassinationBattle(stageAssassinationBattle(first));
assert.equal(second.lastBattleContext!.result,'normal');assert.notEqual(second.lastBattleContext!.winner,'m');
assert.equal(second.moritaniAssassinate!.normalTraitorCall,true);
assert.equal(second.moritaniAssassinate!.opportunities.length,0);
assert.notEqual(second.decision?.kind,'moritaniAssassinate');
assert.match(viewGame(second,'m').moritaniAssassinate!.blocked!,/rest of this game/);
});
void test('saved assassination receipts and cleanup obligations fail closed after corruption',()=>{
const pending=resolveAssassinationBattle(stageAssassinationBattle(assassinationGame()));
for(const breakSave of [
+3 -3
View File
@@ -187,16 +187,16 @@ void test('already-dead targets remain revealable for no death or spice, while p
assert.deepEqual(context, before);
});
void test('ordinary traitor-call ambiguity and already normally revealed cards stay explicit private guards', () => {
void test('the sourced normal-reveal forfeiture and already normally revealed cards stay explicit private guards', () => {
const context = fixture();
context.state.normalTraitorCall = true;
assert.match(
moritaniAssassinateChoices(context).blocked!,
/ordinary.*duration ruling/,
/forfeited Assassinate Leaders for the rest of this game/,
);
assert.throws(
() => quoteMoritaniAssassinate(context, 'atreides-1'),
/duration ruling/,
/forfeited Assassinate Leaders for the rest of this game/,
);
context.state.normalTraitorCall = false;
context.normallyRevealed = ['atreides-1'];