feat: offer Semuta after ordinary battle Ghola revival

This commit is contained in:
Quintine committed 2026-10-12 01:40:24 +11:00
1 parent da61249592
commit 066a097618
8 files changed
+120 -1

No files matched your search

+11
View File
@@ -29,6 +29,17 @@ optional skill bands, combinations and public expansion starts remain open.
Rules implementation continues; comprehensive assurance and administration
remain deferred until all rules are implemented.
**Semuta / ordinary battle Ghola — 11 October.** A completed ordinary Ghola
now offers its consumed card during battle. Revival, the opposing sealed plan
and existing competing-parent boundaries are unchanged. The regression fails
before and passes after; 24 isolated runtime scenarios cover Basic/Advanced,
leader/three-force revival, claim/pass and all four legal AI profiles, then
legal planning. Resumption cannot revive again or alter the opposing plan.
Temporary scripts are removed; no saved store or deployed game was targeted.
Types/lint and 101 affected unit cases across seven files pass.
Human controls reuse the existing panels; no new browser acceptance is claimed.
See [the source/runtime boundary](SEMUTA_SOURCE_UPDATE_20260907.md).
**Semuta / native Advanced Harkonnen capture — 11 October.** Mandatory and
winner-card battle discards now offer Semuta with the original capture pending.
Claim or all-seat decline preserves winner casualties and cleanup before the
+29
View File
@@ -1,5 +1,34 @@
# Dune implementation status
## 11 October 2026 — Semuta after ordinary battle Ghola revival
The completed ordinary-card receipt already binds Ghola revival and the original
battle, but the reaction's battle-presence guard excluded it. The guard now admits
only that completed ordinary Ghola effect in addition to its existing battle
producers. Revival and plan legality are unchanged; competing parents and
market-specific Ghola interruptions remain separate.
The permanent regression fails before and passes after: claim/pass preserves
the returned leader, death history, sealed opposing plan and physical cards;
retired-event/original-card replays reject, and the returned leader can be used
in the next legal plan. Twenty-four isolated engine scenarios cover both rule
modes, leader or three-force revival, claim/pass and all four legal AI profiles.
Each resumes legal planning with the original opposing plan and exactly the
committed revival. Force revival leaves Tanks0/reserves16. The first Advanced
probe omitted declining the existing full-plan inspection; the corrected
Advanced run uses its actual decline action before readiness.
Types/lint pass; 101 affected unit cases across seven files pass. The first test
selection used an unsupported exclusion flag and ran no tests; the corrected
selection uses the runner's `--suite unit`.
Temporary scripts are removed; no saved store or deployed game was targeted.
Existing human Ghola, Semuta and plan controls are reused; changed-path evidence
here is engine runtime, not a new browser check. Full Semuta and deployed
acceptance remain open. Contracts:
[Semuta](SEMUTA_SOURCE_UPDATE_20260907.md),
[completed ordinary effects](ORDINARY_CARD_DISCARD_CONTINUATIONS.md).
## 11 October 2026 — Semuta before native Advanced Harkonnen capture
The resolved battle and winner-cleanup receipts already validate the original
@@ -16,6 +16,13 @@ The live ordinary-card dispatcher retains its existing timing, selection, owners
The suffix marks the discard sequence resolved, clears the frame and restores `response`, `decision`, `pendingKarama` and `phaseOpening`. It does not replay the original action, repeat availability checks against the post-effect position, regenerate randomness or restore a whole older Game. Normal finalizers run only after restoration. A newly restored Tleilaxu income response may wait for its real cancellation opportunity or settle automatically under the existing rules.
11 October 2026 Semuta update: in the explicit Richese development preview, a
completed ordinary Ghola may offer its consumed card during battle. The same
receipt binds the revived pieces and original battle, including a sealed
opposing plan. Claim or all-seat decline restores controls without reviving
again, then normal planning may use the returned leader. Competing parents and
other unsupported producers retain their original boundaries.
## Ghola custody and earned income
Ghola's existing helper remains authoritative. Force revival transfers the chosen ordinary/elite counts once, preserving normal revival usage and the existing Ixian cyborg exception. An owned dead leader keeps its identity, original pool, death count and control record; only the existing revival changes occur. A controlled foreign leader remains in its original owner's leader array. Captured-leader denial and native-owner denial while foreign control persists remain unchanged. Kwisatz Haderach's revival cycle advances only in the actual Ghola effect, never during recovery.
+6
View File
@@ -33,6 +33,12 @@ Harkonnen, loser and territory; winner casualties and optional card cleanup
still precede the capture offer. Claim or decline neither samples a captive
early nor repeats the capture. Other pending battle rewards remain gated.
Completed ordinary Ghola disposal may now offer Semuta during battle as well.
Revival has already committed; the receipt binds the returned leader/forces and
original battle, including a sealed opposing plan. Claim or decline restores
planning without reviving again or changing that plan. This does not admit
competing parents or market-specific Ghola interruptions.
Clean Once Around Sapho stages `saphoAuctionDiscard` after the open
lot's finite bidder reorder, hand-slot release and public card disposal,
but before settlement. The saved receipt binds the lot, prior funding,
+19
View File
@@ -245,6 +245,25 @@ separate [reaction privacy audit](SEMUTA_PRIVACY_REVIEW.md) explains why
auto-closing only when nobody secretly holds the card would leak
possession. The main game and public expansion gates remain closed.
**11 October 2026 — ordinary battle Ghola.** The consumed Ghola now receives
the same Semuta offer during battle after its revival commits. Only the
battle-presence guard gained the completed ordinary Ghola exception; revival,
plan legality and existing competing-parent exclusions are unchanged. The
receipt already binds revived pieces and the original battle. Claim or all-seat
decline cannot revive again or change a sealed opposing plan.
The permanent regression fails before and passes after, covering a returned
leader, claim/pass, unchanged opposing plan, card conservation and rejected
retired-event/original-card replay. Twenty-four isolated engine scenarios cover
Basic and Advanced, leader or three-force revival, claim/pass and all four legal
AI profiles. Each resumes legal planning with the same opposing plan and
exactly the committed revival; force revival leaves Tanks0/reserves16. The
Advanced probe first needed the existing full-plan inspection declined before
readiness; no preparation bypass was added. No saved store or deployed game was
targeted. Human controls reuse the existing Ghola, Semuta and battle-plan panels;
this checkpoint's changed-path proof is engine runtime, not new browser evidence.
Types/lint and 101 affected unit cases across seven files pass.
**11 October 2026 — native Harkonnen capture suffix.** Mandatory and winner
battle discard batches now offer Semuta while the original Advanced Harkonnen
capture remains pending. Only the capture exclusion changed; the battle receipt
+3 -1
View File
@@ -6764,7 +6764,9 @@ function semutaOfferSupported(
continuation.kind === 'ixAllyCard' || !g.richeseAuction) &&
(continuation.kind === 'saphoAggressorDiscard' ||
continuation.kind === 'residualPoisonDiscard' ||
continuation.kind === 'truthtranceDiscard' || !g.battle) &&
continuation.kind === 'truthtranceDiscard' ||
(continuation.kind === 'ordinaryCardDiscard' && continuation.effect === 'ghola') ||
!g.battle) &&
(continuation.kind !== 'choamKulonDiscard' ||
(!g.ornithopter && !g.pendingChoamMove && !g.summonedWorm &&
g.wormRides.length === 0)) &&
+1
View File
@@ -1173,6 +1173,7 @@ export const RULE_TOPICS: RuleTopic[] = [
: 'Reference component only: this Richese card’s game actions are not enabled.',
...(definition.card.effect === 'semutaDrug' ? [
'Mandatory and winner battle discards also pause before native Advanced Harkonnen capture. Claim or Continue completes the original casualties and card cleanup before the capture offer; it does not sample a captive early or repeat capture. Other pending rewards remain outside this bounded path.',
'An ordinary Ghola played during battle also offers its consumed card after revival. Claim or Continue preserves the revived leader or forces and the opposing sealed plan, then normal planning resumes. It cannot revive again by resuming the receipt.',
] : []),
],
...(definition.card.effect === 'semutaDrug'
+44
View File
@@ -784,6 +784,50 @@ for (const answer of ['yes', 'no'] as const) void test(
assert.equal(claimed.discard.some(card => card.id === semuta.id), true);
});
void test('Semuta claims a battle Ghola without reviving twice or changing the sealed opposing plan', () => {
const { g, semuta, hajr } = fixture();
player(g, 'a').hand = [];
g.deck.push(hajr);
const ghola = take(g, 'ghola');
player(g, 'a').hand.push(ghola);
const leader = player(g, 'a').leaders[0];
Object.assign(leader, { dead: true, deaths: 1 });
Object.assign(g, { phase: 6, active: 'a', order: ['a', 'e', 'r'],
decision: null, response: null, phaseOpening: null, movementRemaining: [] });
for (const id of ['a', 'e']) {
player(g, id).forces = { 'arrakeen:10': 4 };
player(g, id).reserves = 16;
}
const original = inventory(g);
let state = applyAction(g, 'a', { type: 'chooseBattle', territory: 'arrakeen', target: 'e' });
while (state.battle?.preparation)
state = applyAction(state, state.battle.preparation.owner, { type: 'declineBattlePower' });
for (const id of ['a', 'e'])
state = applyAction(state, id, { type: 'battlePreparationReady', event: state.battle!.preLeader!.event });
state = applyAction(state, 'e', { type: 'battlePlan', dial: 0, support: 0,
leader: player(state, 'e').leaders[0].id });
const pending = applyAction(state, 'a', { type: 'card', card: ghola.id, leader: leader.id });
assert.equal(pending.pendingTreacheryDiscard?.reaction?.stage, 'offer');
assert.equal(player(pending, 'a').leaders[0].dead, false);
assert.equal(player(pending, 'a').leaders[0].deaths, 1);
const event = pending.pendingTreacheryDiscard!.batch.event;
let declined = reload(pending);
for (const id of ['a', 'e', 'r']) declined = applyAction(declined, id, { type: 'semutaPass', event });
const claimed = applyAction(reload(pending), 'r', { type: 'semutaCommit', event });
for (const resumed of [declined, claimed]) {
assert.deepEqual(resumed.battle, pending.battle);
assert.deepEqual(player(resumed, 'a').leaders, player(pending, 'a').leaders);
assert.deepEqual(inventory(resumed), original);
assert.throws(() => applyAction(resumed, 'r', { type: 'semutaCommit', event }));
assert.throws(() => applyAction(resumed, 'a', { type: 'card', card: ghola.id, leader: leader.id }));
const planned = applyAction(resumed, 'a', { type: 'battlePlan', dial: 1, support: 0, leader: leader.id });
assert.equal(planned.battle!.plans.a.leader, leader.id);
assert.deepEqual(planned.battle!.plans.e, state.battle!.plans.e);
}
assert.equal(player(claimed, 'r').hand.some(c => c.id === ghola.id), true);
assert.equal(claimed.discard.some(c => c.id === semuta.id), true);
});
void test('Semuta preserves an unsubmitted battle-plan promise through claim and decline', () => {
const { g, semuta, hajr } = fixture();
player(g, 'a').hand = [];