feat: offer Semuta after clean battle Distrans transfers
This commit is contained in:
1 parent
235f763625
commit
d319faa80c
8 files changed
+113
-1
No files matched your search
@@ -29,6 +29,19 @@ 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 / clean battle Distrans — 11 October.** The completed clean transfer
|
||||
now offers only its public used Distrans during battle. The private gift and
|
||||
sealed opposing plan remain committed; the recipient may itself claim with
|
||||
Semuta without losing or repeating the gift. The original regression fails
|
||||
before and passes after; the final cases cover both separate and claimant
|
||||
recipients. Types/lint and 69 affected unit cases across seven files pass.
|
||||
Twenty-four runtime scenarios cover both rule modes, recipient
|
||||
roles, claim/pass and all four legal AI profiles, followed by legal planning.
|
||||
Competing parents remain separate. Temporary scripts are removed; no saved
|
||||
store or deployed game was targeted. Existing human panels are reused; no new
|
||||
browser evidence is claimed. See
|
||||
[the source/runtime boundary](SEMUTA_SOURCE_UPDATE_20260907.md).
|
||||
|
||||
**Semuta / native Basic Terror — 11 October.** The local `semuta` entry now
|
||||
admits fresh Basic native Richese/Moritani plus classics with CHOAM/Ecaz decks
|
||||
and no optional modules. Private Sabotage and Robbery overflow after clean
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
# Dune implementation status
|
||||
|
||||
## 11 October 2026 — Semuta after a clean battle Distrans transfer
|
||||
|
||||
The completed `distransDiscard` receipt already binds the donor/recipient hand
|
||||
identities and original battle parent. The reaction guard now admits it during
|
||||
battle without changing transfer legality or competing-parent exclusions. Only
|
||||
the public used Distrans is fresh; the private gift is already held and is not
|
||||
a Semuta target.
|
||||
|
||||
The original regression fails before and passes after. The final two cases
|
||||
cover a separate recipient and the Semuta claimant as recipient, preserving one
|
||||
gift, card conservation and the original sealed plan. Original transfer and
|
||||
retired reaction replay reject. A recipient combatant may commit the given
|
||||
weapon in its next legal plan; a claimant recipient retains both its gift and
|
||||
the claimed used Distrans.
|
||||
Types/lint and 69 affected unit cases across seven files pass.
|
||||
|
||||
Twenty-four isolated engine scenarios cover Basic/Advanced, both recipient
|
||||
roles, claim/pass and all four legal AI profiles, then legal planning. Each
|
||||
retains the original opposing plan and one exact transfer. Temporary scripts
|
||||
are removed; no saved store or deployed game was targeted. Existing transfer,
|
||||
Semuta and plan controls are reused; no new browser evidence is claimed.
|
||||
Full Semuta and combinations remain open. Contracts:
|
||||
[Semuta](SEMUTA_SOURCE_UPDATE_20260907.md),
|
||||
[printed transfer and commitments](RICHESE_DISTRANS_ENGINE_AUDIT.md).
|
||||
|
||||
## 11 October 2026 — native Basic Semuta/Terror continuation
|
||||
|
||||
The local `semuta` initializer now additionally admits Basic native
|
||||
|
||||
@@ -30,7 +30,7 @@ The distinction between an ordinary Treachery effect and a cancellable faction/a
|
||||
1. **Giving the activating Distrans itself.** The give-then-discard wording does not explain self-selection. Do not put the same physical card in both the recipient's hand and the discard, discard a different card, or silently omit the discard. Until clarified, require a separate transferred card and label self-selection unsupported, not a printed prohibition.
|
||||
2. **Exact scope of “during a bid.”** The face does not define whether this means one bid declaration or the complete auction of a card. It does not say the entire Bidding phase. A temporary guard over unresolved normal/Once Around/Silent lots, including unsettled payment, is an explicit conservative implementation boundary. Pre-bid declaration/selection, between-lot windows and post-purchase income are distinct contexts; do not silently present a blanket phase-3 ban as the official rule. The user has already been asked about this boundary by root; this audit adds no question or assumed answer.
|
||||
3. **Active Truthtrance answer priority.** Existing question/priority resolution prevents changing the queried hand mid-question. No retrieved Distrans-specific rule overrides it. Keep this narrow existing priority boundary, rather than disabling Distrans for every unrelated decision or response.
|
||||
4. **Future Semuta discard reaction.** Discarding Distrans creates a real discard event. Semuta may eventually react to that card; the transferred card was not discarded and is not a Semuta target. Do not replay the transfer when resuming a future discard reaction. The exact combined Semuta capacity/counter sequence remains in its own unfinished subsystem.
|
||||
4. **Semuta discard reaction — updated 11 October 2026.** The explicit development preview reacts after a clean completed transfer, including during battle. Only the used Distrans is fresh; the privately transferred card is never a Semuta target. Claim or decline cannot repeat the transfer or change a sealed plan. The recipient may itself hold Semuta and claim the used card while keeping the earlier gift. Competing parents retain their separate boundaries; the neutral response and approved full-hand exchange are documented in [the Semuta source/runtime contract](SEMUTA_SOURCE_UPDATE_20260907.md).
|
||||
|
||||
A setup-only prohibition is also not printed on the face. Current supported setup normally has no reachable Distrans in a hand; if a new setup module makes that reachable, verify that boundary rather than inferring a general rule from current implementation gates.
|
||||
|
||||
|
||||
@@ -14,6 +14,12 @@ gifts and competing parents retain the original direct transfer
|
||||
behavior without this Semuta reaction. The table below remains the
|
||||
broader producer/continuation inventory, not a claim of complete coverage.
|
||||
|
||||
11 October 2026 update: the same clean receipt is admitted during battle.
|
||||
Only the public used Distrans is fresh; the private gift and sealed plans remain
|
||||
committed. Claim or decline restores clean planning without another transfer.
|
||||
The recipient may claim with its own held Semuta while keeping the earlier
|
||||
gift. Competing parents retain their separate boundaries.
|
||||
|
||||
A clean ordinary definite Truthtrance answer now offers its consumed card even
|
||||
when another question remains reserved, when the answer bound a supported
|
||||
shipment or unsubmitted battle-plan promise, or when it concerns a submitted/
|
||||
|
||||
@@ -247,6 +247,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 — clean battle Distrans.** A completed clean private
|
||||
Distrans transfer now offers its public used card during battle. Only the
|
||||
battle-presence guard gained that existing receipt exception; transfer legality,
|
||||
sealed plans and competing-parent exclusions are unchanged. The privately given
|
||||
card is already held by the recipient and never becomes a fresh discard target.
|
||||
A recipient holding Semuta may claim the used Distrans while keeping the gift.
|
||||
|
||||
The original permanent case fails before and passes after; the final regression
|
||||
also covers the claimant being the recipient. Claim/pass preserves the single
|
||||
gift, card conservation and original sealed opposing plan. Retired reaction and
|
||||
original transfer replays reject; the recipient combatant may use the given
|
||||
weapon in its next legal plan. Twenty-four isolated engine scenarios cover
|
||||
Basic/Advanced, a separate recipient or the Semuta holder, claim/pass and all
|
||||
four legal AI profiles. Each resumes legal planning with the same original
|
||||
opposing plan and one exact gift. Temporary scripts are removed; no saved store
|
||||
or deployed game was targeted. Existing human transfer, Semuta and planning
|
||||
controls are reused; no new browser evidence is claimed here.
|
||||
Types/lint and 69 affected unit cases across seven files pass.
|
||||
|
||||
**11 October 2026 — native Basic Terror discards.** The same local `semuta`
|
||||
entry additionally admits fresh Basic native Richese/Moritani with classic
|
||||
opponents, exactly CHOAM/Ecaz decks and no optional modules. Native setup remains
|
||||
|
||||
@@ -6774,6 +6774,7 @@ function semutaOfferSupported(
|
||||
continuation.kind === 'residualPoisonDiscard' ||
|
||||
continuation.kind === 'truthtranceDiscard' ||
|
||||
continuation.kind === 'nullentropyDiscard' ||
|
||||
continuation.kind === 'distransDiscard' ||
|
||||
(continuation.kind === 'ordinaryCardDiscard' && continuation.effect === 'ghola') ||
|
||||
!g.battle) &&
|
||||
(continuation.kind !== 'choamKulonDiscard' ||
|
||||
|
||||
@@ -1175,6 +1175,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
'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.',
|
||||
'A completed paid Nullentropy Box search during battle offers only the used Box. Claim or Continue preserves the two-spice payment, private recovered card, already-shuffled pile and sealed opposing plan. The recovered card may be used in normal legal planning; the search is not repeated.',
|
||||
'A clean Distrans transfer during battle also offers only the used Distrans. The private card has already reached its recipient; claim or Continue cannot repeat the gift or change a sealed plan. A recipient holding Semuta may claim the used Distrans and keep the already-transferred card.',
|
||||
'The same local semuta entry also admits Basic native Richese and Moritani with classic opponents, exactly CHOAM/Ecaz decks and no optional modules. A clean shipment/movement Sabotage or Robbery overflow offers its private fresh discard without showing the face before commitment. Claim or Continue preserves the original victim/draw, removed token, shipment payment and forces; Sabotage may then offer its original optional gift. Advanced Moritani, native Ecaz, Ix/E3 mixtures and worm/Ambassador/overlap arrivals remain outside this bounded entry. The table visibly labels the development preview.',
|
||||
] : []),
|
||||
],
|
||||
|
||||
@@ -695,6 +695,53 @@ void test('final Ornithopter discard resolves Semuta before the original Bene Ge
|
||||
assert.equal(claimed.discard.some(card => card.id === semuta.id), true);
|
||||
});
|
||||
|
||||
for (const recipient of ['a', 'r'] as const) void test(
|
||||
`Semuta claims battle Distrans to ${recipient} without repeating its private transfer or changing a sealed plan`, () => {
|
||||
const { g, semuta, hajr } = fixture();
|
||||
player(g, 'a').hand = [];
|
||||
g.deck.push(hajr);
|
||||
const distrans = take(g, 'richese-distrans');
|
||||
const weapon = take(g, g.deck.find(c => c.kind === 'projectile')!.id);
|
||||
player(g, 'e').hand.push(distrans, weapon);
|
||||
Object.assign(g, { phase: 6, active: 'a', order: ['a', 'e', 'r'],
|
||||
response: null, decision: 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 transfer = { type: 'card', card: distrans.id, target: recipient, give: weapon.id };
|
||||
const pending = applyAction(state, 'e', transfer);
|
||||
assert.equal(pending.pendingTreacheryDiscard?.reaction?.stage, 'offer');
|
||||
assert.deepEqual(pending.pendingTreacheryDiscard!.batch.entries.map(entry => entry.card.id), [distrans.id]);
|
||||
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.equal(player(resumed, recipient).hand.filter(c => c.id === weapon.id).length, 1);
|
||||
assert.equal(player(resumed, 'e').hand.some(c => c.id === weapon.id || c.id === distrans.id), false);
|
||||
assert.deepEqual(resumed.battle, state.battle);
|
||||
assert.deepEqual(inventory(resumed), original);
|
||||
assert.throws(() => applyAction(resumed, 'r', { type: 'semutaCommit', event }));
|
||||
assert.throws(() => applyAction(resumed, 'e', transfer));
|
||||
const planned = applyAction(resumed, 'a', { type: 'battlePlan', dial: 1, support: 0,
|
||||
leader: player(resumed, 'a').leaders[0].id, ...(recipient === 'a' ? { weapon: weapon.id } : {}) });
|
||||
if (recipient === 'a') assert.equal(planned.battle!.plans.a.weapon, weapon.id);
|
||||
assert.deepEqual(planned.battle!.plans.e, state.battle!.plans.e);
|
||||
}
|
||||
assert.equal(player(claimed, 'r').hand.some(c => c.id === distrans.id), true);
|
||||
assert.equal(player(claimed, 'r').hand.some(c => c.id === weapon.id), recipient === 'r');
|
||||
assert.equal(claimed.discard.some(c => c.id === semuta.id), true);
|
||||
});
|
||||
|
||||
void test('a completed private Distrans transfer offers only its public used card', () => {
|
||||
const { g, semuta, hajr } = fixture();
|
||||
const distrans = take(g, 'richese-distrans');
|
||||
|
||||
Reference in new issue
Block a user