Verify native Advanced full-plan card interactions
Replace the Basic-then-flip battle fixture with actual Advanced setup and add five tests covering Truthtrance, Voice, ordinary Prescience, Ghola preparation, private inspection and interrupted decisions. No runtime changes were needed. Keep the special offer deadline and target-first ordering explicitly provisional. Update the development maintenance policy per the user's instruction: restart only when a change or observed server condition warrants it, preserving saves and verifying restoration. No hourly schedule or recurring automation. Validation: npm run check passed: types, lint and all 4,205 offline tests (124.980 seconds). Source fingerprint: 5792403ddfed7ea7f4e0b247d367e7c000dfce6c81e1553204d9c00db376c3f6. Private report: 20260913-full-plan-final/report.json. Earlier build and HTTP evidence are reused for unchanged runtime code. Independent source/test review found no actionable defect in this boundary. Browser AZC4HGLB covers human inspection and Medium AI Ghola preparation, desktop/phone readability and private refresh after the completed restart. The earlier inventory seat also restored; all 64 saved room versions/hashes remain unchanged. No mode or publication gate is opened. Aggregate goal counters: 59,151,671 at batch start to 59,509,237 after completed verification, or 357,566 tokens before final Git and new goal-steering overhead. One bounded workhorse helper reused for source and independent test review; one stable broad check. This is an audit batch, not a new gameplay capability.
This commit is contained in:
1 parent
7454456755
commit
4a0b1ee00a
9 files changed
+407
-24
No files matched your search
@@ -51,9 +51,11 @@ for the in-memory SQLite tests. `tsx` is a pinned development dependency.
|
||||
`docs/VERIFICATION_WORKFLOW.md`. Preserve all games created afterward.
|
||||
- Keep `.wrangler/state` and all saved games. Never reset a database to fix a
|
||||
test, migration, connection or gameplay problem. Migrations are additive.
|
||||
- Reuse a running dev server. Warn before interrupting human play, defer to a
|
||||
safe point, and verify restoration after a necessary restart. No recurring
|
||||
maintenance automation is installed; do not recreate it without a request.
|
||||
- Reuse a running dev server. Restart only when a code/configuration change or
|
||||
observed server condition warrants it; there is no hourly restart schedule.
|
||||
Warn before interrupting human play, defer to a safe point, and verify
|
||||
restoration after a necessary restart. No recurring maintenance automation
|
||||
is installed; do not recreate it without a request.
|
||||
- `.openai/hosting.json` identifies the existing Sites project. Preserve it.
|
||||
Follow the applicable Sites skills for site work; publication has existing
|
||||
completion and verification gates documented in `README.md`.
|
||||
|
||||
@@ -59,6 +59,6 @@ See [AI calibration](docs/AI_CALIBRATION.md), [multiplayer audit](docs/MULTIPLAY
|
||||
|
||||
The user-authorized 13 September 2026 local reset established a fresh saved-game baseline; see the [reset record](docs/VERIFICATION_WORKFLOW.md#authorized-reset-13-september-2026). Preserve all games created after that checkpoint.
|
||||
|
||||
During active development, preserve database state and perform a controlled server restart approximately hourly. Warn before interrupting human play and defer until a safe decision point. Verify server health and restoration afterward. No recurring automation is installed; the user requested leaving it removed. Fix causes of crashes and lost progress rather than relying on restarts.
|
||||
Reuse the running development server. Restart only when a code or configuration change, or an observed server condition, makes leaving it running likely to cause issues; use judgment rather than an hourly schedule. Preserve database state, warn before interrupting human play, and defer until a safe decision point. Verify server health and restoration after a necessary restart. No recurring automation is installed; the user requested leaving it removed. Fix causes of crashes and lost progress rather than relying on restarts.
|
||||
|
||||
The application bundles its artwork, fonts and rules guidance. Source provenance and third-party notices live in developer documentation; the player-facing reference uses internal links. Publication remains gated on completion and integrated verification of the requested rules, expansions and critical player journeys.
|
||||
@@ -0,0 +1,81 @@
|
||||
# Atreides full-plan interaction audit
|
||||
|
||||
13 September 2026. This audit verifies the existing native Advanced interaction
|
||||
paths without promoting an implementation timing choice to an official ruling.
|
||||
The public Advanced and unfinished expansion gates remain closed.
|
||||
|
||||
## Published requirements and remaining interpretation
|
||||
|
||||
The [GF9 base rulebook, printed p14](https://www.gf9games.com/dunegame/wp-content/uploads/Dune-Rulebook.pdf)
|
||||
gives Advanced Atreides a once-per-game Karama inspection of any one player's
|
||||
entire Battle Plan. Its Q&A p23 orders Voice before ordinary Prescience, permits
|
||||
Truthtrance during that interaction and leaves unaffected plan components
|
||||
changeable. The Q&A p22 permits a Ghola-revived leader to fight during Battle.
|
||||
|
||||
The [November 2020 FAQ, pp8–9](https://www.gf9games.com/dune/wp-content/uploads/2020/11/Dune-FAQ-Nov-2020.pdf)
|
||||
confirms any-time Truthtrance, current-turn commitments with impossibility release,
|
||||
and the Battle-phase Ghola use. The independent source audit retrieved official
|
||||
indexed text for these passages.
|
||||
|
||||
Neither retrieved source orders special full-plan inspection against ordinary
|
||||
Voice/Prescience, fixes the special offer's exact deadline, or expressly chooses
|
||||
which combatant seals first. This implementation offers it after preparation,
|
||||
before either plan is sealed, and requires the selected target to seal first.
|
||||
Ghola cannot be played during the pending offer; it can be played after that
|
||||
decision resolves. These remain explicit implementation choices. The older WBC
|
||||
wording supporting target-first commitment is outside the requested GF9 authority
|
||||
and was not adopted as a publisher ruling. Preserve the existing unresolved
|
||||
interpretation instead of silently enabling Advanced on the strength of tests.
|
||||
|
||||
## Verified interaction boundary
|
||||
|
||||
`tests/full-plan.test.ts` now uses `initializeBaseGameForAudit` and completes actual
|
||||
Advanced prediction, traitor and BG advisor setup before staging a battle. It no
|
||||
longer starts Basic and flips the mode afterward. A staged scenario remains
|
||||
distinct from a setup-to-finish Advanced game.
|
||||
|
||||
The 17 focused tests include five new interaction tests:
|
||||
|
||||
- All four profiles can honor a Truthtrance leader/weapon commitment, preserve
|
||||
Voice and ordinary Prescience, revive the required leader with one physical
|
||||
Ghola, seal for inspection and reach public reveal after the other plan seals.
|
||||
- A question during the special offer preserves the pending choice and its
|
||||
revival-dependent answers, whether Atreides later uses or declines the special.
|
||||
- Questions after inspection disclose only the truthful result of the sealed
|
||||
plan. They cannot reopen fields, duplicate a binding record or spend its support.
|
||||
- Seven native preparation windows survive a Truthtrance interrupt: Voice
|
||||
selection/response, Prescience selection/response/answer, full-plan offer and
|
||||
selected-special target wait. Parent response, decision, preparation and plan
|
||||
state are retained; named restrictions leave other fields selectable.
|
||||
- Atreides can revive its own leader after reading and before sealing, while
|
||||
the target's plan remains unchanged and private.
|
||||
|
||||
Existing tests retain owner-only inspection, sealed-card restrictions, ordinary
|
||||
Prescience cancellation, outsiders and once-per-game custody. JSON continuation,
|
||||
rejected-action immutability and physical-card conservation are asserted in the
|
||||
new paths. No runtime change was needed for these tested interactions. An
|
||||
independent review found no actionable issue with the added tests.
|
||||
|
||||
## Browser and restoration evidence
|
||||
|
||||
Room `AZC4HGLB` uses genuine Advanced initialization followed by a staged battle.
|
||||
An Atreides human chose the special Karama inspection through the browser after
|
||||
a Truthtrance leader promise and ordinary Prescience. The Medium Emperor used
|
||||
Ghola to revive Hasimir Fenring and sealed a plan honoring those restrictions.
|
||||
The private inspection displayed dial 0, support 0, Fenring, Chaumas and Shield,
|
||||
while Atreides retained its own plan controls. Expanded component controls and
|
||||
the inspection were readable on desktop and at 390×844 phone width.
|
||||
|
||||
A backed-up restart had already completed when the user removed the hourly
|
||||
restart requirement. Browser refresh restored the same owned inspection and
|
||||
the earlier inventory room `LC3E55GY`. The final comparison retained all 64
|
||||
saved room versions and state hashes with none missing or changed. The server
|
||||
returned HTTP 200. Future restarts occur only when a change or observed server
|
||||
condition warrants them, with preservation and restoration checks; no recurring
|
||||
automation is installed.
|
||||
|
||||
Required check results are recorded in the checkpoint commit and private
|
||||
source-bound report. There are no runtime changes, so the preceding production
|
||||
build and HTTP-suite evidence are reused rather than claimed as new runs.
|
||||
This audit does not certify arbitrary Truthtrance questions, every Advanced
|
||||
interaction, expansion combinations or a complete human/AI game.
|
||||
@@ -37,6 +37,18 @@ These are actual recorded implementation choices, not missing expansion componen
|
||||
|
||||
The broad Truthtrance language creates an engineering and guidance gap even if some natural-language enforcement requires a later user interpretation. It should not be disguised as an ambiguity that excuses all knowable structured questions.
|
||||
|
||||
## 13 September full-plan interaction follow-up
|
||||
|
||||
The [full-plan interaction audit](ATREIDES_FULL_PLAN_TIMING.md) now verifies the
|
||||
native Voice/ordinary Prescience/Truthtrance/Ghola paths discussed above, including
|
||||
seven interruption windows, all four AI profiles, private inspection and JSON
|
||||
continuation. `tests/full-plan.test.ts` uses actual Advanced setup before staging
|
||||
the battle. No runtime change was required for those paths. The special offer's
|
||||
precise deadline and target-first ordering remain implementation choices not
|
||||
settled by the retrieved GF9 text. Keep those source limitations and all wider
|
||||
mode gates; the old statement that this particular interaction audit remains
|
||||
undone is superseded by the linked record.
|
||||
|
||||
## Implemented mechanisms to preserve and verify in integration
|
||||
|
||||
| Requirement | Current authoritative evidence | What that evidence does and does not prove |
|
||||
|
||||
+14
-11
@@ -5,7 +5,7 @@ Updated 13 September 2026. This is the short navigation dashboard; detailed evid
|
||||
## Evidence and source of truth
|
||||
|
||||
- [Rules implementation checklist](../game/reference.ts): `RULE_TOPICS`, topic `implementation-checklist`, and each feature's implementation, controls, AI, documentation and verification entries. Coverage is per stated boundary, not an implicit whole-game certificate.
|
||||
- [Latest gameplay checkpoint before this batch](IMPLEMENTATION_STATUS.md): Guild Nexus Secret Ally, 10 September 2026 (checkpoint `06d0a66`), records types, lint, **4,162 offline tests**, production build and **40 HTTP/session tests** passing, two browser rooms and preserved opening saved games. These are recorded results, not checks rerun for this dashboard.
|
||||
- [Latest verified gameplay checkpoint](IMPLEMENTATION_STATUS.md): Truthtrance hand inventory, 13 September 2026 (`7454456`): types, lint, **4,200 offline tests**, production build and **40 HTTP tests** passed, with a phone/browser refresh journey and preserved saved games. These are recorded results, not checks rerun for this dashboard.
|
||||
- [Development guide](DEVELOPMENT.md) defines focused verification; [rule decisions](RULE_DECISIONS.md) locates settled contracts and pending interpretations. Read the newest checkpoint and relevant feature document before consulting older log entries.
|
||||
|
||||
The user authorized a one-time local reset on 13 September: the 3,644 historical
|
||||
@@ -13,16 +13,19 @@ rooms were archived privately and cleared. [Fresh checkpoint and preservation
|
||||
policy](VERIFICATION_WORKFLOW.md#authorized-reset-13-september-2026) now apply to
|
||||
new games; historical room IDs are no longer active local fixtures.
|
||||
|
||||
The efficiency setup was verified and pushed as `6e26ba5`: types, lint and 4,177
|
||||
offline tests passed. See [pilot measurements](EFFICIENCY_PILOT.md). The current
|
||||
Advanced shipment batch extends typed preparation and commitments without Guild
|
||||
or optional modules; its validation is recorded in the checkpoint commit.
|
||||
The first three efficiency milestones are pushed: tooling `6e26ba5`, typed
|
||||
Advanced shipment commitments `32791ed`, and [hand inventory facts](TRUTHTRANCE_HAND_INVENTORY.md)
|
||||
`7454456`. See [pilot measurements](EFFICIENCY_PILOT.md).
|
||||
|
||||
`32791ed` verified that batch with 4,192 offline tests, build, 40 HTTP tests and
|
||||
two isolated browser journeys. The next bounded batch adds [total hand and
|
||||
primary-role Truthtrance counts](TRUTHTRANCE_HAND_INVENTORY.md), reusing existing
|
||||
private-answer, controls and persisted continuation paths. Its focused browser
|
||||
journey verifies two AI answers, invalid-input feedback and phone-width refresh.
|
||||
The current [Atreides full-plan audit](ATREIDES_FULL_PLAN_TIMING.md) verifies
|
||||
Truthtrance/Voice/Prescience/Ghola interactions from genuine Advanced setup,
|
||||
replacing the old Basic-then-flip test fixture. These existing paths pass without
|
||||
a runtime change. Exact special-offer timing and target-first ordering remain
|
||||
source interpretations; they are not settled by test results.
|
||||
|
||||
The development server is reused until a change or observed condition warrants
|
||||
a restart. The user removed hourly restarts; preserve saved games and verify
|
||||
restoration after necessary restarts. No recurring automation is installed.
|
||||
|
||||
## Remaining readiness
|
||||
|
||||
@@ -43,6 +46,6 @@ Likewise, the [Truthtrance readiness audit](TRUTHTRANCE_NONBATTLE_READINESS_2026
|
||||
|
||||
1. Reconcile the remaining base-only gaps against current helpers, consumers and tests, using the existing [Guild cancellation contract](GUILD_RATE_KARAMA_RULES.md) and [special-Karama findings](BASE_ADVANCED_READINESS_20260907.md#explicit-unresolved-base-interpretations). Keep already-asked interpretations pending; tests are not rulings.
|
||||
2. Ordinary Guild cancellation remains the next dependent gameplay batch around an exact saved shipment intent. Keep discount, cross/return permission and income separate; preserve contributors, independent Karama rates and original typed forces. Establish the unresolved affordability/settlement contract before implementing those outcomes. Verify sufficient-funds paths, nested responses, rejected-action immutability, private controls, all four bots and saved continuations together.
|
||||
3. Close remaining core Truthtrance and Advanced timing gaps, then run genuine Basic/Advanced setup-to-finish multiplayer and browser acceptance before reconsidering mode gates. Expansion completion remains a subsequent part of the same overall goal.
|
||||
3. Continue remaining core Truthtrance and Advanced timing gaps. The [full-plan interaction audit](ATREIDES_FULL_PLAN_TIMING.md) closes the named native card-interaction evidence gap while preserving unresolved timing interpretations. Run genuine Basic/Advanced setup-to-finish multiplayer and browser acceptance before reconsidering mode gates. Expansion completion remains a subsequent part of the same overall goal.
|
||||
|
||||
Consult [AI calibration](AI_CALIBRATION.md), [multiplayer audit](MULTIPLAYER_AUDIT.md), [autopilot](AUTOPILOT.md), [component inventory](COMPONENT_INVENTORY.md) and [visual playtest](VISUAL_PLAYTEST.md) for their measured scope and follow-ups. Use current code and later checkpoints to interpret historical results. Publication still requires the completion and integrated verification gates in [README](../README.md).
|
||||
@@ -10,7 +10,7 @@ The removed recurring automation stays removed.
|
||||
| --- | --- | --- |
|
||||
| 1: setup | Current status, decision index, reusable checks, saved-game snapshots/backups and HTTP seat restoration | Accepted checkpoint `6e26ba5`. Aggregate counters 57,957,554 → 58,124,416: 166,862 tokens including planning, coordination and the user-requested save reset. Two bounded helpers. Fifteen added regressions; 4,177 offline tests passed. Three broad attempts: two stopped before tests on type/lint errors; final check passed in 130.147s. One cancellation defect found and fixed in review. |
|
||||
| 2: core readiness batch | Typed Advanced reserve-shipment commitments without Guild or optional modules | Accepted checkpoint `32791ed`. Aggregate counters 58,124,416 → 58,695,053: 570,637 tokens. Two bounded helpers: regression implementation and independent rules/privacy review. Fifteen added tests; 4,192 offline and 40 HTTP tests plus build passed. One stable broad run; focused checks resolved setup mistakes and two review findings before it. Two browser rooms verified distinct AI and human paths. Pending Guild settlement remains explicit. |
|
||||
| 3: next dependency batch | Truthtrance total hand size and primary-role counts, controls, bots and recovery | Start aggregate counter 58,695,053. Two bounded helpers on the cheaper workhorse model for regression implementation and independent review. Reuses the existing count panel and persisted-room fixtures; eight added tests and expanded SQLite cases. Two copy issues resolved before broad verification. One isolated browser room covers combined role counts, total size, invalid input and phone refresh. Confirmed endpoint and final results are recorded in the checkpoint commit. |
|
||||
| 3: next dependency batch | Truthtrance total hand size and primary-role counts, controls, bots and recovery | Accepted checkpoint `7454456`. Aggregate counter 58,695,053 → 59,144,653 at completed verification: 449,600 tokens, before final Git/push overhead. Two workhorse-model helpers for regression implementation and independent review. Reused count panel and SQLite fixtures; eight added tests and expanded recovery cases. Two copy issues fixed before the single stable broad run: 4,200 offline tests, build and 40 HTTP tests passed. One browser room covered combined roles, total size, invalid input and phone refresh. |
|
||||
|
||||
The aggregate counter may update only between turns. Record a confirmed endpoint
|
||||
when available rather than estimating missing usage. It includes coordination and
|
||||
@@ -23,10 +23,26 @@ the reusable HTTP restoration helper does not automate browser setup or visual Q
|
||||
|
||||
Milestone 2 cost more than the tooling baseline and cannot establish a saving.
|
||||
Its larger rules/interaction scope, browser setup and context recovery contribute
|
||||
to that total. Keep the third batch bounded, reuse fixtures and avoid repeatedly
|
||||
loading old audits. Compare concrete capabilities and reopened defects alongside
|
||||
token counts before choosing the next budget; three different deliverables alone
|
||||
cannot support a reliable percentage estimate.
|
||||
to that total. Milestone 3 was smaller and reused more existing structure; its lower
|
||||
count cannot isolate the effect of model choice or establish a savings rate.
|
||||
Avoid repeatedly loading old audits. Three different deliverables alone cannot
|
||||
support a reliable percentage estimate.
|
||||
|
||||
For initial planning, allow roughly **450,000–575,000 aggregate tokens for a
|
||||
similarly sized reviewed gameplay batch**, based on milestones 2 and 3. This is a
|
||||
low-confidence planning estimate, not a dollar quote, token-budget setting or a
|
||||
reason to stop required work. Larger interaction sets need a revised estimate.
|
||||
The tooling baseline was about 167,000 and is not a gameplay cost target. Reassess
|
||||
after the next few comparable batches, including defects found after acceptance.
|
||||
|
||||
Next adjustment: use the owner plus one bounded helper for source research and
|
||||
independent review when the existing implementation needs verification. Reuse
|
||||
the same helper for its follow-up review, existing fixtures and applicable checks.
|
||||
Avoid adding runtime code merely to make an audit appear to be implementation.
|
||||
The full-plan interaction audit starts at aggregate counter 59,151,671, uses that
|
||||
one-helper pattern and changes tests/documentation. It therefore requires the
|
||||
full offline check, but can reuse the unchanged application's earlier build;
|
||||
browser/restart acceptance supplies direct recovery evidence.
|
||||
|
||||
Check account usage after each verified checkpoint. The user explicitly confirmed
|
||||
the stop point as **80% weekly usage consumed (20% remaining)**. Keep the goal
|
||||
|
||||
@@ -1,6 +1,25 @@
|
||||
# Dune implementation status
|
||||
|
||||
|
||||
## 13 September 2026 — Atreides full-plan interaction verification
|
||||
|
||||
Replaced the full-plan suite's Basic-then-flip fixture with genuine Advanced
|
||||
initialization and added five tests for Truthtrance, Voice, ordinary Prescience,
|
||||
Ghola preparation and private full-plan inspection. Seventeen focused tests pass,
|
||||
including seven interruption windows and all four profiles. The tested native
|
||||
runtime paths needed no change. Independent source and test review found no
|
||||
publisher-backed defect, while preserving the unresolved special offer/deadline
|
||||
and target-first interpretation. See the [timing audit](ATREIDES_FULL_PLAN_TIMING.md)
|
||||
for exact evidence and limits. Browser room `AZC4HGLB` verifies human special
|
||||
inspection, Medium AI Ghola preparation and a restricted sealed plan; desktop
|
||||
and phone inspection are readable. Refresh after the already-completed restart
|
||||
restores that private view and the earlier inventory seat. All 64 saved room
|
||||
versions/hashes remain unchanged. The user removed hourly restarts: restart
|
||||
only when warranted, preserving games and verifying restoration. Final required
|
||||
checks and aggregate usage belong to the verified checkpoint; no mode gate is
|
||||
opened and no recurring automation is installed.
|
||||
|
||||
|
||||
## 13 September 2026 — Truthtrance hand size and primary-role facts
|
||||
|
||||
Added total physical hand counts and explicit primary-role comparisons, sharing
|
||||
|
||||
@@ -25,7 +25,7 @@ These are unresolved in the cited records. Do not infer an answer from elapsed t
|
||||
| Boundary | Still unresolved | Existing record |
|
||||
| --- | --- | --- |
|
||||
| Ordinary Guild cancellation | Unaffordable repricing, replacement/retry/decline, consumed opportunity, contributor adjustment and ordering with special shipment-stop. The affordability question was already sent. | [Material timing](GUILD_RATE_KARAMA_RULES.md#material-unresolved-timing) |
|
||||
| Base special Karama | Unfunded auction recovery, stopped-shipment settlement and Atreides full-plan commitment order remain provisional or require interaction audit. | [Explicit base interpretations](BASE_ADVANCED_READINESS_20260907.md#explicit-unresolved-base-interpretations) |
|
||||
| Base special Karama | Unfunded auction recovery, stopped-shipment settlement and Atreides full-plan commitment order remain provisional. Native full-plan/Voice/Prescience/Truthtrance/Ghola interactions now have genuine-setup focused evidence, but the source audit does not settle the special offer/deadline or target-first interpretation. | [Explicit base interpretations](BASE_ADVANCED_READINESS_20260907.md#explicit-unresolved-base-interpretations), [full-plan interaction audit](ATREIDES_FULL_PLAN_TIMING.md) |
|
||||
| Guild Secret Ally/Homeworlds | Whether the reserve-return grant overrides the Homeworld return restriction. The question was already sent; that route stays guarded. | [Homeworld return](NEXUS_GUILD_SECRET_ALLY_RULES.md#homeworld-prices-and-the-unresolved-return), [checkpoint](NEXUS_GUILD_SECRET_ALLY_RUNTIME.md) |
|
||||
| Occupied Homeworlds | Entitlement/expiry after departure, replacement or contest; unique powers and shared income. The first-occupier/current-occupier question was already sent. | [Occupation questions](HOMEWORLD_OCCUPATION_RULES.md#questions-the-retrieved-sources-do-not-settle), [lifecycle audit](HOMEWORLD_OCCUPATION_LIFECYCLE_AUDIT.md) |
|
||||
| Nexus responses and composition | Betrayal private-response timing, partial revival/payment choices, Emperor fewer-than-five, fresh advisor/storm cases, Duke restoration and remaining faction-specific combinations. | [Common boundaries](NEXUS_CARD_RULES.md#remaining-source-questions-and-release-boundaries) and the individual faction source/runtime documents above |
|
||||
|
||||
+253
-3
@@ -3,6 +3,7 @@ import assert from 'node:assert/strict';
|
||||
import {
|
||||
applyAction,
|
||||
createGame,
|
||||
initializeBaseGameForAudit,
|
||||
newPlayer,
|
||||
joinGame,
|
||||
viewGame,
|
||||
@@ -12,6 +13,7 @@ import {
|
||||
import { baseDeck } from '../game/cards';
|
||||
import { botActions } from '../game/bots';
|
||||
import { DIFFICULTIES } from '../game/bot-profiles';
|
||||
import type { PlanClaim } from '../game/battle-promises';
|
||||
const cards = baseDeck();
|
||||
const karama = cards.find((c) => c.effect === 'karama')!;
|
||||
const poison = cards.find((c) => c.kind === 'poison')!;
|
||||
@@ -19,12 +21,12 @@ const shield = cards.find((c) => c.kind === 'shield')!;
|
||||
const snooper = cards.find((c) => c.kind === 'snooper')!;
|
||||
const worthless = cards.find((c) => c.kind === 'worthless')!;
|
||||
function fixture(outsider = false) {
|
||||
let g = createGame('FULLPLAN', newPlayer('a', 'Atreides', 'atreides'));
|
||||
let g = createGame('FULLPLAN', newPlayer('a', 'Atreides', 'atreides'), true);
|
||||
joinGame(g, newPlayer('e', 'Emperor', 'emperor'));
|
||||
joinGame(g, newPlayer('g', 'Guild', 'guild'));
|
||||
joinGame(g, newPlayer('b', 'Bene Gesserit', 'beneGesserit'));
|
||||
g.players.forEach((p) => (p.ready = true));
|
||||
g = applyAction(g, 'a', { type: 'start' });
|
||||
g = initializeBaseGameForAudit(g);
|
||||
g = applyAction(g, 'b', { type: 'predict', faction: 'atreides', turn: 3 });
|
||||
for (const p of g.players)
|
||||
if (p.traitorChoices.length)
|
||||
@@ -32,7 +34,13 @@ function fixture(outsider = false) {
|
||||
type: 'traitor',
|
||||
leader: p.traitorChoices[0],
|
||||
});
|
||||
g.advanced = true;
|
||||
g = applyAction(g, 'b', {
|
||||
type: 'advisorSetup',
|
||||
territory: 'polar_sink',
|
||||
sector: 0,
|
||||
});
|
||||
assert.equal(g.status, 'playing');
|
||||
assert.equal(g.advanced, true);
|
||||
g.phase = 6;
|
||||
g.active = outsider ? 'e' : 'a';
|
||||
g.order = outsider ? ['e', 'g', 'a', 'b'] : ['a', 'e', 'g', 'b'];
|
||||
@@ -44,10 +52,19 @@ function fixture(outsider = false) {
|
||||
p.spice = 10;
|
||||
p.hand = [];
|
||||
p.traitors = [];
|
||||
p.advisors = {};
|
||||
if (p.elites) {
|
||||
p.elites.reserves = 5;
|
||||
p.elites.tanks = 0;
|
||||
p.elites.forces = {};
|
||||
}
|
||||
}
|
||||
g.players[0].hand = [karama, snooper];
|
||||
g.players[1].hand = [poison, shield, worthless];
|
||||
g.discard = [];
|
||||
const held = new Set(g.players.flatMap((p) => p.hand.map((c) => c.id)));
|
||||
g.deck = baseDeck().filter((c) => !held.has(c.id));
|
||||
g.traitorReserve = g.players.flatMap((p) => p.leaders.map((l) => l.id));
|
||||
return g;
|
||||
}
|
||||
function responses(state: Game) {
|
||||
@@ -381,3 +398,236 @@ void test('full inspection includes KH and can target either combatant under any
|
||||
assert.equal(g.battle?.revealed, true);
|
||||
assert.equal(viewGame(g, 'g').battle?.plans.a.kwisatz, true);
|
||||
});
|
||||
|
||||
function take(g: Game, id: string, name: string) {
|
||||
const index = g.deck.findIndex((card) => card.name === name);
|
||||
assert.ok(index >= 0, name);
|
||||
const card = g.deck.splice(index, 1)[0];
|
||||
g.players.find((p) => p.id === id)!.hand.push(card);
|
||||
return card.id;
|
||||
}
|
||||
function question(g: Game, claim: PlanClaim) {
|
||||
g = applyAction(g, 'a', {
|
||||
type: 'card',
|
||||
card: g.players[0].hand.find((c) => c.effect === 'truthtrance')!.id,
|
||||
});
|
||||
while (g.truthtrance?.stage === 'priority')
|
||||
g = applyAction(
|
||||
g,
|
||||
g.players.find((p) => !g.truthtrance!.passed.includes(p.id))!.id,
|
||||
{ type: 'truthPass' },
|
||||
);
|
||||
return applyAction(g, 'a', {
|
||||
type: 'truthAsk',
|
||||
question: { kind: 'battlePlan', target: 'e', claim },
|
||||
});
|
||||
}
|
||||
function answer(g: Game, value: 'yes' | 'no' = 'yes') {
|
||||
return applyAction(JSON.parse(JSON.stringify(g)), 'e', {
|
||||
type: 'truthAnswer',
|
||||
answer: value,
|
||||
});
|
||||
}
|
||||
function unchanged(g: Game, id: string, action: Action) {
|
||||
const copy = structuredClone(g);
|
||||
assert.throws(() => applyAction(g, id, action));
|
||||
assert.deepEqual(g, copy);
|
||||
}
|
||||
function custody(g: Game) {
|
||||
const all = [...g.deck, ...g.discard, ...g.players.flatMap((p) => p.hand)]
|
||||
.map((c) => c.id)
|
||||
.sort();
|
||||
assert.equal(all.length, new Set(all).size);
|
||||
return all;
|
||||
}
|
||||
|
||||
void test('Truthtrance and Ghola survive Voice, ordinary prescience and full-plan inspection from genuine Advanced setup', () => {
|
||||
for (const level of DIFFICULTIES) {
|
||||
const initial = fixture();
|
||||
initial.players[0].ally = 'b';
|
||||
initial.players[3].ally = 'a';
|
||||
initial.players[1].bot = level;
|
||||
initial.players[1].leaders[0].dead = true;
|
||||
initial.players[1].leaders[0].deaths = 1;
|
||||
take(initial, 'a', 'Truthtrance');
|
||||
const ghola = take(initial, 'e', 'Tleilaxu Ghola');
|
||||
const ids = custody(initial);
|
||||
let g = responses(choose(initial));
|
||||
assert.equal(g.battle?.preparation?.kind, 'voice');
|
||||
const originalPreparation = structuredClone(g.battle!.preparation);
|
||||
g = question(g, {
|
||||
kind: 'and',
|
||||
terms: [
|
||||
{ kind: 'leader', leader: 'emperor-0' },
|
||||
{ kind: 'weapon', name: poison.name },
|
||||
],
|
||||
});
|
||||
assert.deepEqual(viewGame(g, 'e').truthBattleAnswers, ['yes', 'no']);
|
||||
assert.deepEqual(g.battle!.preparation, originalPreparation);
|
||||
unchanged(g, 'e', { type: 'card', card: ghola, leader: 'emperor-0' });
|
||||
g = answer(g);
|
||||
g = responses(
|
||||
applyAction(g, 'b', { type: 'voice', kind: 'snooper', must: false }),
|
||||
);
|
||||
g = responses(applyAction(g, 'a', { type: 'prescience', field: 'leader' }));
|
||||
g = applyAction(g, 'e', { type: 'prescienceAnswer', value: 'emperor-0' });
|
||||
assert.equal(g.decision?.kind, 'fullPlanOffer');
|
||||
assert.equal(g.battle!.truthPromises![0].released, undefined);
|
||||
assert.equal(g.players[1].leaders[0].dead, true);
|
||||
g = inspect(g);
|
||||
const actions = botActions(viewGame(g, 'e'));
|
||||
assert.equal(actions[0].type, 'card', level);
|
||||
assert.equal(actions[0].card, ghola, level);
|
||||
assert.equal(actions[0].leader, 'emperor-0', level);
|
||||
for (const id of ['a', 'g', 'b'])
|
||||
assert.equal(viewGame(g, id).battle!.compliantPreparation, null);
|
||||
g = applyAction(g, 'e', actions[0]);
|
||||
unchanged(g, 'e', { ...targetPlan, leader: 'emperor-1' });
|
||||
unchanged(g, 'e', { ...targetPlan, weapon: null });
|
||||
g = applyAction(g, 'e', targetPlan);
|
||||
assert.equal(
|
||||
viewGame(g, 'a').battle!.fullPlanInsight?.plan.leader,
|
||||
'emperor-0',
|
||||
);
|
||||
assert.equal(g.battle!.revealed, false);
|
||||
assert.equal(g.decision, null);
|
||||
g = applyAction(JSON.parse(JSON.stringify(g)), 'a', ownPlan);
|
||||
assert.equal(g.battle!.revealed, true);
|
||||
assert.deepEqual(custody(g), ids);
|
||||
assert.equal(g.discard.filter((c) => c.id === ghola).length, 1);
|
||||
}
|
||||
});
|
||||
|
||||
void test('Truthtrance at the special offer preserves the offer and both revival-dependent answers until a real plan is sealed', () => {
|
||||
for (const useSpecial of [false, true]) {
|
||||
const before = fixture();
|
||||
take(before, 'a', 'Truthtrance');
|
||||
const ghola = take(before, 'e', 'Tleilaxu Ghola');
|
||||
before.players[1].leaders[0].dead = true;
|
||||
before.players[1].leaders[0].deaths = 1;
|
||||
let g = offer(before);
|
||||
const offered = structuredClone(g.decision);
|
||||
g = question(g, { kind: 'leader', leader: 'emperor-0' });
|
||||
assert.deepEqual(viewGame(g, 'e').truthBattleAnswers, ['yes', 'no']);
|
||||
g = answer(g);
|
||||
assert.deepEqual(g.decision, offered);
|
||||
unchanged(g, 'e', targetPlan);
|
||||
g = useSpecial
|
||||
? inspect(g)
|
||||
: applyAction(g, 'a', { type: 'decision', decline: true });
|
||||
assert.equal(g.battle!.truthPromises![0].released, undefined);
|
||||
g = applyAction(g, 'e', { type: 'card', card: ghola, leader: 'emperor-0' });
|
||||
g = applyAction(g, 'e', targetPlan);
|
||||
assert.equal(!!viewGame(g, 'a').battle!.fullPlanInsight, useSpecial);
|
||||
assert.equal(!!g.players[0].specialKaramaUsed, useSpecial);
|
||||
}
|
||||
});
|
||||
|
||||
void test('Truthtrance after inspection reports only the sealed plan and cannot reopen its fields or physical card custody', () => {
|
||||
const before = fixture();
|
||||
take(before, 'a', 'Truthtrance');
|
||||
take(before, 'a', 'Truthtrance');
|
||||
let g = applyAction(inspect(offer(before)), 'e', targetPlan);
|
||||
const sealed = structuredClone(g.battle!.plans.e),
|
||||
ids = custody(g);
|
||||
for (const [claim, expected] of [
|
||||
[{ kind: 'weapon', name: poison.name }, 'yes'],
|
||||
[{ kind: 'dial', compare: 'eq', value: 1 }, 'no'],
|
||||
] as const) {
|
||||
g = question(g, claim);
|
||||
assert.deepEqual(viewGame(g, 'e').truthBattleAnswers, [expected]);
|
||||
unchanged(g, 'e', {
|
||||
type: 'truthAnswer',
|
||||
answer: expected === 'yes' ? 'no' : 'yes',
|
||||
});
|
||||
g = answer(g, expected);
|
||||
assert.deepEqual(g.battle!.plans.e, sealed);
|
||||
assert.equal(g.battle!.truthPromises?.length ?? 0, 0);
|
||||
assert.deepEqual(viewGame(g, 'a').battle!.fullPlanInsight?.plan, sealed);
|
||||
for (const id of ['g', 'b']) {
|
||||
assert.equal(viewGame(g, id).battle!.fullPlanInsight, null);
|
||||
assert.equal(viewGame(g, id).battle!.plans.e, undefined);
|
||||
}
|
||||
}
|
||||
unchanged(g, 'e', { ...targetPlan, defense: null });
|
||||
unchanged(g, 'e', { type: 'bribe', target: 'g', amount: 9 });
|
||||
assert.deepEqual(custody(g), ids);
|
||||
g = applyAction(g, 'a', ownPlan);
|
||||
assert.equal(g.battle!.revealed, true);
|
||||
assert.deepEqual(g.battle!.plans.e, sealed);
|
||||
});
|
||||
|
||||
void test('Truthtrance suspends and restores every native pre-plan window without choosing or dropping another power', () => {
|
||||
const initial = fixture();
|
||||
initial.players[0].ally = 'b';
|
||||
initial.players[3].ally = 'a';
|
||||
take(initial, 'a', 'Truthtrance');
|
||||
take(initial, 'g', 'Karama');
|
||||
let g = responses(choose(initial));
|
||||
const frames = [g]; // Voice selection.
|
||||
g = applyAction(g, 'b', { type: 'voice', kind: 'snooper', must: false });
|
||||
frames.push(g); // Voice cancellation response.
|
||||
g = responses(g);
|
||||
frames.push(g); // Prescience selection.
|
||||
g = applyAction(g, 'a', { type: 'prescience', field: 'defense' });
|
||||
frames.push(g); // Prescience cancellation response.
|
||||
g = responses(g);
|
||||
frames.push(g); // Prescience answer.
|
||||
g = applyAction(g, 'e', { type: 'prescienceAnswer', value: shield.id });
|
||||
frames.push(g); // Special full-plan offer.
|
||||
frames.push(inspect(g)); // Special chosen; target has not sealed.
|
||||
for (const frame of frames) {
|
||||
const copy = structuredClone(frame);
|
||||
const asked = question(frame, { kind: 'defense', name: 'Shield' });
|
||||
assert.ok(viewGame(asked, 'e').truthBattleAnswers!.includes('yes'));
|
||||
const resumed = answer(asked);
|
||||
for (const key of [
|
||||
'response',
|
||||
'decision',
|
||||
'active',
|
||||
'phase',
|
||||
'turn',
|
||||
] as const)
|
||||
assert.deepEqual(resumed[key], frame[key], key);
|
||||
for (const key of [
|
||||
'preparation',
|
||||
'prescience',
|
||||
'voice',
|
||||
'fullPlan',
|
||||
'plans',
|
||||
] as const)
|
||||
assert.deepEqual(resumed.battle![key], frame.battle![key], key);
|
||||
assert.deepEqual(frame, copy);
|
||||
assert.equal(resumed.battle!.truthPromises![0].released, undefined);
|
||||
assert.equal(
|
||||
resumed.players[0].specialKaramaUsed,
|
||||
frame.players[0].specialKaramaUsed,
|
||||
);
|
||||
assert.deepEqual(custody(resumed), custody(frame));
|
||||
}
|
||||
});
|
||||
|
||||
void test('Atreides may revive its own leader after reading the target plan without changing or redisclosing that plan', () => {
|
||||
const initial = fixture();
|
||||
const ghola = take(initial, 'a', 'Tleilaxu Ghola');
|
||||
initial.players[0].leaders[0].dead = true;
|
||||
initial.players[0].leaders[0].deaths = 1;
|
||||
let g = applyAction(inspect(offer(initial)), 'e', targetPlan);
|
||||
const inspected = structuredClone(viewGame(g, 'a').battle!.fullPlanInsight);
|
||||
const ids = custody(g);
|
||||
g = applyAction(JSON.parse(JSON.stringify(g)), 'a', {
|
||||
type: 'card',
|
||||
card: ghola,
|
||||
leader: 'atreides-0',
|
||||
});
|
||||
assert.equal(g.players[0].leaders[0].dead, false);
|
||||
assert.equal(g.players[0].leaderRevived, false);
|
||||
assert.deepEqual(viewGame(g, 'a').battle!.fullPlanInsight, inspected);
|
||||
for (const id of ['e', 'g', 'b'])
|
||||
assert.equal(viewGame(g, id).battle!.fullPlanInsight, null);
|
||||
assert.equal(g.discard.filter((c) => c.id === ghola).length, 1);
|
||||
assert.deepEqual(custody(g), ids);
|
||||
g = applyAction(g, 'a', ownPlan);
|
||||
assert.equal(g.battle!.plans.a.leader, 'atreides-0');
|
||||
assert.equal(g.battle!.revealed, true);
|
||||
});
|
||||
Reference in new issue
Block a user