Recognize group Guild shipment promises
Verify and publish checkpoint container / container (push) Canceled after 0s

This commit is contained in:
Quintine committed 2026-10-29 03:50:33 +11:00
1 parent 6eb417ff32
commit 44e7cf8001
4 files changed
+145 -7

No files matched your search

+9 -6
View File
@@ -17,6 +17,15 @@ open mode gates.
## Current checkpoint and work
**Group Fremen–Guild shipment promise — 29 October. Prototyped / Partial.**
In a staged Basic group, the Truthtrance shipment search now recognizes a legal
Fremen reserve shipment through its Guild ally. A focused journey forms the
group through Nexus, stages Movement, then completes the real question and
paid shipment; all four bot profiles have a legal path. Twenty-five related
cases, typecheck, lint and the production build pass. Group lobby admission
and full rules acceptance remain open.
[Scope](OPTIONAL_ALLIANCE_VICTORY.md#group-fremenguild-shipment-answer--29-october-2026).
**Truthtrance card types — 29 October. Prototyped / Partial.** Verified
current-hand questions can distinguish primary projectile and poison weapons
and primary projectile and poison defenses. Physical hybrid cards count for
@@ -25,12 +34,6 @@ focused cases pass, including human and bot answers; typecheck, lint and the
production build pass. Full Truthtrance and mode acceptance remain open.
[Scope](TRUTHTRANCE_HAND_INVENTORY.md#primary-attack-and-defense-types--29-october-2026).
**Group-alliance lobby remains unexposed.** A source inspection found that
the Basic shipment-promise search checks the scalar Fremen–Guild alliance
even though a three-member coalition clears that scalar. The native transport
permission has a group-aware path. This is a candidate legal-action gap to
reproduce and resolve before exposing group play; no group lobby was added.
**Whole-board Truthtrance forces — 29 October. Prototyped / Partial.**
Players can ask a verified physical-force question across the current Arrakis
board. The room's current locations include a placed Mobile Stronghold and
+19
View File
@@ -1,5 +1,24 @@
# Optional three-stronghold alliance victory
## Group Fremen–Guild shipment answer — 29 October 2026
**Prototyped / Partial.** In the staged Basic group, a Fremen member can
answer Yes to a Truthtrance reserve-shipment question when its actual Guild
ally can transport the group to the named territory. The completion search
uses the same group-aware native Guild permission as the transport itself,
including its cancellation state, rather than relying on the pair-only ally
field cleared by a larger coalition. The printed transport, two native Karama
response windows, actual cost and physical force transfer remain unchanged.
The [focused journey](../tests/coalition-fremen-guild-truth-shipment.test.ts)
forms a group through actual Nexus actions after staging an empty ledger; it
then stages the Movement phase, conserved force pools and wallets and transfers
the actual Truthtrance and Karama from the undealt deck. It verifies the legal
Guild route to Carthag, a real question and fulfillment, and minimal legal
responses across four bot profiles. Group consent and timing are provisional
online interpretations; this does not expose a lobby option or certify full
group play, Advanced combinations or other shipment continuations.
## Self-funded Guild transport with a live promise — 29 October 2026
In staged Basic and Advanced group games, a Guild ally's transport quote now
+1 -1
View File
@@ -32347,7 +32347,7 @@ function findShipmentCompletion(
if (!(error instanceof RuleError)) throw error;
}
}
if (p.faction === 'fremen' && byFaction(g, 'guild')?.id === p.ally) {
if (p.faction === 'fremen' && nativeGuildTransportPermission(g, p)) {
const action: Action = {
type: 'guildShip',
from: 'reserves',
@@ -0,0 +1,116 @@
import assert from 'node:assert/strict';
import { test } from 'node:test';
import { baseDeck } from '../game/cards';
import { botActions } from '../game/bots';
import { coalitionOf, emptyCoalitionState } from '../game/coalition-alliance';
import { applyAction, createGame, newPlayer, viewGame, type Action, type Game } from '../game/engine';
const act = (game: Game, id: string, action: Action) => applyAction(game, id, action);
const player = (game: Game, id: string) => game.players.find(seat => seat.id === id)!;
const shipment: Action = {type:'guildShip', from:'reserves', territory:'carthag', sector:11, amount:3, elite:0, allyPayment:0};
/** The unexposed group mode requires a staged empty ledger and later Movement
* phase; the pair and third member join using actual Nexus actions. Forces and
* wallets are staged with physical totals intact, and the actual Truthtrance
* card is transferred from the undealt deck. No saved room is touched. */
function fixture() {
let game = createGame('GROUPFREMENSHIP', newPlayer('g', 'Guild', 'guild'), false);
game.players.push(newPlayer('e', 'Emperor', 'emperor'), newPlayer('f', 'Fremen', 'fremen'),
newPlayer('h', 'Harkonnen', 'harkonnen'));
Object.assign(game, {status:'playing', phase:1, turn:2, nexus:true, order:['g','e','f','h'],
ready:[], storm:18, deck:baseDeck(), response:null, decision:null, phaseOpening:null});
game.coalitionState = emptyCoalitionState();
game = act(game, 'g', {type:'alliance', target:'e'});
game = act(game, 'e', {type:'alliance', target:'g'});
const pair = coalitionOf(game.players, game.coalitionState, 'g'); assert.ok(pair);
game = act(game, 'g', {type:'coalitionPropose', groupId:pair.id, entrant:'f',
expectedRevision:game.coalitionState!.revision});
for (const id of ['e','f']) {
const proposal = game.coalitionState!.proposal!;
game = act(game, id, {type:'coalitionAnswer', event:proposal.event, nexus:proposal.nexus,
turn:proposal.turn, accept:true, expectedRevision:game.coalitionState!.revision});
}
assert.equal(player(game,'f').ally, null, 'A three-member group does not use the pair-only scalar.');
Object.assign(game, {phase:5, nexus:false, active:'f', ready:[],
movementRemaining:['f','g','e','h'], guildTimingGranted:false, guildTimingLocked:false});
for (const seat of game.players) {
seat.forces = {}; seat.reserves = 20; seat.tanks = 0; seat.spice = 10;
seat.hand = []; seat.traitors = []; seat.traitorChoices = []; seat.shipped = false; seat.moved = 0;
}
const index = game.deck.findIndex(card => card.effect === 'truthtrance'); assert.ok(index >= 0);
const [truth] = game.deck.splice(index,1); player(game,'h').hand.push(truth);
// A distinct undealt Karama gives the outside player a real opportunity to
// counter the native permission and rate; it stays held when passed.
const karama = game.deck.findIndex(card => card.effect === 'karama'); assert.ok(karama >= 0);
player(game,'h').hand.push(...game.deck.splice(karama,1));
return {game, truth};
}
function settle(game: Game): Game {
for (let n = 0; game.response && n < 32; n++) {
const seat = game.players.find(candidate => !game.response!.passed.includes(candidate.id)); assert.ok(seat);
game = act(game,seat.id,{type:'passResponse'});
}
assert.equal(game.response ?? null,null);
return game;
}
function ask() {
const {game:before, truth} = fixture();
let game = act(before,'h',{type:'card',card:truth.id});
while (game.truthtrance?.stage === 'priority') {
const seat = game.players.find(candidate => !game.truthtrance!.passed.includes(candidate.id)); assert.ok(seat);
game = act(game,seat.id,{type:'truthPass'});
}
game = act(game,'h',{type:'truthAsk',question:{kind:'shipment',target:'f',turn:game.turn,
territory:'carthag',minimum:3}});
assert.equal(game.truthtrance?.stage,'answer');
return {before,game,truth};
}
void test('group-allied Fremen can actually ship from reserves to Carthag with native Guild permission', () => {
let {game} = fixture();
// Carthag is outside the Fremen southern reinforcement area. The native
// group-aware Guild path is the real legal transport for this location.
assert.throws(() => act(game,'f',{type:'ship',territory:'carthag',sector:11,amount:3,elite:0}));
game = act(game,'f',shipment);
assert.equal(game.response?.kind,'guildTransportPermission');
assert.equal(player(game,'f').reserves,20);
game = settle(game);
assert.equal(player(game,'f').forces['carthag:11'],3);
assert.equal(player(game,'f').reserves,17);
assert.equal(player(game,'f').spice,8);
assert.equal(player(game,'g').spice,12);
});
void test('a group-allied Fremen can answer Yes to the real Carthag reserve-shipment question and fulfill it', () => {
const {game:asked,truth} = ask();
assert.ok(viewGame(asked,'f').truthShipmentAnswers?.includes('yes'));
let game = act(asked,'f',{type:'truthAnswer',answer:'yes'});
assert.ok(game.shipmentPromises?.some(promise => promise.player === 'f' && promise.answer && !promise.released));
assert.equal(game.discard.filter(card => card.id === truth.id).length,1);
assert.throws(() => act(game,'f',{type:'endMovement'}),/Truthtrance/i);
game = act(game,'f',shipment);
assert.equal(game.response?.kind,'guildTransportPermission');
game = settle(game);
assert.equal(player(game,'f').forces['carthag:11'],3);
assert.equal(player(game,'f').spice,8);
assert.equal(player(game,'g').spice,12);
assert.ok(game.shipmentPromises?.some(promise => promise.player === 'f' && promise.fulfilled && !promise.released));
});
void test('all four bot profiles can answer and follow the original group Guild shipment witness', () => {
const {game:asked} = ask();
for (const difficulty of ['Easy','Medium','Hard','Brutal'] as const) {
const answerView = viewGame(asked,'f');
answerView.players.find(seat => seat.id === 'f')!.bot = difficulty;
const answer = botActions(answerView).find(action => action.type === 'truthAnswer'); assert.ok(answer);
assert.equal(answer.answer,'yes');
let game = act(structuredClone(asked),'f',answer);
const movementView = viewGame(game,'f');
movementView.players.find(seat => seat.id === 'f')!.bot = difficulty;
const next = botActions(movementView)[0]; assert.equal(next?.type,'guildShip');
assert.equal(next.from,'reserves'); assert.equal(next.territory,'carthag');
game = act(game,'f',next);
game = settle(game);
assert.ok(game.shipmentPromises?.some(promise => promise.player === 'f' && promise.fulfilled && !promise.released));
}
});