Keep occupied Guild smoke outside actual storm
This commit is contained in:
1 parent
adb00358dd
commit
e57f8ddab7
4 files changed
+35
-235
No files matched your search
@@ -29,6 +29,17 @@ optional skill bands, combinations and public expansion starts remain open.
|
||||
Rules implementation continues; comprehensive assurance and administration
|
||||
remain deferred until all rules are implemented.
|
||||
|
||||
**Checkpoint test repairs — 13 October.**
|
||||
The observed occupied-Guild failure came from always shipping to Arsunt11,
|
||||
even when the actual turn3 storm was there. The native fixture now chooses
|
||||
the first printed Arsunt sector outside the current storm; no storm, force,
|
||||
payment or occupation rule changes. Twenty actual Advanced receipts retain
|
||||
native3/occupied3/Bank2, including storm11 with shipment to Arsunt12.
|
||||
The obsolete reference tests pinned prose, checklist defaults and test-only
|
||||
evidence paths; these are removed, retaining actual topic/link reachability.
|
||||
The two-file union passes10/10 and types/lint pass. No whole-suite or deployed
|
||||
acceptance is claimed.
|
||||
|
||||
**Captured Leader Skill replacement ruling — 13 October.**
|
||||
The user resolved the previously gated captured-card replacement question: the
|
||||
original faction keeps its existing Skill assignment while its trained leader is
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
# Dune implementation status
|
||||
|
||||
## 13 October 2026 — storm-aware occupation fixture and obsolete guidance tests
|
||||
|
||||
The observed Advanced occupied-Guild receipt failure was in the fixture:
|
||||
Arsunt11 was selected regardless of the real storm. Select the first printed
|
||||
Arsunt sector outside the current storm instead. Both original sectors remain
|
||||
available for the scenario; no storm entropy, force stock, payment ledger or
|
||||
production rule is altered.
|
||||
|
||||
Twenty actual Advanced native receipt programs pass exact original payer6/
|
||||
donor2 debits, native3/occupied3/Bank2 and qualification retention. This includes
|
||||
an actual storm11 receipt using Arsunt12. The two affected test files pass10/10;
|
||||
`npm run check:quick` passes.
|
||||
|
||||
`tests/reference.test.ts` no longer pins prose, incidental checklist status,
|
||||
source-text links or test-only evidence filenames. Developer evidence paths
|
||||
legitimately include helpers, controls and contracts. Keep the meaningful
|
||||
public navigation invariant: all phase/faction topics are discoverable and
|
||||
every related topic resolves. Behavioral rule/automation tests are untouched.
|
||||
No broad-suite, publication or deployed acceptance claim follows.
|
||||
|
||||
## 13 October 2026 — captured Leader Skill replacement ruling
|
||||
|
||||
The user resolved the previously gated captured-card replacement question: the
|
||||
|
||||
@@ -174,7 +174,9 @@ export function createOccupiedGuildFixture(options: {
|
||||
const beforePledge = structuredClone(game);
|
||||
if (donorShare) act(donor, { type: 'pledgeAid', amount: donorShare });
|
||||
const beforeAction = structuredClone(game), gross = payerShare + donorShare;
|
||||
const target = 'arsunt', sector = territory(target).sectors[0];
|
||||
const target = 'arsunt';
|
||||
const sector = territory(target).sectors.find(candidate => candidate !== game.storm);
|
||||
assert.ok(sector !== undefined, 'The actual shipment needs a printed Arsunt sector outside the current storm');
|
||||
assert.ok(occupiedGuildPlayer(game, payer).spice >= payerShare);
|
||||
assert.ok(game.players.every(p => !Object.keys(p.forces).some(key => key.startsWith(`${target}:`))));
|
||||
const action: Action = { type: 'ship', territory: target, sector, amount: gross / 2,
|
||||
|
||||
+1
-234
@@ -1,12 +1,7 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {
|
||||
RULE_TOPICS,
|
||||
RULE_CHECKLIST_AREAS,
|
||||
phaseRuleId,
|
||||
} from '../game/reference';
|
||||
import { RULE_TOPICS, phaseRuleId } from '../game/reference';
|
||||
import { FACTIONS, PHASES } from '../game/catalog';
|
||||
import { readFileSync } from 'node:fs';
|
||||
|
||||
void test('every phase and base faction has a discoverable rule topic with valid related links', () => {
|
||||
const ids = new Set(RULE_TOPICS.map((t) => t.id));
|
||||
@@ -20,231 +15,3 @@ void test('every phase and base faction has a discoverable rule topic with valid
|
||||
assert.ok(ids.has('ai-players'));
|
||||
});
|
||||
|
||||
void test('reviewed feature checklists expose all five independent facets and real local verification evidence', () => {
|
||||
const hub = RULE_TOPICS.find(
|
||||
(topic) => topic.id === 'implementation-checklist',
|
||||
)!;
|
||||
const features = [
|
||||
'setup',
|
||||
'truthtrance-spice',
|
||||
'richese-cards',
|
||||
'richese-acquisition',
|
||||
'richese-no-field',
|
||||
'nullentropy-search',
|
||||
'ecaz-ambassadors',
|
||||
'automatic-casualties',
|
||||
'automatic-responses',
|
||||
'automatic-decisions',
|
||||
'ai-pacing',
|
||||
];
|
||||
for (const id of features) {
|
||||
const topic = RULE_TOPICS.find((candidate) => candidate.id === id)!;
|
||||
assert.ok(
|
||||
hub.related?.includes(id),
|
||||
`${id} is discoverable from the checklist hub`,
|
||||
);
|
||||
assert.deepEqual(
|
||||
topic.checklist?.map((item) => item.area),
|
||||
[...RULE_CHECKLIST_AREAS],
|
||||
id,
|
||||
);
|
||||
assert.equal(
|
||||
topic.checklist?.find((item) => item.area === 'Verification')?.status,
|
||||
'Partial',
|
||||
'focused tests do not certify all browser and expansion interactions',
|
||||
);
|
||||
for (const item of topic.checklist ?? []) {
|
||||
assert.ok(
|
||||
item.detail.length > 20,
|
||||
`${id}: ${item.area} has an explicit scope`,
|
||||
);
|
||||
for (const evidence of item.evidence ?? []) {
|
||||
assert.match(evidence, /^tests\/[a-z0-9-]+\.test\.tsx?$/);
|
||||
assert.match(
|
||||
readFileSync(new URL(`../${evidence}`, import.meta.url), 'utf8'),
|
||||
/test\(/,
|
||||
evidence,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
void test('Richese acquisition guide separates purchase immunity, income response and independent transfer/search controls', () => {
|
||||
const topic = RULE_TOPICS.find(
|
||||
(candidate) => candidate.id === 'richese-acquisition',
|
||||
)!;
|
||||
assert.equal(topic.coverage, 'Partial');
|
||||
const text = topic.steps.join(' ');
|
||||
for (const phrase of [
|
||||
'once per game',
|
||||
'enters your hand secretly',
|
||||
'cannot cancel the special purchase',
|
||||
'Emperor income has a separate response',
|
||||
'Full-hand activation',
|
||||
'final cached card',
|
||||
'have their own hand-panel controls',
|
||||
])
|
||||
assert.ok(text.includes(phrase), phrase);
|
||||
assert.equal(topic.checklist!.length, 5);
|
||||
assert.ok(topic.checklist!.every((item) => item.status === 'Partial'));
|
||||
assert.ok(
|
||||
topic
|
||||
.checklist!.find((item) => item.area === 'Verification')!
|
||||
.evidence!.includes('tests/richese-acquisition-bots.test.ts'),
|
||||
);
|
||||
assert.doesNotMatch(JSON.stringify(topic), /https?:\/\//);
|
||||
});
|
||||
|
||||
void test('automation reference keeps single-outcome settlement, cosmetic notices and paced AI distinct', () => {
|
||||
const casualty = RULE_TOPICS.find(
|
||||
(topic) => topic.id === 'automatic-casualties',
|
||||
)!;
|
||||
const pacing = RULE_TOPICS.find((topic) => topic.id === 'ai-pacing')!;
|
||||
assert.match(
|
||||
casualty.steps.join(' '),
|
||||
/If several physical casualty combinations are legal, the winner still chooses/,
|
||||
);
|
||||
assert.match(casualty.steps.join(' '), /1\.5 seconds each/);
|
||||
assert.match(casualty.steps.join(' '), /while notices are off are consumed/);
|
||||
assert.match(casualty.steps.join(' '), /turning notices on does not replay/);
|
||||
assert.match(
|
||||
casualty.steps.join(' '),
|
||||
/Successful powers without a notice tag remain outside/,
|
||||
);
|
||||
assert.match(
|
||||
casualty.steps.join(' '),
|
||||
/hiding notices does not disable automatic rule settlement or change AI timing/,
|
||||
);
|
||||
assert.match(
|
||||
casualty.steps.join(' '),
|
||||
/Uncancelable responses now settle automatically/,
|
||||
);
|
||||
for (const event of [
|
||||
'successful Ecaz Ambassador placement',
|
||||
'Emperor auction income',
|
||||
'Harkonnen bonus-card draws',
|
||||
'private full-plan inspection availability',
|
||||
])
|
||||
assert.ok(casualty.steps.join(' ').includes(event), event);
|
||||
assert.match(
|
||||
casualty.steps.join(' '),
|
||||
/without exposing its private card identity/,
|
||||
);
|
||||
assert.match(pacing.steps.join(' '), /at most one AI action per step/);
|
||||
assert.match(pacing.steps.join(' '), /Competing workers cannot both commit/);
|
||||
assert.match(pacing.steps.join(' '), /Offline simulations use fast batches/);
|
||||
assert.ok(
|
||||
RULE_TOPICS.find((topic) => topic.id === 'battle')!.related?.includes(
|
||||
casualty.id,
|
||||
),
|
||||
);
|
||||
assert.ok(
|
||||
RULE_TOPICS.find((topic) => topic.id === 'ai-players')!.related?.includes(
|
||||
pacing.id,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
void test('player-facing entry and rules pages contain no external navigation links', () => {
|
||||
for (const file of [
|
||||
'app/page.tsx',
|
||||
'components/game-table.tsx',
|
||||
'components/rules-reference.tsx',
|
||||
'app/rules/coverage/page.tsx',
|
||||
]) {
|
||||
const source = readFileSync(new URL(`../${file}`, import.meta.url), 'utf8');
|
||||
assert.doesNotMatch(source, /href\s*=\s*["'](?:https?:)?\/\//, file);
|
||||
}
|
||||
});
|
||||
|
||||
void test('automatic allowance checklist distinguishes implemented physical custody from conservative promise legality and incomplete notice coverage', () => {
|
||||
const topic = RULE_TOPICS.find(
|
||||
(candidate) => candidate.id === 'automatic-responses',
|
||||
)!;
|
||||
const guide = topic.steps.join(' ');
|
||||
assert.equal(topic.coverage, 'Partial');
|
||||
assert.match(
|
||||
guide,
|
||||
/owner cannot cancel that power and needs no Allow click/,
|
||||
);
|
||||
assert.match(guide, /only your own cancelable cards/);
|
||||
assert.match(guide, /Other players’ pass choices.*are not exposed/);
|
||||
assert.match(guide, /already allowed may still cancel/);
|
||||
assert.match(guide, /Allies retain legal cancellation choices/);
|
||||
assert.match(
|
||||
guide,
|
||||
/active Truthtrance question or phase-opening choice pauses/,
|
||||
);
|
||||
assert.match(
|
||||
guide,
|
||||
/remain listed conservatively.*would break a promise and be rejected/,
|
||||
);
|
||||
assert.match(
|
||||
guide,
|
||||
/merely preparing a player’s view does not award spice or draw cards/,
|
||||
);
|
||||
assert.match(guide, /all successful-power notice tags.*remain unfinished/);
|
||||
const verification = topic.checklist!.find(
|
||||
(item) => item.area === 'Verification',
|
||||
)!;
|
||||
assert.equal(verification.status, 'Partial');
|
||||
assert.ok(
|
||||
verification.evidence?.includes('tests/automatic-responses.test.ts'),
|
||||
);
|
||||
assert.ok(
|
||||
verification.evidence?.includes(
|
||||
'tests/automatic-response-recovery.test.ts',
|
||||
),
|
||||
);
|
||||
assert.equal(
|
||||
topic.checklist!.find((item) => item.area === 'Player controls')!.status,
|
||||
'Partial',
|
||||
);
|
||||
assert.ok(topic.related?.includes('card-truthtrance'));
|
||||
assert.ok(
|
||||
RULE_TOPICS.find(
|
||||
(candidate) => candidate.id === 'automatic-casualties',
|
||||
)!.related?.includes(topic.id),
|
||||
);
|
||||
});
|
||||
|
||||
void test('no-choice decision checklist preserves genuine alternatives and persistent owner-only inspection', () => {
|
||||
const topic = RULE_TOPICS.find(
|
||||
(candidate) => candidate.id === 'automatic-decisions',
|
||||
)!;
|
||||
const guide = topic.steps.join(' ');
|
||||
assert.equal(topic.coverage, 'Partial');
|
||||
assert.match(guide, /declared split.*can be paid exactly/);
|
||||
assert.match(
|
||||
guide,
|
||||
/genuine Karama option or an unfunded payment still requires/,
|
||||
);
|
||||
assert.match(guide, /There is no Finish inspection action/);
|
||||
assert.match(guide, /outside the battle helping an ally/);
|
||||
assert.match(guide, /not copied into the ally’s private view/);
|
||||
assert.match(
|
||||
guide,
|
||||
/Card enlargement and Truthtrance promise controls remain separate/,
|
||||
);
|
||||
assert.match(
|
||||
guide,
|
||||
/Other decisions with only one legal outcome.*remain under review/,
|
||||
);
|
||||
const special = RULE_TOPICS.find(
|
||||
(candidate) => candidate.id === 'special-karama',
|
||||
)!;
|
||||
assert.match(
|
||||
special.steps.join(' '),
|
||||
/No inspection acknowledgement is required/,
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
special.steps.join(' '),
|
||||
/table pauses while Atreides reads/,
|
||||
);
|
||||
assert.ok(
|
||||
topic
|
||||
.checklist!.find((item) => item.area === 'Verification')!
|
||||
.evidence!.includes('tests/automatic-decisions.test.ts'),
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user