Adopt authorized Advanced Moritani normal-reveal forfeiture
This commit is contained in:
1 parent
b89e93a67f
commit
934b2ee4d7
9 files changed
+96
-19
No files matched your search
@@ -25,6 +25,19 @@ authorization and deployment/publication safeguards remain intact. Use the
|
|||||||
[bounded context index](DEVELOPMENT.md#context-index) and
|
[bounded context index](DEVELOPMENT.md#context-index) and
|
||||||
[feedback/proof schedule](DEVELOPMENT.md#feedback-and-proof-schedule).
|
[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
|
**Independent cards + Advanced Occupy — bounded Development Verified:** fresh
|
||||||
explicit CLI `--ecaz-treachery` adds all three physical cards36/50/38/50.
|
explicit CLI `--ecaz-treachery` adds all three physical cards36/50/38/50.
|
||||||
Own Ecaz Harass returns only its floor count while original mandatory ceil
|
Own Ecaz Harass returns only its floor count while original mandatory ceil
|
||||||
|
|||||||
@@ -1,5 +1,32 @@
|
|||||||
# Dune implementation status
|
# 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
|
## 2 October 2026 — independent Treachery cards with Advanced Occupy
|
||||||
|
|
||||||
Existing `ecaz-occupy` now accepts explicit fresh CLI `--ecaz-treachery`,
|
Existing `ecaz-occupy` now accepts explicit fresh CLI `--ecaz-treachery`,
|
||||||
|
|||||||
@@ -4,12 +4,13 @@
|
|||||||
preview. Normal game-start and publication gates remain closed. This is the
|
preview. Normal game-start and publication gates remain closed. This is the
|
||||||
battle-loss ability, separate from the random Assassination Terror token.
|
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),
|
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.
|
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
|
The historical publisher review found no official duration clarification.
|
||||||
below. The runtime uses the following undisputed sequence:
|
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
|
1. Native Advanced Moritani loses a normal battle against a surviving opposing
|
||||||
leader disc, with no traitor called.
|
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.
|
private physical replacement. Use the advantage once per opposing faction.
|
||||||
Karama has no effect on this ability.
|
Karama has no effect on this ability.
|
||||||
|
|
||||||
The printed statement that a normal traitor reveal loses this advantage does not
|
**Authorized Advanced cutover — 2 October 2026:** the supplied
|
||||||
specify a battle, faction or game duration. The prototype records the normal call
|
`UNOFFICIAL_Revised_Dune_Rulebook_v.2.3_web.pdf`, physical p35 / logical p33,
|
||||||
and explicitly guards further use; it does not label that guard as a settled
|
Assassinate Leaders D condition(d), says Moritani must **“not yet [have]
|
||||||
permanent forfeiture. The user has been asked whether to keep that interaction
|
revealed your Traitor Card normally this game.”** A normal own-card reveal
|
||||||
gated or choose a duration. Previously normally revealed cards and exceptional
|
therefore forfeits the advantage for the rest of this game, across later turns
|
||||||
leader custody also remain outside this first runtime contract.
|
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
|
The captured-own-leader answer preserves original faction identity: a Moritani
|
||||||
leader used by Harkonnen does not become a Harkonnen-faction assassination card.
|
leader used by Harkonnen does not become a Harkonnen-faction assassination card.
|
||||||
|
|||||||
@@ -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
|
Comprehensive recovery/privacy/custody review follows all rules rather than
|
||||||
becoming another prototype assurance wave.
|
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
|
## Recorded contracts and implementation boundaries
|
||||||
|
|
||||||
| Topic | Decision to preserve | Detailed authority and evidence |
|
| Topic | Decision to preserve | Detailed authority and evidence |
|
||||||
|
|||||||
+2
-2
@@ -7581,7 +7581,7 @@ function projectedMoritaniAssassinate(g: Game, viewer: string) {
|
|||||||
const own = viewer === state.owner;
|
const own = viewer === state.owner;
|
||||||
const choices = pending && own ? nexusRule(() => moritaniAssassinateChoices(moritaniAssassinateContext(g,pending))) : null;
|
const choices = pending && own ? nexusRule(() => moritaniAssassinateChoices(moritaniAssassinateContext(g,pending))) : null;
|
||||||
return {owner:state.owner,blocked:own && state.normalTraitorCall
|
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,
|
pending:pending ? {event:pending.event,opponent:pending.opponent,territory:pending.territory,
|
||||||
cards:choices?.cards ?? [],blocked:choices?.blocked ?? null} : null,
|
cards:choices?.cards ?? [],blocked:choices?.blocked ?? null} : null,
|
||||||
history:state.opportunities.filter(r => r.stage === 'revealed' || r.stage === 'replaced').map(r => ({
|
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);
|
?.name ?? identity);
|
||||||
log(g, `${holder.name} revealed ${name} as a traitor.`);
|
log(g, `${holder.name} revealed ${name} as a traitor.`);
|
||||||
if (holder.id === g.moritaniAssassinate?.owner)
|
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) => {
|
const dead = (l: Leader | undefined) => {
|
||||||
if (l) {
|
if (l) {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export type MoritaniAssassinateReceipt = {
|
|||||||
export type MoritaniAssassinateState = {
|
export type MoritaniAssassinateState = {
|
||||||
version: 1;
|
version: 1;
|
||||||
owner: string;
|
owner: string;
|
||||||
|
/** Authorized Advanced source: normal reveal forfeits this ability for the game. */
|
||||||
normalTraitorCall: boolean;
|
normalTraitorCall: boolean;
|
||||||
opportunities: MoritaniAssassinateReceipt[];
|
opportunities: MoritaniAssassinateReceipt[];
|
||||||
};
|
};
|
||||||
@@ -291,7 +292,7 @@ export function moritaniAssassinateChoices(
|
|||||||
return stopped('This Assassinate opportunity has already ended.');
|
return stopped('This Assassinate opportunity has already ended.');
|
||||||
if (state.normalTraitorCall)
|
if (state.normalTraitorCall)
|
||||||
return stopped(
|
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 (
|
if (
|
||||||
state.opportunities.some(
|
state.opportunities.some(
|
||||||
|
|||||||
+2
-2
@@ -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.',
|
'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.',
|
'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.',
|
'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.',
|
'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.',
|
'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:'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:'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:'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']},
|
{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: [
|
related: [
|
||||||
|
|||||||
@@ -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'});
|
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 staged=stageAssassinationBattle(assassinationGame(),'guild-0');
|
||||||
const done=resolveAssassinationBattle(staged,{normalCall:true});
|
const done=resolveAssassinationBattle(staged,{normalCall:true});
|
||||||
assert.equal(done.moritaniAssassinate!.normalTraitorCall,true);
|
assert.equal(done.moritaniAssassinate!.normalTraitorCall,true);
|
||||||
assert.equal(done.moritaniAssassinateCallEvents!.length,1);
|
assert.equal(done.moritaniAssassinateCallEvents!.length,1);
|
||||||
assert.equal(done.moritaniAssassinate!.opportunities.length,0);
|
assert.equal(done.moritaniAssassinate!.opportunities.length,0);
|
||||||
assert.match(viewGame(done,'m').moritaniAssassinate!.blocked!,/clarification/);
|
|
||||||
const corrupt=structuredClone(done);corrupt.moritaniAssassinate!.normalTraitorCall=false;
|
const corrupt=structuredClone(done);corrupt.moritaniAssassinate!.normalTraitorCall=false;
|
||||||
assert.throws(()=>viewGame(corrupt,'m'),/original event/);
|
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',()=>{
|
void test('saved assassination receipts and cleanup obligations fail closed after corruption',()=>{
|
||||||
const pending=resolveAssassinationBattle(stageAssassinationBattle(assassinationGame()));
|
const pending=resolveAssassinationBattle(stageAssassinationBattle(assassinationGame()));
|
||||||
for(const breakSave of [
|
for(const breakSave of [
|
||||||
|
|||||||
@@ -187,16 +187,16 @@ void test('already-dead targets remain revealable for no death or spice, while p
|
|||||||
assert.deepEqual(context, before);
|
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();
|
const context = fixture();
|
||||||
context.state.normalTraitorCall = true;
|
context.state.normalTraitorCall = true;
|
||||||
assert.match(
|
assert.match(
|
||||||
moritaniAssassinateChoices(context).blocked!,
|
moritaniAssassinateChoices(context).blocked!,
|
||||||
/ordinary.*duration ruling/,
|
/forfeited Assassinate Leaders for the rest of this game/,
|
||||||
);
|
);
|
||||||
assert.throws(
|
assert.throws(
|
||||||
() => quoteMoritaniAssassinate(context, 'atreides-1'),
|
() => quoteMoritaniAssassinate(context, 'atreides-1'),
|
||||||
/duration ruling/,
|
/forfeited Assassinate Leaders for the rest of this game/,
|
||||||
);
|
);
|
||||||
context.state.normalTraitorCall = false;
|
context.state.normalTraitorCall = false;
|
||||||
context.normallyRevealed = ['atreides-1'];
|
context.normallyRevealed = ['atreides-1'];
|
||||||
|
|||||||
Reference in new issue
Block a user