Allow group actions after settled Truthtrance
Verify and publish checkpoint container / container (push) Canceled after 0s
Verify and publish checkpoint container / container (push) Canceled after 0s
This commit is contained in:
1 parent
9cc659602c
commit
420943b890
8 files changed
+135
-5
No files matched your search
@@ -17,6 +17,14 @@ open mode gates.
|
||||
|
||||
## Current checkpoint and work
|
||||
|
||||
**Staged group actions after Truthtrance history — 29 October. Prototyped / Partial.**
|
||||
Settled commitment records no longer permanently disable group Nexus and
|
||||
funding. Unresolved records remain blocked until reconciliation, and active
|
||||
bribe-expression and revival variants are also fenced. Group play is still
|
||||
unavailable from the lobby. Five related files pass 60 cases; typecheck, lint
|
||||
and the production build pass. The new cases stage commitment histories and exercise real group
|
||||
actions, rather than a full Truthtrance journey. [Scope](OPTIONAL_ALLIANCE_VICTORY.md#group-actions-after-truthtrance-history--29-october-2026).
|
||||
|
||||
**Staged group funding with Karama shipment pricing — 29 October. Prototyped / Partial.**
|
||||
Group pledges remain available at a purchased Karama rate for physical reserve
|
||||
shipments and native Guild transport, with all shares paid to the bank. Group
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# Optional three-stronghold alliance victory
|
||||
|
||||
## Group actions after Truthtrance history — 29 October 2026
|
||||
|
||||
**Staged Basic prototype, not a selectable game mode.** Fulfilled and released
|
||||
Truthtrance records no longer disable later group Nexus negotiations or group
|
||||
funding. Unresolved records remain fenced until their native reconciliation
|
||||
closes them, including an unresolved older record; funding also fences live
|
||||
bribe-expression and leader/expression revival commitments. The existing
|
||||
committed-bid settlement exception is retained. Combining group actions with
|
||||
unresolved commitments remains unfinished, and no lobby exposes group play.
|
||||
|
||||
## Group funding with Karama shipment pricing — 29 October 2026
|
||||
|
||||
**Staged Basic prototype, not a selectable game mode.** Group members can
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Rule decision index
|
||||
|
||||
## Group actions after Truthtrance history — 29 October 2026
|
||||
|
||||
Settled commitment records no longer prevent staged group negotiation and
|
||||
funding. Unresolved records continue to require native reconciliation before
|
||||
these unsupported combinations can proceed. [Scope](OPTIONAL_ALLIANCE_VICTORY.md#group-actions-after-truthtrance-history--29-october-2026).
|
||||
|
||||
## Group funding with Karama shipment pricing — 29 October 2026
|
||||
|
||||
The staged Basic group prototype retains separate donor shares when a player
|
||||
|
||||
+10
-4
@@ -16031,6 +16031,11 @@ function allianceQuestionWindow(g: Game): boolean {
|
||||
return !!allianceOpportunity(g) && !g.spiceWindow && !g.spiceResolution && !g.response &&
|
||||
!g.decision && !g.phaseOpening && !g.pendingTreacheryDiscard && !g.pendingKarama && !g.pendingKull;
|
||||
}
|
||||
/** Retained settled Truthtrance history is not an active commitment. Keep
|
||||
* unresolved records fenced until native reconciliation has closed them. */
|
||||
function unsettledPromises(records: readonly {fulfilled?: boolean; released?: boolean}[] | undefined): boolean {
|
||||
return !!records?.some(record => !record.fulfilled && !record.released);
|
||||
}
|
||||
/** Group negotiation is deliberately unexposed until its other alliance
|
||||
* mechanics are integrated. This narrow profile keeps existing pair games
|
||||
* and unrelated commitments on their original paths. */
|
||||
@@ -16044,8 +16049,8 @@ function coalitionNexusAvailable(g: Game): boolean {
|
||||
g.expansions.length === 0 && g.players.every(seat =>
|
||||
['atreides', 'beneGesserit', 'emperor', 'fremen', 'guild', 'harkonnen'].includes(seat.faction)) &&
|
||||
Object.values(g.aid).every(credit => credit.amount === 0) &&
|
||||
!(g.alliancePromises?.length || g.allianceExpressionPromises?.length ||
|
||||
g.bribePromises?.length || g.bribeExpressionPromises?.length || g.turnActionPromises?.length);
|
||||
![g.alliancePromises, g.allianceExpressionPromises, g.bribePromises,
|
||||
g.bribeExpressionPromises, g.turnActionPromises].some(unsettledPromises);
|
||||
}
|
||||
function coalitionRule<T>(work: () => T): T {
|
||||
try { return work(); }
|
||||
@@ -16103,8 +16108,9 @@ function coalitionFundingAvailable(g: Game, p: Player, committedBid = false): bo
|
||||
g.expansions.length || g.homeworlds || g.nexusCards || g.leaderSkills || g.techTokens ||
|
||||
g.strongholdCards || g.ecazTreachery || g.discoveryEnabled ||
|
||||
g.spiceBankerIncomePreview || g.kullPreview || g.richeseAuction || g.ixAuction ||
|
||||
(!committedBid && (g.bidPromises?.length || g.phaseBidPromises?.length || g.turnActionPromises?.length ||
|
||||
g.revivalPromises?.length || g.bribePromises?.length))) return false;
|
||||
(!committedBid && [g.bidPromises, g.phaseBidPromises, g.turnActionPromises,
|
||||
g.revivalPromises, g.leaderRevivalPromises, g.revivalExpressionPromises,
|
||||
g.bribePromises, g.bribeExpressionPromises].some(unsettledPromises))) return false;
|
||||
const group = coalitionRule(() => coalitionOf(g.players, g.coalitionState, p.id));
|
||||
return group?.members.length === 3;
|
||||
}
|
||||
|
||||
+1
-1
@@ -4505,7 +4505,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
{ area: 'Player controls', status: 'Partial', detail: 'The lobby offers the pair option. A staged group Emperor can choose either ally for spice and extra revival; group games cannot yet be started from the lobby.', evidence: ['components/game-table.tsx', 'components/coalition-emperor-aid.tsx', 'tests/short-alliance-victory-controls.test.tsx'] },
|
||||
{ area: 'AI', status: 'Partial', detail: 'Legal alliance and victory paths use the shared target; staged group Emperor bots can offer a targeted revival. Strategic tuning remains deferred.', evidence: ['game/bots.ts', 'game/victory-progress.ts'] },
|
||||
{ area: 'Documentation', status: 'Partial', detail: 'The printed option and provisional two-controller precedence are recorded.', evidence: ['docs/OPTIONAL_ALLIANCE_VICTORY.md'] },
|
||||
{ area: 'Verification', status: 'Partial', detail: 'Focused pair and staged group evidence includes real Nexus actions, Guild transport, purchased-rate funding, Emperor and Fremen aid, worm protection, battle preparation and allied traitor votes; larger alliances and full combinations remain open.', evidence: ['tests/alliance-victory-option.test.ts', 'tests/short-alliance-victory-runtime.test.ts', 'tests/coalition-guild-shipment.test.ts', 'tests/coalition-guild-transport.test.ts', 'tests/coalition-fremen-worm.test.ts', 'tests/coalition-emperor-aid.test.ts', 'tests/coalition-fremen-revival.test.ts', 'tests/coalition-battle-preparation.test.ts', 'tests/coalition-battle-power-question.test.ts', 'tests/coalition-harkonnen-traitor.test.ts', 'tests/coalition-traitor-declarations.test.ts'] },
|
||||
{ area: 'Verification', status: 'Partial', detail: 'Focused pair and staged group evidence includes real Nexus actions, funding after staged settled Truthtrance records, Guild transport, purchased-rate funding, Emperor and Fremen aid, worm protection, battle preparation and allied traitor votes; larger alliances and full combinations remain open.', evidence: ['tests/alliance-victory-option.test.ts', 'tests/short-alliance-victory-runtime.test.ts', 'tests/coalition-nexus-engine.test.ts', 'tests/coalition-shipment-funding.test.ts', 'tests/coalition-bid-funding.test.ts', 'tests/coalition-guild-shipment.test.ts', 'tests/coalition-guild-transport.test.ts', 'tests/coalition-fremen-worm.test.ts', 'tests/coalition-emperor-aid.test.ts', 'tests/coalition-fremen-revival.test.ts', 'tests/coalition-battle-preparation.test.ts', 'tests/coalition-battle-power-question.test.ts', 'tests/coalition-harkonnen-traitor.test.ts', 'tests/coalition-traitor-declarations.test.ts'] },
|
||||
],
|
||||
related: [
|
||||
'mentat',
|
||||
|
||||
@@ -104,6 +104,19 @@ void test('each donor share of a leading group bid remains locked until outbid',
|
||||
assert.equal(spice(game), 32);
|
||||
});
|
||||
|
||||
void test('fulfilled and released bid promises leave group bid funding usable', () => {
|
||||
for (const history of ['settled', 'prior-turn'] as const) {
|
||||
const initial = fixture();
|
||||
initial.phaseBidPromises = [{event:`bid-${history}`, player:'d', asker:'a',
|
||||
turn:history === 'prior-turn' ? initial.turn - 1 : initial.turn,
|
||||
minimum:1, answer:true, ...(history === 'settled' ? {fulfilled:true} : {released:true})}];
|
||||
const game = act(pledge(initial), 'a', groupBid);
|
||||
assert.equal(game.auction?.bidder, 'a');
|
||||
assert.deepEqual(game.auction?.coalitionContributions, groupBid.coalitionContributions);
|
||||
assert.deepEqual(game.phaseBidPromises, initial.phaseBidPromises);
|
||||
}
|
||||
});
|
||||
|
||||
void test('only the bidder sees the combined group payment, without raw donor allocations', () => {
|
||||
const game = act(pledge(fixture()), 'a', groupBid);
|
||||
const own = viewGame(game, 'a').auction; assert.ok(own);
|
||||
|
||||
@@ -65,6 +65,45 @@ void test('native pair expands only after three separate group approvals', () =>
|
||||
assert.deepEqual(game.ready, []);
|
||||
});
|
||||
|
||||
void test('fulfilled and released Truthtrance records do not prevent group Nexus negotiation', () => {
|
||||
for (const history of ['settled', 'prior-turn'] as const) {
|
||||
const game = originalPair();
|
||||
// Stage only the public history; pair, proposal and answers are real actions.
|
||||
game.bribePromises = [{event: `bribe-${history}`, player:'d', asker:'a',
|
||||
turn:history === 'prior-turn' ? game.turn - 1 : game.turn,
|
||||
recipient:'c', minimum:1, answer:true, progress:history === 'settled' ? 1 : 0,
|
||||
...(history === 'settled' ? {fulfilled:true} : {released:true})}];
|
||||
game.alliancePromises = [{event:`alliance-${history}`, source:'old-nexus',
|
||||
player:'d', asker:'a', turn:game.turn - 1, partner:'c', answer:false, fulfilled:true}];
|
||||
assert.ok(viewGame(game, 'a').coalitionNexus, 'Old answers do not hide the group controls.');
|
||||
let formed = propose(game);
|
||||
formed = answer(formed, 'b'); formed = answer(formed, 'c');
|
||||
assert.deepEqual(coalitionOf(formed.players, formed.coalitionState, 'a')!.members, ['a','b','c']);
|
||||
assert.deepEqual(formed.bribePromises, game.bribePromises);
|
||||
assert.deepEqual(formed.alliancePromises, game.alliancePromises);
|
||||
}
|
||||
});
|
||||
|
||||
void test('an unresolved prior-turn Truthtrance record still blocks group Nexus', () => {
|
||||
const game = originalPair();
|
||||
game.bribePromises = [{event:'unreconciled-bribe', player:'d', asker:'a', turn:game.turn - 1,
|
||||
recipient:'c', minimum:1, answer:true, progress:0}];
|
||||
assert.equal(viewGame(game, 'a').coalitionNexus, null);
|
||||
const pair = coalitionOf(game.players, game.coalitionState, 'a')!;
|
||||
rejected(game, 'a', {type:'coalitionPropose', groupId:pair.id, entrant:'c',
|
||||
expectedRevision:game.coalitionState!.revision});
|
||||
});
|
||||
|
||||
void test('a current unanswered group-incompatible Truthtrance commitment still blocks group Nexus', () => {
|
||||
const game = originalPair();
|
||||
game.bribePromises = [{event:'live-bribe', player:'d', asker:'a', turn:game.turn,
|
||||
recipient:'c', minimum:1, answer:true, progress:0}];
|
||||
assert.equal(viewGame(game, 'a').coalitionNexus, null);
|
||||
const pair = coalitionOf(game.players, game.coalitionState, 'a')!;
|
||||
rejected(game, 'a', {type:'coalitionPropose', groupId:pair.id, entrant:'c',
|
||||
expectedRevision:game.coalitionState!.revision});
|
||||
});
|
||||
|
||||
void test('any participant may decline and the original pair remains unchanged', () => {
|
||||
const original = originalPair();
|
||||
const pending = propose(original);
|
||||
|
||||
@@ -101,6 +101,54 @@ void test('two allies fund one ordinary shipment with donor-specific settlement
|
||||
assert.equal(spiceIncludingEscrow(game), originalSpice);
|
||||
});
|
||||
|
||||
void test('fulfilled and released Truthtrance records leave group shipment funding usable', () => {
|
||||
for (const history of ['settled', 'prior-turn'] as const) {
|
||||
const initial = fixture();
|
||||
initial.bribePromises = [{event:`bribe-${history}`, player:'d', asker:'a',
|
||||
turn:history === 'prior-turn' ? initial.turn - 1 : initial.turn,
|
||||
recipient:'c', minimum:1, answer:true, progress:history === 'settled' ? 1 : 0,
|
||||
...(history === 'settled' ? {fulfilled:true} : {released:true})}];
|
||||
let game = pledge(initial);
|
||||
assert.equal(viewGame(game, 'a').coalitionFunding?.incoming.length, 2);
|
||||
game = finishResponses(send(game, 'a', shipment));
|
||||
assert.equal(game.players[0].forces['imperial_basin:9'], 3);
|
||||
assert.equal(remaining(game, 'b'), 1);
|
||||
assert.equal(remaining(game, 'c'), 1);
|
||||
assert.deepEqual(game.bribePromises, initial.bribePromises);
|
||||
conservedForces(game);
|
||||
}
|
||||
});
|
||||
|
||||
void test('an unreconciled prior-turn commitment blocks group shipment funding', () => {
|
||||
const game = fixture();
|
||||
game.bribePromises = [{event:'unreconciled-bribe', player:'d', asker:'a', turn:game.turn - 1,
|
||||
recipient:'c', minimum:1, answer:true, progress:0}];
|
||||
assert.equal(viewGame(game, 'a').coalitionFunding, null);
|
||||
const before = structuredClone(game);
|
||||
assert.throws(() => send(game, 'b', {type:'coalitionPledge', recipient:'a', amount:4}));
|
||||
assert.deepEqual(game, before);
|
||||
});
|
||||
|
||||
void test('a live No bribe-expression commitment blocks group shipment funding', () => {
|
||||
const game = fixture();
|
||||
game.bribeExpressionPromises = [{event:'live-expression-no', player:'d', asker:'a',
|
||||
turn:game.turn, claim:{kind:'payment', recipient:'c', minimum:1}, answer:false, progress:[]}];
|
||||
assert.equal(viewGame(game, 'a').coalitionFunding, null);
|
||||
const before = structuredClone(game);
|
||||
assert.throws(() => send(game, 'b', {type:'coalitionPledge', recipient:'a', amount:4}));
|
||||
assert.deepEqual(game, before);
|
||||
});
|
||||
|
||||
void test('a current unsupported Truthtrance commitment still blocks group shipment funding', () => {
|
||||
const game = fixture();
|
||||
game.bribePromises = [{event:'live-bribe', player:'d', asker:'a', turn:game.turn,
|
||||
recipient:'c', minimum:1, answer:true, progress:0}];
|
||||
assert.equal(viewGame(game, 'a').coalitionFunding, null);
|
||||
const before = structuredClone(game);
|
||||
assert.throws(() => send(game, 'b', {type:'coalitionPledge', recipient:'a', amount:4}));
|
||||
assert.deepEqual(game, before);
|
||||
});
|
||||
|
||||
void test('fake or insufficient donor contributions reject without payment or arrival', () => {
|
||||
const game = pledge(fixture());
|
||||
for (const contributions of [
|
||||
|
||||
Reference in new issue
Block a user