Preserve Ghola promises during Box selection
Verify and publish checkpoint container / container (push) Canceled after 0s

This commit is contained in:
Quintine committed 2026-10-29 17:52:25 +11:00
1 parent bdd5aaf18c
commit 4224bb3e2a
4 files changed
+34 -2

No files matched your search

+8
View File
@@ -17,6 +17,14 @@ open mode gates.
## Current checkpoint and work
**Ghola promise during Box selection — 29 October. Prototyped / Partial.**
A paid Nullentropy Box selection must preserve an outstanding named Ghola Yes;
an incompatible card is rejected before shuffle while the paid search remains
open. A controlled conserved-card fixture exercises rejection and the real
Ghola replay. Eleven related files pass 51 cases; broader interactions and
deployed verification remain open.
[Contract](TRUTHTRANCE_LEADER_REVIVAL_PROMISES.md#paid-box-selection-preserves-a-ghola-answer--29-october-2026).
**Multiple named Ghola returns — 29 October. Prototyped / Partial.**
Multiple distinct Yes answers can share a bounded native completion when
currently held Ghola and known Box recovery permit separate physical returns.
@@ -4,6 +4,16 @@ The [grouped follow-up](TRUTHTRANCE_REVIVAL_EXPRESSIONS.md) combines these named
returns and ordinary force minima with whole AND/OR, preserving dynamic choices
and the shared native planner. Scalar questions keep their existing meaning.
## Paid Box selection preserves a Ghola answer — 29 October 2026
**Prototyped / Partial.** Once the owner has paid for a Nullentropy Box search,
a selected recovery must preserve the shared completion of any outstanding
named Ghola Yes. An incompatible choice is rejected before the discard pile is
shuffled; the paid search remains open for a legal selection. A controlled
multi-leader fixture uses the original physical Ghola and Box and checks that
an alternative card is rejected before the actual Ghola replay. Broader
interactions, saved-game assurance and deployed verification remain open.
## Multiple named Ghola answers — 29 October 2026
**Prototyped / Partial.** Multiple
+7
View File
@@ -5229,6 +5229,13 @@ function finishNullentropy(
requireRule(!hasMovementPromises(g, player, true) ||
groundMovementCompletion(movementPreview, player) !== null,
'Choose a card that preserves your accepted ground-movement promises. The paid search remains available.');
// The owner has paid for this search but has not yet committed a selection.
// A different recovered card must not consume the Box and erase the last
// owned route to an accepted named Ghola return. Quote the selected outcome
// before shuffling, with the original suspended controls restored.
requireRule(!liveGholaLeaderPromises(g.gholaLeaderPromises ?? [], player, g.turn).some(promise => promise.answer) ||
!!turnActionCompletion(movementPreview, player),
'Choose a card that preserves your named Ghola Truthtrance answers. The paid search remains available.');
// All rejection paths precede this single server shuffle. The paid record and
// final shuffled order are each persisted by the room's normal CAS fence.
const order = shuffle(remaining).map((c) => c.id);
+9 -2
View File
@@ -98,8 +98,15 @@ void test('two named Ghola Yes answers reuse one physical card through the paid
assert.ok(recovery, 'The owner receives the next Box preparation');
assert.equal(recovery.action.type, 'card'); assert.equal(recovery.action.card, f.box);
game = act(game, f.target, recovery.action);
if (game.decision?.kind === 'nullentropy')
game = act(game, f.target, {type:'decision', event:viewGame(game, f.target).nullentropy!.search!.event, card:f.ghola});
assert.equal(game.decision?.kind, 'nullentropy');
const search = viewGame(game, f.target).nullentropy!.search!;
assert.ok(search);
const alternative = search.cards.find(card => card.id !== f.ghola);
assert.ok(alternative, 'The paid search must also contain an independently eligible non-Ghola card');
reject(game, f.target, {type:'decision', event:search.event, card:alternative.id});
assert.equal(viewGame(game, f.target).nullentropy!.search!.event, search.event,
'Rejecting an evasion keeps the already-paid search available');
game = act(game, f.target, {type:'decision', event:search.event, card:f.ghola});
game = advance(game, clean);
assert.ok(player(game, f.target).hand.some(card => card.id === f.ghola));
const secondGuide = viewGame(game, f.target).gholaLeaderPromiseGuide;