diff --git a/components/game-table.tsx b/components/game-table.tsx
index 4bd5b9df..f8c0e79a 100644
--- a/components/game-table.tsx
+++ b/components/game-table.tsx
@@ -3318,8 +3318,10 @@ export function GameTable({
{g.guildShipmentStopPromiseBlock}
} > ) : g.decision.kind === 'guildShipment' ? ( <> @@ -3351,9 +3353,11 @@ export function GameTable({ type: 'card', mode: 'special', card: c.id, - })} + }, !!g.guildShipmentStopPromiseBlock, + g.guildShipmentStopPromiseBlock ? 'guild-stop-promise-guidance' : undefined)} ))} + {g.guildShipmentStopPromiseBlock &&{g.guildShipmentStopPromiseBlock}
} > ) : g.decision.kind === 'handExchange' ? ( <> diff --git a/components/truthtrance.tsx b/components/truthtrance.tsx index 4e631adb..16525825 100644 --- a/components/truthtrance.tsx +++ b/components/truthtrance.tsx @@ -354,11 +354,15 @@ export function Truthtrance({ Includes Fremen reinforcements and Guild transport from southern reserves. Ground movement and transport of forces already on the board do not count. + {' '}Guild interception and Karama rate responses resolve + before delivery. A stopped shipment can release an + impossible answer; an unfinished response does not fulfill it. {!shipmentAvailable && (
Automatic enforcement currently supports the active
- player’s unused ordinary shipment in base rules or a
+ player’s unused ordinary shipment in Basic or Advanced
+ base rules, including the Guild, or a
classic payer in the Richese Semuta profile. Select
that player and finish pending decisions first.
Native No-Field shipments and other rules configurations
diff --git a/docs/CURRENT_STATUS.md b/docs/CURRENT_STATUS.md
index 923bbff2..81454eef 100644
--- a/docs/CURRENT_STATUS.md
+++ b/docs/CURRENT_STATUS.md
@@ -16,6 +16,27 @@ open mode gates.
## Current checkpoint and work
+**Truthtrance with Advanced Guild — 18 October. Prototyped / Partial.** Base
+Advanced questions include Guild without another profile/module opening. Native
+interception, permission and rate responses retain the promise until delivery or
+resolved opposition. A stop settles the whole expression against no shipment;
+self-interception cannot evade a required shipment. Executable guidance waits
+for pending controls, and owned alternatives survive an unaffordable repricing.
+
+One disjoint fixture lane, bounded continuation review and owner integration pass
+**10 files106/106**, one updated existing in-memory scope case, types/lint/build,
+four original-action/rendered-table programmes and four finite legal policy
+continuations. Public classic Advanced setup actually deals original unopened
+Truthtrance/Karamas; native TAKE/WAIT creates the unused shipment in39 source
+actions. Emperor typed3 costs3 or opposing stop costs/delivers0; Guild3 costs
+half2 or actual opposing-Karama full3. Eight-to-ten continuations include the
+respondent's finished turn. Four profiles make12 legal answer/ship/Guild proposals.
+Controlled No/mixed/self-stop/price/permission probes remain separate. Frozen
+feedback removed one unused fixture import; no rule changes followed. No broad
+recovery audit, browser/visual/deployed or full-mode acceptance. No saved-room/
+database/reset/restart/deployment operation.
+[Canonical source, behavior and exact proof](TRUTHTRANCE_SHIPMENT_PROMISES.md#advanced-guild-interception-and-rates--18-october-2026).
+
**Residual Poison / Advanced Harkonnen — 18 October. Prototyped / Partial.**
The card now includes available ordinary captives, captured foreign Gholas and
the shared Duke. One selected public death pays no bounty; other living
@@ -5990,7 +6011,7 @@ These links define bounded working behavior, not complete module certification.
| Leader Skills | [Common lifecycle](LEADER_SKILLS_RUNTIME.md), [known skilled capture](LEADER_SKILLS_CAPTURE.md), [Mentat question preview](MENTAT_QUESTION.md), [Bureaucrat payments](BUREAUCRAT_PAYMENTS.md), five battle disciplines, [Planetologist](PLANETOLOGIST_RULES.md), [Suk Graduate](SUK_GRADUATE_RULES.md), [Rihani and other battle effects](LEADER_BATTLE_EFFECTS.md), [Smuggler shipment](SMUGGLER_SHIPMENT.md), [No-Field](SMUGGLER_NO_FIELD.md) and [battle collection](SMUGGLER_BATTLE.md), [Sandmaster routes](SANDMASTER_MOVEMENT.md) and [worm rides](SANDMASTER_WORM.md), [Banker spending](SPICE_BANKER_RUNTIME.md), [Diplomat defense](DIPLOMAT_DEFENSE.md) and [bounded retreat](LEADER_SKILLS_RULES.md#bounded-diplomat-retreat-interpretation). Remaining bands and combinations are explicit in those contracts. |
| Discoveries | [Prototype](DISCOVERY_PROTOTYPE.md): genuine setup, Great Maker, seven cards/eight tokens, inspection and stash rewards, nested sites, signed later free entry, carried Ornithopter, sole Cistern, bounded Orgiz transfers, Jacurutu income, Testing Station and Shrine. Orgiz's per-deposit interpretation, contested/shared benefits and mixed physical allocation remain provisional or pending. The Orgiz path in current source is not yet deployed. |
| Nexus/Homeworlds | [Decision index](RULE_DECISIONS.md) links each integrated faction family, native/borrowed effects, physical custody, private choices, payments and transport. [CHOAM Secret Ally trade and bounded after-victory inspection](NEXUS_CHOAM_SECRET_ALLY.md), [Moritani Secret Ally losing-card retention](NEXUS_MORITANI_RULES.md#secret-ally-losing-card-retention-prototype), [Ecaz private yes/no inquiry and bounded native Duke Cunning](NEXUS_ECAZ_RULES.md#bounded-native-cunning-duke-vidal), [Emperor extra revival and bounded bank-auction purchase](NEXUS_EMPEROR_SECRET_ALLY_RUNTIME.md) are connected. Uniform CHOAM winner/Moritani loser windows, Ecaz inquiry scope/audience and Ecaz Cunning's Battle-only window are product inferences, not publisher rulings. Source audits or helpers alone do not certify full module games. |
-| Truthtrance | [Spice facts](TRUTHTRANCE_SPICE_FACTS.md), [card counts](TRUTHTRANCE_CARD_COUNT.md), [hand inventory](TRUTHTRANCE_HAND_INVENTORY.md), [recorded knowledge](TRUTHTRANCE_KNOWLEDGE.md), and [Basic/no-Guild Advanced reserve-shipment promises](TRUTHTRANCE_SHIPMENT_PROMISES.md) use authoritative private state. Earlier readiness audits do not supersede these follow-ups. |
+| Truthtrance | [Spice facts](TRUTHTRANCE_SPICE_FACTS.md), [card counts](TRUTHTRANCE_CARD_COUNT.md), [hand inventory](TRUTHTRANCE_HAND_INVENTORY.md), [recorded knowledge](TRUTHTRANCE_KNOWLEDGE.md), and [base Basic/Advanced reserve-shipment promises, including Guild](TRUTHTRANCE_SHIPMENT_PROMISES.md) use authoritative private state. Earlier readiness audits do not supersede these follow-ups. |
| Other cards | [Sapho runtime](JUICE_OF_SAPHO_RUNTIME.md) includes first among remaining unstarted movement turns after Advanced Guild has finished. [Recruits](RECRUITS_RUNTIME.md) connects clean Revival play; [Harass & Withdraw](HARASS_WITHDRAW_RUNTIME.md) and [Reinforcements](REINFORCEMENTS_RUNTIME.md) connect bounded battle effects in the same independent three-card preview. Richese card contracts and the checklist identify other connected effects and explicit gaps. |
| Multiplayer | [Public and private discussion](TABLE_DISCUSSION.md), saved rooms/seats, [recovery](../README.md#saved-seats-and-reconnecting), uncertain-request retry, [own-seat AI](AUTOPILOT.md), [named-player AI permission](SEAT_AI_PERMISSION.md) and [voluntary seat handover](SEAT_HANDOVER.md) have connected controls and recovery evidence. |
diff --git a/docs/IMPLEMENTATION_STATUS.md b/docs/IMPLEMENTATION_STATUS.md
index cb7727c1..de1e1c64 100644
--- a/docs/IMPLEMENTATION_STATUS.md
+++ b/docs/IMPLEMENTATION_STATUS.md
@@ -1,5 +1,25 @@
# Dune implementation status
+## 18 October 2026 — Truthtrance with native Advanced Guild
+
+Base Advanced shipment promises now include Guild. Native responses remain
+pending until actual delivery or resolved opposition; stopped absence fulfills
+matching whole claims and releases the rest. Server legality blocks voluntary
+self-interception evasion, controls show its reason, and executable guidance
+waits for clean native windows. Other profile/module gates remain.
+
+A disjoint original-action fixture lane, bounded continuation review and owner
+integration pass10-file106/106, one updated existing in-memory scope case,
+types/lint/build and four gameplay/rendering programmes plus four finite policy
+continuations. Both source setups use39 genuine actions; typed Emperor3 pays3
+or opposing stop pays/delivers0, Guild3 pays half2 or actual Karama full3. Each
+finishes in8–10 continuations; four profiles make12 legal proposals. Controlled
+compound/alternative/route probes are separate. Only an unused fixture import
+needed frozen-feedback repair. [Canonical source and exact proof](TRUTHTRANCE_SHIPMENT_PROMISES.md#advanced-guild-interception-and-rates--18-october-2026).
+No broad recovery campaign, complete-mode or browser/deployed acceptance; final
+assurance remains due. No saved-room/database/reset/restart/deployment operation.
+
+
## 18 October 2026 — Residual Poison with native Harkonnen captives
Remove the Advanced Harkonnen and exceptional-custody card exclusions. All
diff --git a/docs/RULE_DECISIONS.md b/docs/RULE_DECISIONS.md
index 89a5fd9b..50083882 100644
--- a/docs/RULE_DECISIONS.md
+++ b/docs/RULE_DECISIONS.md
@@ -854,7 +854,7 @@ dedicated retrieved worked FAQs. [Contract and remaining limits](HOMEWORLD_OCCUP
| Purchased price with immune Junction | Keep the original offered half/full tariff and normal payees; no quarter price or bank-only overlay. Under the standing choose-recommendation instruction, accepting this source with a purchased grant requires explicit acknowledgment that it ends unused, with no card refund. Consume only upon actual delivery, not failed, stale, unfunded or deferred acceptance; the ordinary purchased route remains available. This is a conservative product composition, not a publisher worked example. | [Source immunity and selected choice](JUNCTION_TRANSPORT_RULES.md#purchased-price-and-immune-offer--15-october-2026), [original versus controlled proof](JUNCTION_TRANSPORT_RUNTIME.md#purchased-price-source-choice--15-october-2026) |
| Deferred shipment arrival | Quote unsupported ordinary arrivals before saving Guild interception. A validated legacy ordinary declaration may return unspent on the typed arrival error; corrupted declarations and special parent/card receipts are not silently cleared. This repairs continuation without deciding simultaneous reaction priority. | [Reproduction and bounded recovery](DEFERRED_SHIPMENT_ARRIVAL.md) |
| Native Ambassador own-arrival continuation | Under the standing choose-recommended-option instruction, reuse committed entry after its own Guild income, BG Intrusion and accompaniment controls. Existing callbacks put a legitimate accompanying BG fighter child before the deferred original entrant; each sees the then-live token, so declining the child preserves a later original offer and consuming it does not recreate a token. Advisors remain exempt. This child-first/live-token composition is provisional, not a located publisher priority ruling. Actual busy controls, unsupported paired/module guards and separate native rate-window admission remain distinct. | [Native original-action evidence and boundary](DEFERRED_SHIPMENT_ARRIVAL.md#native-own-arrival-continuations--15-october-2026) |
-| Truthtrance | Current facts are boolean disclosures, not promises to preserve resources. Structured Basic and base Advanced active-opportunity shipment promises bind within their documented scope (Advanced excludes Guild and optional modules); prose is not automatically enforced. | [Spice facts](TRUTHTRANCE_SPICE_FACTS.md), [card facts](TRUTHTRANCE_CARD_COUNT.md), [shipment promises](TRUTHTRANCE_SHIPMENT_PROMISES.md), [remaining audit](TRUTHTRANCE_NONBATTLE_READINESS_20260907.md) |
+| Truthtrance | Current facts are boolean disclosures, not promises to preserve resources. Structured Basic/base Advanced active-opportunity shipment promises include native Guild interception and rate/permission responses; no other profile/module opens. Opposing stops settle the whole claim against absent shipment; own stops cannot evade a promise. Executable guidance waits for native controls, and prose is not automatically enforced. | [Spice facts](TRUTHTRANCE_SPICE_FACTS.md), [card facts](TRUTHTRANCE_CARD_COUNT.md), [shipment promises](TRUTHTRANCE_SHIPMENT_PROMISES.md#advanced-guild-interception-and-rates--18-october-2026), [remaining audit](TRUTHTRANCE_NONBATTLE_READINESS_20260907.md) |
| Truthtrance hand categories | Count current physical hand custody by explicitly named primary role; alternate battle slots and faction powers do not reclassify cards. Only an aggregate answer is disclosed. | [Hand inventory](TRUTHTRANCE_HAND_INVENTORY.md) |
| Moritani Advanced assassination preview | After an eligible battle loss, a different opposing-faction Traitor Card may be revealed even if its leader is already dead (zero bounty). Face-up set-aside and one private replacement occur during Mentat; one use per faction. Normal-call duration, uniform hidden-eligibility UX and exceptional combinations remain explicit. | [Runtime/source contract](MORITANI_ASSASSINATE_LEADERS.md) |
| Moritani Extortion at Mentat | The user selected normal Terror placement/relocation first, including any Karama response; only then collect the reserved five bank spice and approach other players in storm order for a possible three-spice payment. Payment removes the token; all declines recover it with rotated hidden supply IDs. The relative placement timing is a user-selected interpretation, not a publisher ruling. | [Publisher sequence and selected timing](MORITANI_EXTORTION_RULES.md#mentat-ordering-selected-interpretation-not-a-publisher-ruling) |
diff --git a/docs/TRUTHTRANCE_SHIPMENT_PROMISES.md b/docs/TRUTHTRANCE_SHIPMENT_PROMISES.md
index d824a817..5c85771b 100644
--- a/docs/TRUTHTRANCE_SHIPMENT_PROMISES.md
+++ b/docs/TRUTHTRANCE_SHIPMENT_PROMISES.md
@@ -2,6 +2,10 @@
Original checkpoint, 7 September 2026; Advanced follow-up, 13 September 2026. The [14 September compound follow-up](COMPOUND_SHIPMENT_PROMISES.md) adds shipment-only AND/OR while retaining the boundaries below. This implements a bounded reserve-shipment future-action predicate. It does not certify complete Truthtrance, Advanced play or expansion interactions, and opens no new mode gate.
+The former Advanced Guild exclusion is superseded by the
+[18 October native Guild composition](#advanced-guild-interception-and-rates--18-october-2026).
+Earlier scope descriptions and verification totals remain historical.
+
## Authority and supported meaning
The [GF9 November 2020 FAQ, printed p8](https://www.gf9games.com/dune/wp-content/uploads/2020/11/Dune-FAQ-Nov-2020.pdf) provides the Emperor/Carthag example: a definite Yes binds the matching shipment while possible; No allows fewer forces, another destination or none. Current-turn commitments, unknown for outcomes beyond the respondent's control, storm-order priority and release upon impossibility are the publisher's rules. The independent reviewer retrieved official indexed text this checkpoint; direct PDF access returned 403. No tournament or other-edition rule was adopted.
@@ -64,3 +68,92 @@ The reviewed offline runner `/tmp/dune-shipment-fullgames.ts` uses the unchanged
All 20 games completed: 9,597 accepted actions, zero rejected candidates, stalls, exceptions or invariant failures; 94 actual setup actions and 441 actual continuations from serialized JSON. Thirty shipment questions produced 23 Yes and seven No; all 30 records were fulfilled, with no pending or released promises. Yes occurred at every difficulty; the sampled No answers occurred at Hard and Brutal. Full-game thresholds here are minimum one; the six-force, all-minimum and preparation-path coverage comes from focused tests and browser play. This homogeneous sample cannot calibrate relative AI strength or certify the remaining game scope.
The root independently reviewed the runner and reread all 57 source/runner hashes with zero changes. Combined SHA-256: `e6c4c008d059934b8519d06b01c2bc15c9aaec40270cdd78b23a97bb6a70ca2c`. Results `/tmp/dune-shipment-fullgames.json`, SHA-256 `62f6d214b3b17f661497c65159534d732e710873f04fa407a1d15079bfa135df`. The run took 48.392 seconds and its terminal exited successfully.
+
+## Advanced Guild interception and rates — 18 October 2026
+
+**Prototyped / Partial.** Base Advanced games now support the same structured
+reserve-shipment and mixed current-fact questions with Guild seated, including
+Guild itself and its reciprocal ally as respondent. Asking still requires the
+active player's unused shipment and finished control windows. Native TAKE/WAIT
+decides that active opportunity first. No faction expansion or other optional
+module is admitted by this change; existing Basic/Richese Semuta scope remains.
+
+The original FAQ's commitment/release rule composes with native Guild powers;
+no new numerical or timing ruling is introduced. A declaration awaiting native
+interception, transport permission or rate responses neither fulfills nor releases
+the promise. Actual delivery settles the actual reserve count/destination once,
+before its later arrival reactions. Physical force type, tariffs, bank/Guild
+income, original funding and one-use powers remain authoritative.
+
+An opposing Guild special Karama consumes the shipment opportunity without
+delivery or payment. Evaluate each complete accepted expression against that
+absence: matching answers fulfill; impossible answers publicly release. This
+also handles a Yes to an OR whose frozen current fact is already true. The
+respondent's promise cannot veto the opponent's printed stop. The Guild may not
+use self-interception to evade its own required shipment; shared server legality
+disables the stop button with its reason, while native allowance stays available.
+Own stops permitted by the whole promise retain the normal power and card cost.
+
+Rate cancellation remains separate from interception. A funded full tariff
+delivers and fulfills normally. An unaffordable original payment split returns
+the declaration unused, retains the full-rate restriction and rechecks available
+owned preparation. An independent retained Karama discount may keep a promise
+possible; without a legal completion, an opposing cancellation releases it. The
+respondent cannot deliberately spend its last completion away. Denied native
+cross/return permission moves nothing and consumes no shipment: ordinary Fremen
+reinforcement remains a separate possible fallback, while an unreachable
+destination releases. A purchased price does not restore denied route permission.
+
+Private suggested actions appear only at an executable clean boundary, not while
+Truthtrance, a decision, response, opening or physical shipment is pending. The
+reachability solver can preserve a possible allowed route during those windows
+without offering a concurrent duplicate action. Existing policies use the same
+private feasible answers and completion witness; no strategy tuning is included.
+
+### Targeted proof and limits
+
+A disjoint fixture lane, bounded continuation review and owner integration pass
+**10 files106/106**, one updated existing in-memory scope-regression case,
+`npm run check:quick`, production build and actual-rule/rendered-control smoke.
+Frozen feedback removed one unused fixture import. The old negative case that
+classified Advanced Guild itself as corruption is removed; the remaining
+unsupported-expansion case remains. Only that existing in-memory test runs,
+not a new recovery or production-save assurance campaign.
+
+The passing frozen source is based on
+`377e9c516035191a6d73f8eca05e253d31f39739`, tree
+`a1b4fe7440dbfeb2a664db86acf0813ca9711ff486c30e505131057f077792ee`,
+2,595 tracked/nonignored files. The preserved untracked source PDF contributes
+only its hash to the snapshot; it is not committed or republished.
+
+`tests/fixture-truthtrance-guild-shipment.ts` uses actual public classic Advanced
+setup, original physical starting deals and native TAKE/WAIT. Only unopened
+starting-deal/Spice positions and authoritative scalar lotteries are selected;
+no postdeal hand, wallet, army, clock, flag or promise is installed. Both sources
+use39 actions. Four programmes finish the actual respondent turn in8–10 more:
+
+- Emperor3, including one Sardaukar, is allowed for3 paid to Guild and fulfills;
+ actual opposing special Karama instead consumes the opportunity, pays and
+ delivers0, and publicly releases the impossible positive.
+- Guild TAKE ships3 for half2, or an actual opposing Emperor Karama removes that
+ rate and the funded full3 is paid once. Both deliveries fulfill.
+
+`tests/truthtrance-guild-shipment-engine.test.ts` covers these original controls.
+Four existing profiles make12 legal answer/shipment/Guild proposals and finish
+their resulting opportunity. `tests/truthtrance-guild-shipment.test.ts` separately
+uses controlled No/mixed, self-stop, funded/unaffordable rate, owned alternative
+and denied Fremen-route probes. The ordinary completion remains legal without
+strategic refinement. Existing compound, core Guild and shipment tests pass.
+
+Actual GameTable/ShipmentPromises render question, bound, interception, rate and
+finished states. The real self-stop button is disabled with the server reason;
+the executable guide is absent during pending controls. Framework aliases/CSS
+stubs mean no browser click or visual acceptance. Private logs, snapshots, HTML
+and runtime results are in
+`/home/quintine/.local/share/dune/checkpoints/20261018-truth-guild-shipment/`;
+`frozen-integrated-2/report.json` is the complete passing batch. Final prose and
+reference evidence edits are checked against all frozen file hashes before push.
+
+Broader phases/routes/modules, full Truthtrance/modes, final assurance and
+browser/deployed acceptance remain open. No saved-room/database/reset/restart/
+deployment operation was performed.
diff --git a/game/engine.ts b/game/engine.ts
index 1815cdef..2b9274cf 100644
--- a/game/engine.ts
+++ b/game/engine.ts
@@ -22920,6 +22920,27 @@ function finishShipmentPromises(
))
promise.fulfilled = true;
}
+function ownGuildStopPromiseBlock(g: Game, p: Player): string | null {
+ const decision = g.decision;
+ if (p.faction !== 'guild' ||
+ (decision?.kind !== 'guildShipment' && decision?.kind !== 'homeworldShipmentGuild') ||
+ decision.shipper !== p.id || g.pendingShipment?.source === 'ambassador') return null;
+ try { checkShipmentPromises(g, p, null); return null; }
+ catch (error) { if (error instanceof RuleError) return error.message; throw error; }
+}
+/** An opposing Guild stop consumes the opportunity without a shipment. It
+ * fulfills statements matching that absence and releases impossible answers;
+ * the respondent's promise cannot veto the opponent's printed power. */
+function settleStoppedShipmentPromises(g: Game, p: Player, actor: string) {
+ if (actor === p.id) checkShipmentPromises(g, p, null);
+ for (const promise of liveShipmentPromises(g.shipmentPromises ?? [], p.id, g.turn)) {
+ if (matchesShipment(promise, null) === promise.answer) promise.fulfilled = true;
+ else {
+ promise.released = true;
+ log(g, `The Guild stopped ${p.name}’s shipment; that Truthtrance shipment answer is no longer binding.`);
+ }
+ }
+}
function reconcileShipmentPromises(
g: Game,
voluntary?: { actor: string; action: Action },
@@ -29875,6 +29896,8 @@ export function prepareSpecialKaramaIntent(
},
};
} else if (p.faction === 'guild') {
+ const promiseBlock = ownGuildStopPromiseBlock(g, p);
+ requireRule(!promiseBlock, promiseBlock ?? 'Honor your Truthtrance shipment answer.');
if (g.decision?.kind === 'homeworldShipmentGuild') {
requireRule(!g.response && g.decision.player === p.id,
'Wait for your Homeworld shipment interception decision.');
@@ -30236,7 +30259,7 @@ function resolveSpecialKaramaEffect(g: Game, p: Player, card: Card, intent: Spec
refundStoppedShipmentKarama(g, shipper);
g.pendingHomeworldShipment = null;
g.decision = null;
- finishShipmentPromises(g, shipper, null);
+ settleStoppedShipmentPromises(g, shipper, p.id);
log(g, `${p.name} used special Karama to stop ${shipper.name}’s interplanetary shipment. No forces arrive or spice is paid; their shipment is used and movement remains available.`);
} else if (intent.kind === 'guild') {
const shipper = getPlayer(g, intent.target);
@@ -30263,6 +30286,7 @@ function resolveSpecialKaramaEffect(g: Game, p: Player, card: Card, intent: Spec
g,
`${p.name} used special Karama to stop ${shipper.name}’s shipment. No forces arrive or spice is paid; movement remains available.`,
);
+ settleStoppedShipmentPromises(g, shipper, p.id);
} else if (
intent.kind === 'emperorLeader' ||
intent.kind === 'emperorForces'
@@ -35127,6 +35151,7 @@ export function viewGame(state: Game, id: string) {
battleOrder: projectedBattleOrder(g),
guildAmbassadorAdvisorChoices: guildAdvisorChoices(g, id),
truthShipmentSupported: shipmentPromiseModeSupported(g),
+ guildShipmentStopPromiseBlock: ownGuildStopPromiseBlock(g, me),
truthShipmentAnswers:
g.truthtrance?.stage === 'answer' &&
g.truthtrance.question?.target === id &&
@@ -35138,7 +35163,9 @@ export function viewGame(state: Game, id: string) {
shipmentPromises: (g.shipmentPromises ?? []).filter(p => !p.mixed || p.player === id),
shipmentCompletion:
!g.pendingTreacheryDiscard &&
- !g.response && !g.pendingKarama &&
+ !g.response && !g.pendingKarama && !g.truthtrance &&
+ !g.decision && !g.phaseOpening && !g.pendingShipment &&
+ !g.pendingGuildTransport && !g.pendingHomeworldShipment &&
liveShipmentPromises(g.shipmentPromises ?? [], id, g.turn).some(
(p) => matchesShipment(p,null) !== p.answer,
)
diff --git a/game/reference.ts b/game/reference.ts
index 10269b45..e7002989 100644
--- a/game/reference.ts
+++ b/game/reference.ts
@@ -2914,12 +2914,13 @@ export const RULE_TOPICS: RuleTopic[] = [
summary:
'Bind a shipment from reserves to a named territory with a minimum physical force count during the active supported shipment opportunity.',
steps: [
- 'Declare Truthtrance, select the active player and choose Bind a shipment from reserves. Choose a printed destination and minimum of one to twenty forces, or combine two shipment conditions using AND or OR. Each describes the same shipment. Automatic enforcement supports base Basic games and base Advanced games without Guild or optional modules, before that player ships and after pending decisions finish.',
+ 'Declare Truthtrance, select the active player and choose Bind a shipment from reserves. Choose a printed destination and minimum of one to twenty forces, or combine two shipment conditions using AND or OR. Each describes the same shipment. Automatic enforcement supports base Basic and base Advanced games, including Guild, before that active player ships and after pending decisions finish. Other optional configurations keep their existing boundaries.',
'Yes requires the complete statement to match while possible: all AND conditions or at least one OR condition. No requires the whole statement to be false: at least one AND condition false, or every OR condition false. A smaller count, different destination, transport of forces already on the board or no shipment may satisfy No. Forces already in the destination and ground movement do not satisfy the question. Fremen reinforcements and Guild transport from southern reserves do count.',
'The target privately sees feasible answers based on their own resources and earlier promises. Available Ghola revival, Karama discounts, incoming pledged aid and reclaiming their own unused pledge are included. Other players’ unplayed cards and future voluntary gifts are not assumed.',
'Accepted whole answers are public and constrain the event together with earlier promises. When an answer requires a shipment, honor it before ground movement or finishing and do not voluntarily spend away the last legal completion. A completed opposing effect that makes compliance impossible releases it publicly.',
+ 'Guild interception and rate or transport-permission responses finish before delivery. An opposing stop consumes the shipment without payment or arrival: answers matching no shipment fulfill and impossible answers release. You cannot stop your own shipment to evade your answer. An unaffordable canceled rate leaves shipment available; a retained owned discount or ordinary Fremen reinforcement may still provide a legal route.',
'Choose Mixed current fact and shipment for nested AND/OR groups of supported current facts and this-turn reserve shipments. Current facts freeze privately when answered: Yes OR a true current fact can allow skipping even after later losing that holding; No AND a false current fact can allow shipping even after that fact changes. Unknown is not false. Only the whole question and answer are public.',
- 'Your own promises show the original grouped question, not private compiled clause results. Optional private next-step guidance includes preparation, funding or shipment. All four legal policies use server-feasible choices and the same complete route. Earlier phases, Advanced Guild/other modules, expansion shipment modes, other future actions and arbitrary prose enforcement remain unfinished.',
+ 'Your own promises show the original grouped question, not private compiled clause results. Optional private next-step guidance includes preparation, funding or shipment, only after pending controls finish. All four legal policies use server-feasible choices and the same complete route. Earlier phases, other modules, expansion shipment modes, other future actions and arbitrary prose enforcement remain unfinished.',
],
example:
'Yes to at least six forces to Carthag and No to at least eight there leaves a shipment of six or seven. Moving existing forces into Carthag does not fulfill either shipment event.',
@@ -2929,7 +2930,7 @@ export const RULE_TOPICS: RuleTopic[] = [
area: 'Implementation',
status: 'Partial',
detail:
- 'Authoritative supported reserve-origin commitments, shipment and mixed current-fact AND/OR, frozen private clause values, joint legal preparation, actual shipment/skip, voluntary-spend guards and opposing release. Broader timing and expansion routes remain unfinished.',
+ 'Authoritative reserve commitments, mixed current-fact AND/OR, frozen clauses, owned preparation, actual shipment/skip and opposing release include base Advanced Guild interception, permission and rates. Self-stop cannot evade an answer; broader timing/modules remain unfinished.',
},
{
area: 'Player controls',
@@ -2953,7 +2954,7 @@ export const RULE_TOPICS: RuleTopic[] = [
area: 'Verification',
status: 'Partial',
detail:
- 'Focused rules, all-profile AI, privacy and production-room persistence/concurrency coverage. Full Truthtrance and Advanced/expansion acceptance remain unfinished.',
+ 'Current Guild batch passes106 rule cases plus one updated existing in-memory scope case, types/lint/build, four original-action/rendered-table programmes and four finite policy continuations. Earlier persistence/privacy evidence is historical; full Truthtrance, modes, final assurance and deployed acceptance remain open.',
evidence: [
'tests/shipment-promises.test.ts',
'tests/shipment-promises-bots.test.ts',
@@ -2964,6 +2965,10 @@ export const RULE_TOPICS: RuleTopic[] = [
'tests/compound-shipment-recovery.test.ts',
'tests/mixed-shipment-question.test.ts',
'tests/mixed-shipment-engine.test.ts',
+ 'tests/truthtrance-guild-shipment.test.ts',
+ 'tests/truthtrance-guild-shipment-engine.test.ts',
+ 'tests/fixture-truthtrance-guild-shipment.ts',
+ 'docs/TRUTHTRANCE_SHIPMENT_PROMISES.md',
],
},
],
diff --git a/game/shipment-promises.ts b/game/shipment-promises.ts
index 3d9d5fc2..d267f741 100644
--- a/game/shipment-promises.ts
+++ b/game/shipment-promises.ts
@@ -175,8 +175,7 @@ export function shipmentPromiseModeSupported(game: {
return (
!game.expansions.length &&
(!game.advanced ||
- (!game.players.some((player) => player.faction === 'guild') &&
- !game.techTokens &&
+ (!game.techTokens &&
!game.strongholdCards &&
!game.nexusCards &&
!game.homeworlds))
diff --git a/game/truthtrance.ts b/game/truthtrance.ts
index 310ed638..45a0a933 100644
--- a/game/truthtrance.ts
+++ b/game/truthtrance.ts
@@ -204,7 +204,7 @@ function parseQuestion(g: Game, asker: string, value: unknown): TruthQuestion {
!g.decision &&
!g.phaseOpening &&
!g.pendingShipment,
- 'Structured shipment promises require a supported active unused shipment: base rules or a classic payer in the Richese Semuta profile. Finish pending decisions first.',
+ 'Structured shipment promises require a supported active unused shipment: Basic or Advanced base rules, or a classic payer in the Richese Semuta profile. Finish pending decisions first.',
);
try {
if (v.kind === 'mixedShipment') {
diff --git a/tests/fixture-truthtrance-guild-shipment.ts b/tests/fixture-truthtrance-guild-shipment.ts
new file mode 100644
index 00000000..2fe882b7
--- /dev/null
+++ b/tests/fixture-truthtrance-guild-shipment.ts
@@ -0,0 +1,169 @@
+import assert from 'node:assert/strict';
+import { applyAction, createGame, joinGame, newPlayer, type Action, type Game } from '../game/engine';
+import { nextStrongholdFactionsNativeStep } from './fixture-stronghold-factions';
+import { withClassicDiscoveryNexusLottery } from './fixture-discovery-classic-nexus';
+
+export type TruthtranceGuildShipmentStep = { actor: string; action: Action };
+export const truthtranceGuildShipmentPlayer = (game: Game, actor = 'emperor') => {
+ const player = game.players.find(player => player.id === actor); assert.ok(player); return player;
+};
+export const truthtranceGuildShipmentClean = (game: Game) => !game.response && !game.phaseOpening &&
+ !game.decision && !game.truthtrance && !game.pendingTreacheryDiscard;
+export function actTruthtranceGuildShipment(game: Game, actor: string, action: Action,
+ actions: TruthtranceGuildShipmentStep[] = []): Game {
+ const next = withClassicDiscoveryNexusLottery(() => applyAction(game, actor, action));
+ actions.push({ actor, action: structuredClone(action) }); return next;
+}
+/** Only original native continuations. Questions, answers and shipment
+ * declarations remain explicit; ordinary responses allow their declared effect. */
+export function nextTruthtranceGuildShipmentStep(game: Game): TruthtranceGuildShipmentStep | null {
+ if (game.truthtrance) {
+ if (game.truthtrance.stage !== 'priority') return null;
+ const actor = game.players.find(player => !game.truthtrance!.passed.includes(player.id)); assert.ok(actor);
+ return { actor: actor.id, action: { type: 'truthPass' } };
+ }
+ if (!game.response && !game.phaseOpening && !game.pendingTreacheryDiscard) {
+ const decision = game.decision;
+ if (decision?.kind === 'guildTiming') return { actor: decision.player,
+ action: { type: 'decision', take: false } };
+ }
+ return nextStrongholdFactionsNativeStep(game);
+}
+export function advanceTruthtranceGuildShipment(game: Game, until: (state: Game) => boolean,
+ actions: TruthtranceGuildShipmentStep[] = []): Game {
+ for (let count = 0; count < 1800; count++) {
+ if (until(game)) return game;
+ assert.notEqual(game.status, 'finished', 'The original Truthtrance/Guild programme ended before its next control');
+ const step = nextTruthtranceGuildShipmentStep(game); assert.ok(step, 'Choose the original structured question and answer explicitly');
+ game = actTruthtranceGuildShipment(game, step.actor, step.action, actions);
+ }
+ throw Error('The original Truthtrance/Guild programme did not reach its finite boundary');
+}
+
+/** The actual public Advanced classic start deals the selected unopened
+ * Truthtrance to Atreides and distinct original Karamas to Emperor/Guild.
+ * Native TAKE/WAIT controls create the current unused reserve shipment.
+ * No postdeal card, resource, army, phase, timing flag or promise is installed. */
+export function createTruthtranceGuildShipmentFixture(options: { target?: 'emperor' | 'guild' } = {}) {
+ const target = options.target ?? 'emperor';
+ let game = createGame('TRUTHGUILDSHIPMENT', newPlayer('atreides', 'Atreides', 'atreides'), true);
+ for (const faction of ['emperor', 'guild'] as const) joinGame(game, newPlayer(faction, faction, faction));
+ const actions: TruthtranceGuildShipmentStep[] = [];
+ const act = (actor: string, action: Action) => { game = actTruthtranceGuildShipment(game, actor, action, actions); };
+ const reach = (until: (state: Game) => boolean) => { game = advanceTruthtranceGuildShipment(game, until, actions); };
+ for (const player of game.players) act(player.id, { type: 'ready' });
+ const lobby = structuredClone(game);
+ act(game.host, { type: 'start', advancedPreview: true });
+ const setup = structuredClone(game);
+ assert.equal(game.status, 'setup');
+ assert.ok(game.players.every(player => player.hand.length === 0));
+ const truth = game.deck.find(card => card.effect === 'truthtrance');
+ const karmas = game.deck.filter(card => card.effect === 'karama');
+ assert.ok(truth && karmas.length === 2);
+ const deal = [truth.id, karmas[0].id, karmas[1].id];
+ for (const [position, id] of deal.entries()) {
+ const from = game.deck.findIndex(card => card.id === id); assert.ok(from >= position);
+ [game.deck[position], game.deck[from]] = [game.deck[from], game.deck[position]];
+ }
+ const beforeStartingDeal = structuredClone(game);
+ reach(state => state.status === 'playing');
+ const afterSetup = structuredClone(game), front: Game['spiceDeck'] = [];
+ assert.ok(truthtranceGuildShipmentPlayer(game, 'atreides').hand.some(card => card.id === truth.id));
+ assert.ok(truthtranceGuildShipmentPlayer(game, 'emperor').hand.some(card => card.id === karmas[0].id));
+ assert.ok(truthtranceGuildShipmentPlayer(game, 'guild').hand.some(card => card.id === karmas[1].id));
+ for (let count = 0; count < 2; count++) {
+ const index = game.spiceDeck.findIndex(card => 'territory' in card && !card.discovery); assert.ok(index >= 0);
+ front.push(game.spiceDeck.splice(index, 1)[0]);
+ }
+ game.spiceDeck.unshift(...front);
+ reach(state => state.phase === 5 && !state.response && !state.phaseOpening && state.decision?.kind === 'guildTiming');
+ const timing: { before: Game; step: TruthtranceGuildShipmentStep; declared: Game; after: Game }[] = [];
+ const chooseTiming = (take: boolean) => {
+ assert.equal(game.decision?.kind, 'guildTiming');
+ const before = structuredClone(game), step: TruthtranceGuildShipmentStep = { actor: 'guild', action: { type: 'decision', take } };
+ act(step.actor, step.action);
+ const declared = structuredClone(game);
+ reach(truthtranceGuildShipmentClean);
+ timing.push({ before, step, declared, after: structuredClone(game) });
+ };
+ chooseTiming(target === 'guild');
+ let completedPrefix: { before: Game; step: TruthtranceGuildShipmentStep; after: Game } | null = null;
+ if (target === 'emperor') {
+ assert.equal(game.active, 'atreides');
+ const before = structuredClone(game), step: TruthtranceGuildShipmentStep = { actor: 'atreides', action: { type: 'endMovement' } };
+ act(step.actor, step.action);
+ completedPrefix = { before, step, after: structuredClone(game) };
+ reach(state => !state.response && !state.phaseOpening && state.decision?.kind === 'guildTiming');
+ chooseTiming(false);
+ }
+ assert.equal(game.active, target);
+ assert.equal(truthtranceGuildShipmentPlayer(game, target).shipped, false);
+ assert.equal(truthtranceGuildShipmentPlayer(game, target).moved, 0);
+ return { game, lobby, setup, beforeStartingDeal, afterSetup, timing, completedPrefix,
+ asker: 'atreides', target, guild: 'guild', rateCanceler: 'emperor', card: truth.id,
+ guildKarama: karmas[1].id, rateKarama: karmas[0].id, location: 'carthag:11',
+ sourceActionCount: actions.length, actions,
+ staging: ['Only the three unopened original starting-deal positions, original land Spice positions and scalar lotteries are selected. Public Advanced classic setup actually deals the physical Truthtrance and distinct Karamas; native Guild TAKE/WAIT and an optional explicit completed Atreides turn create the unused shipment. No expansions/modules or postdeal hands, resources, forces, clocks, timing flags or promise receipts are installed.'] };
+}
+export type TruthtranceGuildShipmentFixture = ReturnType