Resolve concealed No-Field losses in Discovery blows
This commit is contained in:
1 parent
933a9c1379
commit
11e50e7f49
14 files changed
+587
-10
No files matched your search
@@ -3,6 +3,7 @@ import type { Game, Player, ResponseWindow } from './engine';
|
||||
import { splitLocation, territory, validLocation } from './board';
|
||||
import { SPICE_CARDS } from './cards';
|
||||
import { revealRicheseNoField } from './richese-no-field';
|
||||
import { DISCOVERY_SPICE_CARDS } from './discoveries';
|
||||
|
||||
export class DisasterPreflightError extends Error {}
|
||||
function requireDisaster(
|
||||
@@ -31,7 +32,7 @@ function casualtyCustody(
|
||||
g: Game,
|
||||
p: Player,
|
||||
affected: (key: string) => boolean,
|
||||
cause: 'storm' | 'worm',
|
||||
cause: 'storm' | 'worm' | 'discoveryBlow',
|
||||
) {
|
||||
let losses = 0,
|
||||
eliteLosses = 0;
|
||||
@@ -173,6 +174,21 @@ export function validateStormCancellation(g: Game, response: ResponseWindow) {
|
||||
);
|
||||
casualtyCustody(g, p, (candidate) => candidate === key, 'storm');
|
||||
}
|
||||
/** A printed Discovery blow destroys the entire named territory. Native worm
|
||||
* survival and allied protection do not exempt any faction from this effect. */
|
||||
export function validateDiscoveryBlow(g: Game, t: string) {
|
||||
requireDisaster(
|
||||
DISCOVERY_SPICE_CARDS.some(card => card.territory === t),
|
||||
'The Discovery spice blow needs a printed Discovery territory.',
|
||||
);
|
||||
for (const p of g.players)
|
||||
casualtyCustody(
|
||||
g,
|
||||
p,
|
||||
(key) => splitLocation(key).territory === t && !!boardKey(key),
|
||||
'discoveryBlow',
|
||||
);
|
||||
}
|
||||
export function validateWormDevouring(
|
||||
g: Game,
|
||||
t: string,
|
||||
|
||||
+4
-3
@@ -435,6 +435,7 @@ import {
|
||||
validateStormCancellation,
|
||||
validateWormCancellation,
|
||||
validateWormDevouring,
|
||||
validateDiscoveryBlow,
|
||||
DisasterPreflightError,
|
||||
} from './disaster-preflight';
|
||||
import {
|
||||
@@ -4624,7 +4625,7 @@ function revealPlayerNoField(g: Game, p: Player, cause: NoFieldRevealCause) {
|
||||
observeOccupation(g);
|
||||
log(
|
||||
g,
|
||||
`${p.name} revealed the ${result.value} No-Field in ${territory(result.location.territory).name} (${cause === 'alliedSeparation' ? 'allied separation' : cause}) and placed ${result.forces} physical forces from reserves.${result.forces < result.value ? ' Only the remaining reserves could be placed.' : ''}`,
|
||||
`${p.name} revealed the ${result.value} No-Field in ${territory(result.location.territory).name} (${cause === 'alliedSeparation' ? 'allied separation' : cause === 'discoveryBlow' ? 'Discovery spice blow; provisional destructive reveal' : cause}) and placed ${result.forces} physical forces from reserves.${result.forces < result.value ? ' Only the remaining reserves could be placed.' : ''}`,
|
||||
{ faction: 'richese', name: 'No-Field revealed' },
|
||||
);
|
||||
return result;
|
||||
@@ -13375,8 +13376,8 @@ function resolveDiscoveryBlow(g: Game, card: Extract<SpiceCard, {territory:strin
|
||||
const printed = DISCOVERY_SPICE_CARDS.find(value => value.discovery === card.discovery)!;
|
||||
requireRule(card.territory === printed.territory && card.sector === printed.sector && card.amount === printed.amount,
|
||||
'The Discovery spice blow has changed its printed territory, sector or amount.');
|
||||
disasterPreflight(() => validateWormDevouring(g,card.territory,undefined,false));
|
||||
for (const p of g.players) killTerritory(g,p,card.territory);
|
||||
disasterPreflight(() => validateDiscoveryBlow(g,card.territory));
|
||||
for (const p of g.players) killTerritory(g,p,card.territory,Infinity,false,'discoveryBlow');
|
||||
for (const key of Object.keys(g.spice)) if (splitLocation(key).territory === card.territory) delete g.spice[key];
|
||||
log(g, `A Discovery spice blow destroyed all previous spice and forces in ${territory(card.territory).name}, including Fremen forces. The new spice is placed afterward.`);
|
||||
const placement = DISCOVERY_CARD_PLACEMENTS[card.discovery];
|
||||
|
||||
+6
-5
@@ -5734,6 +5734,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
'Revealing Ornithopter records who carries it and the turn it was gained. On a later turn, its owner may spend it on one ordinary movement action to give that selected group a fixed range of three. It grants no extra action, adds no shipment and is removed only after the movement commits; saved arrival interactions retain the exact selected group.',
|
||||
'The five revealed location tokens become separate, initially empty territories inside their surroundings. A private peek does not open the location. Normal ground movement enters through the surrounding territory, shipment uses stronghold prices, at most two factions may occupy it, and forces inside are protected from storms and sandworms. Only Jacurutu Sietch counts toward stronghold victory.',
|
||||
'Native typed forces enter as their actual ordinary and elite groups, never as passengers from the mobile stronghold or the concealed value of a No-Field. Richese may ship or move a concealed marker into an actually revealed nested location at sector zero; it remains one effective presence until its original reveal materializes available reserves. CHOAM Card Stash uses its five-card limit, drawing first and choosing any held discard afterward.',
|
||||
'A printed Discovery blow destroys all old spice and forces in its target territory, including Fremen. A concealed No-Field provisionally reveals, materializes only available reserves and loses those actual counters with its physical companions; zero creates no casualty. This destructive reveal is a selected interpretation. It preserves token history and the original new-spice, storm and face-down placement sequence. Separately identified nested locations and other territories stay outside the parent target. Continue through normal Ready and Accept spice blow controls.',
|
||||
'At the start of the next turn, before the storm and mobile stronghold movement, each eligible faction may move any positive subset of its non-advisor groups from the surrounding territory into the newly revealed location. The signed choice preserves ordinary and elite forces across sectors, normal source storm and occupancy restrictions, and costs no spice, shipment or normal movement. The printed text gives no sequence and does not restate the source-storm restriction; processing tokens in reveal order and factions in storm order while excluding storm sources are source inferences.',
|
||||
'After advisor releases, every Cistern fighter occupant receives two bank spice. Orgiz occupants resolve in original storm order using actual ordinary deposit receipts, not mere force presence or old wallets. Advanced charges once per territory; Basic provisionally charges once per positive deposit. The first actual foreign ordinary collector in storm order with available spice pays. Shared Ecaz lots in both modes wait for the actual split, then consider positive foreign recipients Ecaz first, ally second; Advanced counts the territory once and Basic counts actually reduced source deposits. Self, zero shares and empty purses do not qualify. Balances track each transfer without debt; ordering and Basic mapping remain provisional.',
|
||||
'Jacurutu pays one bank spice for each opposing undialed physical force actually sent to the Tanks. Where an Advanced dial admits different physical counts, the defeated player chooses an exact ordinary/elite/paid reading before support, casualties or rewards settle; unique readings remain automatic. This choice timing is a visibly provisional implementation interpretation. Original Harass returns bind their physical dial and actual withdrawal/retreat counters do not earn spice.',
|
||||
@@ -5746,8 +5747,8 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
checklist: [
|
||||
{
|
||||
area: 'Implementation', status: 'Partial',
|
||||
detail: 'Original classic and supported native Skills/Nexus/Discovery entries retain their selected modules and controls. Jacurutu uses an exact defeated-player reading before native settlement; Testing Station queues cumulative choices before one traversal. Cistern pays every fighter occupant. Orgiz uses actual ordinary receipts and bilateral shared allocations in both modes, grouping by territory in Advanced and positive source deposit in Basic without guessed payers or debt. Available balances track all transfers and cumulative overflow. Broader mode/module acceptance remains open.',
|
||||
evidence: ['game/discovery-module-profile.ts', 'game/richese-no-field.ts', 'game/discoveries.ts', 'game/discovery-actions.ts', 'game/discovery-entry.ts', 'game/discovery-collection.ts', 'game/board-resolution-quote.ts', 'game/discovery-flight.ts', 'game/discovery-battle.ts', 'game/discovery-storm.ts', 'game/shrine.ts', 'game/great-maker.ts', 'game/board.ts', 'game/engine.ts'],
|
||||
detail: 'Concealed No-Fields now follow provisional destructive revelation at a printed Discovery blow through a distinct cause and all-player preflight; only actual reserve-capped counters are lost before original placement. Original classic and supported native Skills/Nexus/Discovery entries retain their selected modules and controls. Jacurutu uses an exact defeated-player reading before native settlement; Testing Station queues cumulative choices before one traversal. Cistern pays every fighter occupant. Orgiz uses actual ordinary receipts and bilateral shared allocations in both modes, grouping by territory in Advanced and positive source deposit in Basic without guessed payers or debt. Available balances track all transfers and cumulative overflow. Broader mode/module acceptance remains open.',
|
||||
evidence: ['game/disaster-preflight.ts', 'game/discovery-module-profile.ts', 'game/richese-no-field.ts', 'game/discoveries.ts', 'game/discovery-actions.ts', 'game/discovery-entry.ts', 'game/discovery-collection.ts', 'game/board-resolution-quote.ts', 'game/discovery-flight.ts', 'game/discovery-battle.ts', 'game/discovery-storm.ts', 'game/shrine.ts', 'game/great-maker.ts', 'game/board.ts', 'game/engine.ts'],
|
||||
},
|
||||
{
|
||||
area: 'Player controls', status: 'Partial',
|
||||
@@ -5761,13 +5762,13 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
},
|
||||
{
|
||||
area: 'Documentation', status: 'Partial',
|
||||
detail: 'Sourced identities and native/classic-skill boundaries are recorded. Advanced Orgiz uses territory Collection; Basic retains its provisional deposit mapping, including actual source reductions after bilateral shared settlement. Recommended occupant/payer ordering, Jacurutu allocation and cumulative Testing Station choices are visible interpretations, not publisher rulings. Great Maker and entry/source-storm inferences remain explicit; full acceptance stays open.',
|
||||
evidence: ['docs/DISCOVERY_COMPONENTS.md', 'docs/DISCOVERY_PROTOTYPE.md'],
|
||||
detail: 'The destructive No-Field reveal and separate nested-territory interpretation are explicit provisional compositions, without a specific combined publisher ruling. Sourced identities and native/classic-skill boundaries are recorded. Advanced Orgiz uses territory Collection; Basic retains its provisional deposit mapping, including actual source reductions after bilateral shared settlement. Recommended occupant/payer ordering, Jacurutu allocation and cumulative Testing Station choices are visible interpretations, not publisher rulings. Great Maker and entry/source-storm inferences remain explicit; full acceptance stays open.',
|
||||
evidence: ['docs/DISCOVERY_COMPONENTS.md', 'docs/DISCOVERY_PROTOTYPE.md', 'docs/RICHESE_NO_FIELD_RULES.md'],
|
||||
},
|
||||
{
|
||||
area: 'Verification', status: 'Partial',
|
||||
detail: 'Independent Discovery adds frozen15-file118/118, types/lint/build and six original-action/rendered-control programmes; broader modes, visual/deployed acceptance and final assurance remain open. Current Collection batch passes80/80 in6 related files, types/lint and build. Genuine Basic/Advanced cases cover shared equal/full/zero splits, acceptance timing, wrong actors and stale events; helper boundaries cover multiple deposits, exhausted recipients, self/zero-share exclusion and cumulative overflow. Actual390px Basic Red Chasm lot4 changes Ecaz7/Guild8 to8/11 with one transfer and Ready restored, no overflow or errors. Four legal policies complete that Basic allocation via actual actions. Earlier canonical-source and Jacurutu/Station evidence remains source-bound; combinations, assurance, strategy and deployed acceptance remain open.',
|
||||
evidence: ['tests/independent-discovery-nexus-entry.test.ts', 'tests/independent-discovery-cards.test.ts', 'tests/independent-discovery-battles.test.ts', 'tests/mixed-nexus-modules-runtime.test.ts', 'tests/paired-e3-nexus-modules-runtime.test.ts', 'tests/sample-custody.test.ts', 'tests/ambassador-terror-overlap-engine.test.ts', 'tests/e3-nexus-ecaz-runtime.test.ts', 'tests/e3-nexus-moritani-runtime.test.ts', 'tests/single-nexus-e1-runtime.test.ts', 'tests/single-nexus-e2-runtime.test.ts', 'tests/homeworld-paired-nexus-runtime.test.ts', 'tests/homeworld-standalone-nexus-runtime.test.ts',
|
||||
evidence: ['tests/fixture-discovery-no-field-blow.ts', 'tests/discovery-no-field-blow-engine.test.ts', 'tests/discovery-no-field-blow-preflight.test.ts', 'tests/independent-discovery-nexus-entry.test.ts', 'tests/independent-discovery-cards.test.ts', 'tests/independent-discovery-battles.test.ts', 'tests/mixed-nexus-modules-runtime.test.ts', 'tests/paired-e3-nexus-modules-runtime.test.ts', 'tests/sample-custody.test.ts', 'tests/ambassador-terror-overlap-engine.test.ts', 'tests/e3-nexus-ecaz-runtime.test.ts', 'tests/e3-nexus-moritani-runtime.test.ts', 'tests/single-nexus-e1-runtime.test.ts', 'tests/single-nexus-e2-runtime.test.ts', 'tests/homeworld-paired-nexus-runtime.test.ts', 'tests/homeworld-standalone-nexus-runtime.test.ts',
|
||||
'tests/fixture-jacurutu-mixed-dial.ts', 'tests/jacurutu-mixed-dial-runtime.test.ts', 'tests/fixture-testing-station-contest.ts', 'tests/testing-station-contest-runtime.test.ts',
|
||||
'tests/fixture-discovery-occupied-collection.ts', 'tests/discovery-occupied-collection-runtime.test.ts', 'tests/fixture-discovery-shared-orgiz.ts', 'tests/discovery-shared-orgiz-runtime.test.ts',
|
||||
'tests/board-resolution-quote.test.ts', 'tests/ecaz-collection-quote.test.ts',
|
||||
|
||||
@@ -26,7 +26,8 @@ export type NoFieldRevealCause =
|
||||
| 'beforeAlly'
|
||||
| 'allyShipment'
|
||||
| 'atomics'
|
||||
| 'alliedSeparation';
|
||||
| 'alliedSeparation'
|
||||
| 'discoveryBlow';
|
||||
export type NoFieldReveal = {
|
||||
state: RicheseNoField;
|
||||
tokenId: string;
|
||||
@@ -263,6 +264,7 @@ export function revealRicheseNoField(
|
||||
'allyShipment',
|
||||
'atomics',
|
||||
'alliedSeparation',
|
||||
'discoveryBlow',
|
||||
].includes(input.cause),
|
||||
'Choose a valid No-Field reveal cause.',
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user