Resume storm traversal after surviving advisor conversion
Preserve current-sector losses and original Fremen casualty priority before the mandatory BG counter. Bind public sector facts to a private continuation and resume only the remaining storm once.
This commit is contained in:
1 parent
9959f1b347
commit
d66b594770
13 files changed
+869
-17
No files matched your search
@@ -1,6 +1,7 @@
|
||||
import type { FactionId } from './catalog';
|
||||
import { gameTerritories, splitLocation, type MobileBoard } from './board';
|
||||
import { presenceAt, type ForcePresence } from './force-presence';
|
||||
import { stormExposesTerritory, stormSectorAfter } from './disaster-rules';
|
||||
|
||||
export type AdvisorStance = {
|
||||
lockedTurn?: number;
|
||||
@@ -59,6 +60,14 @@ export type AutomaticAdvisorFaceDance = {
|
||||
returned: Record<string, number>;
|
||||
};
|
||||
|
||||
export type AutomaticAdvisorStorm = {
|
||||
event: string;
|
||||
from: number;
|
||||
distance: number;
|
||||
traversed: number;
|
||||
sector: number;
|
||||
};
|
||||
|
||||
export type AutomaticAdvisorFlipSource = {
|
||||
event: string;
|
||||
owner: string;
|
||||
@@ -73,6 +82,7 @@ export type AutomaticAdvisorFlipSource = {
|
||||
| { producer: 'gamont'; gamont: AutomaticAdvisorGamont }
|
||||
| { producer: 'battleLosses'; battle: AutomaticAdvisorBattle }
|
||||
| { producer: 'faceDance'; faceDance: AutomaticAdvisorFaceDance }
|
||||
| { producer: 'storm'; storm: AutomaticAdvisorStorm }
|
||||
);
|
||||
|
||||
export type AutomaticAdvisorFlipPlayer = ForcePresence & {
|
||||
@@ -104,6 +114,14 @@ export type AutomaticAdvisorFlipContext = AdvisorSettlementContext & MobileBoard
|
||||
} | null;
|
||||
battle?: unknown;
|
||||
homeworlds?: unknown;
|
||||
storm?: number;
|
||||
shieldWallDestroyed?: boolean;
|
||||
stormResolution?: {
|
||||
from: number;
|
||||
distance: number;
|
||||
traversed: number;
|
||||
pending: readonly string[];
|
||||
} | null;
|
||||
};
|
||||
|
||||
export class AutomaticAdvisorFlipError extends Error {
|
||||
@@ -137,7 +155,7 @@ function otherPresence(ctx: AdvisorSettlementContext, player: AutomaticAdvisorFl
|
||||
|
||||
function currentWindow(ctx: AutomaticAdvisorFlipContext) {
|
||||
return ctx.advanced === true && ctx.status === 'playing' &&
|
||||
(ctx.phase === 5 || ctx.phase === 6 || ctx.phase === 8) && positive(ctx.turn);
|
||||
(ctx.phase === 0 || ctx.phase === 5 || ctx.phase === 6 || ctx.phase === 8) && positive(ctx.turn);
|
||||
}
|
||||
|
||||
/** A supported committed producer opens a response for these current groups.
|
||||
@@ -367,6 +385,29 @@ function validateFaceDanceSource(
|
||||
}
|
||||
}
|
||||
|
||||
function validateStormSource(
|
||||
ctx: AutomaticAdvisorFlipContext,
|
||||
source: Extract<AutomaticAdvisorFlipSource, { producer: 'storm' }>,
|
||||
) {
|
||||
const receipt = source.storm;
|
||||
requireFlip(record(receipt) &&
|
||||
Object.keys(receipt).sort().join(',') === 'distance,event,from,sector,traversed' &&
|
||||
identifier(receipt.event) && nonnegative(receipt.from) && receipt.from <= 18 &&
|
||||
ctx.storm === receipt.from && positive(receipt.distance) && receipt.distance <= 40 &&
|
||||
positive(receipt.traversed) && receipt.traversed <= receipt.distance &&
|
||||
positive(receipt.sector) && receipt.sector <= 18 &&
|
||||
receipt.sector === stormSectorAfter(receipt.from, receipt.traversed),
|
||||
'Automatic advisor conversion lost its exact public current-sector storm receipt.');
|
||||
const current = ctx.stormResolution;
|
||||
requireFlip(record(current) && current.from === receipt.from && current.distance === receipt.distance &&
|
||||
current.traversed === receipt.traversed && Array.isArray(current.pending) && current.pending.length === 0,
|
||||
'Automatic advisor conversion must finish the original current-sector storm casualties first.');
|
||||
const place = gameTerritories(ctx).find((candidate) => candidate.id === source.territory);
|
||||
requireFlip(place && place.sectors.includes(receipt.sector) &&
|
||||
stormExposesTerritory(place.id, ctx.shieldWallDestroyed === true),
|
||||
'Automatic advisor conversion needs its current exposed storm territory.');
|
||||
}
|
||||
|
||||
/** Validate the same committed producer and exact held group before either response outcome. */
|
||||
export function validateAutomaticAdvisorFlip(
|
||||
ctx: AutomaticAdvisorFlipContext,
|
||||
@@ -384,10 +425,12 @@ export function validateAutomaticAdvisorFlip(
|
||||
(source.producer === 'battleLosses' &&
|
||||
Object.keys(source).sort().join(',') === 'battle,event,forces,owner,phase,producer,territory,turn') ||
|
||||
(source.producer === 'faceDance' &&
|
||||
Object.keys(source).sort().join(',') === 'event,faceDance,forces,owner,phase,producer,territory,turn')) &&
|
||||
Object.keys(source).sort().join(',') === 'event,faceDance,forces,owner,phase,producer,territory,turn') ||
|
||||
(source.producer === 'storm' &&
|
||||
Object.keys(source).sort().join(',') === 'event,forces,owner,phase,producer,storm,territory,turn')) &&
|
||||
identifier(source.event) && identifier(source.owner) && identifier(source.territory) &&
|
||||
positive(source.turn) && source.turn === ctx.turn &&
|
||||
source.phase === (source.producer === 'gamont' ? 8 :
|
||||
source.phase === (source.producer === 'storm' ? 0 : source.producer === 'gamont' ? 8 :
|
||||
source.producer === 'battleLosses' || source.producer === 'faceDance' ? 6 : 5) &&
|
||||
currentWindow(ctx) && source.phase === ctx.phase,
|
||||
'Automatic advisor conversion lost its original current-turn committed producer.');
|
||||
@@ -406,7 +449,8 @@ export function validateAutomaticAdvisorFlip(
|
||||
else if (source.producer === 'homeworldTransport') validateHomeworldTransportSource(ctx, source);
|
||||
else if (source.producer === 'gamont') validateGamontSource(ctx, source);
|
||||
else if (source.producer === 'battleLosses') validateBattleSource(ctx, source);
|
||||
else validateFaceDanceSource(ctx, source);
|
||||
else if (source.producer === 'faceDance') validateFaceDanceSource(ctx, source);
|
||||
else validateStormSource(ctx, source);
|
||||
const forces = physicalGroup(owner, source.territory);
|
||||
requireFlip(record(source.forces) && Object.keys(forces).length > 0 &&
|
||||
Object.values(forces).every(positive) && Object.values(source.forces).every(positive) &&
|
||||
|
||||
+76
-3
@@ -12,7 +12,7 @@ import { advancedMoritaniLeaderSkillsProfile, selectedDeckLeaderSkillsProfile, h
|
||||
import { bribeTimingBlock, maximumBribe, type BribeOptions } from './bribe-options';
|
||||
import { quoteAtreidesFullPlan } from './atreides-full-plan';
|
||||
import { quoteTleilaxuSpecialKarama } from './tleilaxu-special-karama';
|
||||
import { quoteAutomaticAdvisorFlips, validateAutomaticAdvisorFlip, AutomaticAdvisorFlipError, type AdvisorStance, type AutomaticAdvisorFlipSource, type AutomaticAdvisorTransport, type AutomaticAdvisorGamont, type AutomaticAdvisorBattle, type AutomaticAdvisorHomeworldTransport, type AutomaticAdvisorFaceDance } from './advisor-automatic-flip';
|
||||
import { quoteAutomaticAdvisorFlips, validateAutomaticAdvisorFlip, AutomaticAdvisorFlipError, type AdvisorStance, type AutomaticAdvisorFlipSource, type AutomaticAdvisorTransport, type AutomaticAdvisorGamont, type AutomaticAdvisorBattle, type AutomaticAdvisorHomeworldTransport, type AutomaticAdvisorFaceDance, type AutomaticAdvisorStorm } from './advisor-automatic-flip';
|
||||
import { quoteSpicePlacement, stormExposesTerritory, stormSectorAfter, wormConsumesForces } from './disaster-rules';
|
||||
import { isStormCardDistance, type StormCardComponent } from './storm-cards';
|
||||
import { createLeaderSkills, validateLeaderSkills, dealLeaderSkills, chooseLeaderSkill, returnDeadLeaderSkills, offerRevivedLeaderSkill, drawRevivedLeaderSkills, declineRevivedLeaderSkill, LeaderSkillError, type LeaderSkillsState, type LeaderSkillsView } from './leader-skills';
|
||||
@@ -1277,6 +1277,7 @@ export type Game = {
|
||||
signature: string;
|
||||
};
|
||||
pendingAutomaticAdvisorFaceDance?: AutomaticAdvisorFaceDance;
|
||||
pendingAutomaticAdvisorStorm?: { receipt: AutomaticAdvisorStorm; ready: boolean; cursor?: string };
|
||||
advisorPreparation?: AdvisorPreparationWindow;
|
||||
ecazOccupyPreview?: boolean;
|
||||
/** Fresh native Guild/Nexus payment-replacement profile, never inferred from a save. */
|
||||
@@ -11539,6 +11540,8 @@ function continueStorm(g: Game) {
|
||||
if (g.decision) return;
|
||||
}
|
||||
}
|
||||
// All current-sector casualties finish before conversion or the next step.
|
||||
if (openStormAdvisorFlips(g)) return;
|
||||
if (resolution.traversed === resolution.distance) break;
|
||||
resolution.traversed++;
|
||||
const s = stormSectorAfter(resolution.from, resolution.traversed);
|
||||
@@ -11578,6 +11581,8 @@ function continueStorm(g: Game) {
|
||||
}
|
||||
for (const key of Object.keys(g.spice))
|
||||
if (splitLocation(key).sector === s) delete g.spice[key];
|
||||
// Retain surviving advisor stances through any pending Fremen choices.
|
||||
prepareStormAdvisorFlips(g);
|
||||
}
|
||||
if (g.ecologicalStorm?.stage === 'traversal')
|
||||
g.ecologicalStorm = nexusRule(() => finishDiscoveryStorm(g.ecologicalStorm!));
|
||||
@@ -20329,6 +20334,12 @@ function currentAutomaticAdvisorFlip(g: Game, response: ResponseWindow) {
|
||||
'The mandatory conversion has lost its original completed Face Dance.');
|
||||
validateCompletedFaceDance(g, parent);
|
||||
}
|
||||
if (source.producer === 'storm') {
|
||||
const parent = g.pendingAutomaticAdvisorStorm;
|
||||
requireRule(parent && JSON.stringify(source.storm) === JSON.stringify(parent.receipt),
|
||||
'The mandatory conversion has lost its original storm sector.');
|
||||
validateStormAdvisorCursor(g, parent);
|
||||
}
|
||||
automaticAdvisorRule(() => validateAutomaticAdvisorFlip(g, source));
|
||||
return source;
|
||||
}
|
||||
@@ -20338,6 +20349,7 @@ function openAutomaticAdvisorFlip(g: Game) {
|
||||
if (!source) return;
|
||||
// Hold immediate losses through the original mandatory discard/casualty work.
|
||||
if (source.producer === 'battleLosses' && !g.pendingAutomaticAdvisorBattle?.resume) return;
|
||||
if (source.producer === 'storm' && !g.pendingAutomaticAdvisorStorm?.ready) return;
|
||||
automaticAdvisorRule(() => validateAutomaticAdvisorFlip(g, source));
|
||||
g.response = { kind: 'advisorFlip', owner: source.owner, location: source.territory,
|
||||
advisors: false, passed: [], automaticAdvisorFlip: source,
|
||||
@@ -20593,6 +20605,66 @@ function finishAutomaticAdvisorFaceDance(g: Game) {
|
||||
delete g.pendingAutomaticAdvisorFaceDance;
|
||||
finishBattle(g);
|
||||
}
|
||||
function stormAdvisorCursor(g: Game) {
|
||||
return JSON.stringify([g.stormResolution, g.stormMovementSource ?? null]);
|
||||
}
|
||||
function prepareStormAdvisorFlips(g: Game) {
|
||||
const resolution = g.stormResolution;
|
||||
if (!g.advanced || g.status !== 'playing' || g.phase !== 0 || !resolution || resolution.traversed <= 0) return;
|
||||
const sector = stormSectorAfter(resolution.from, resolution.traversed);
|
||||
const groups = automaticAdvisorRule(() => quoteAutomaticAdvisorFlips(g)).filter(group =>
|
||||
gameTerritories(g).some(place => place.id === group.territory && place.sectors.includes(sector) &&
|
||||
stormExposesTerritory(place.id, g.shieldWallDestroyed)));
|
||||
if (!groups.length) return;
|
||||
let parent = g.pendingAutomaticAdvisorStorm;
|
||||
if (parent) requireRule(!parent.ready && parent.receipt.from === resolution.from &&
|
||||
parent.receipt.distance === resolution.distance && parent.receipt.traversed === resolution.traversed,
|
||||
'The storm conversion must retain its original current sector.');
|
||||
else {
|
||||
requireRule(!g.pendingAutomaticAdvisorFlips?.length && !g.pendingAutomaticAdvisorTransport &&
|
||||
!g.pendingAutomaticAdvisorHomeworldTransport && !g.pendingAutomaticAdvisorGamont &&
|
||||
!g.pendingAutomaticAdvisorBattle && !g.pendingAutomaticAdvisorFaceDance,
|
||||
'Finish the original advisor conversion before another storm sector.');
|
||||
parent = { receipt: { event: crypto.randomUUID(), from: resolution.from,
|
||||
distance: resolution.distance, traversed: resolution.traversed, sector }, ready: false };
|
||||
g.pendingAutomaticAdvisorStorm = parent;
|
||||
}
|
||||
for (const group of groups) {
|
||||
const source: AutomaticAdvisorFlipSource = { event: crypto.randomUUID(), owner: group.player,
|
||||
territory: group.territory, turn: g.turn, phase: g.phase, forces: group.forces,
|
||||
producer: 'storm', storm: parent.receipt };
|
||||
getPlayer(g, group.player).advisors![group.territory].aloneFlipHold = source.event;
|
||||
(g.pendingAutomaticAdvisorFlips ??= []).push(source);
|
||||
}
|
||||
}
|
||||
function openStormAdvisorFlips(g: Game) {
|
||||
prepareStormAdvisorFlips(g);
|
||||
const parent = g.pendingAutomaticAdvisorStorm;
|
||||
if (!parent) return false;
|
||||
requireRule(!parent.ready && !g.response && !g.decision && g.stormResolution?.pending.length === 0 &&
|
||||
!!g.pendingAutomaticAdvisorFlips?.length,
|
||||
'Finish all original current-sector casualties before advisor conversion.');
|
||||
parent.ready = true;
|
||||
parent.cursor = stormAdvisorCursor(g);
|
||||
openAutomaticAdvisorFlip(g);
|
||||
return true;
|
||||
}
|
||||
function validateStormAdvisorCursor(g: Game, parent: NonNullable<Game['pendingAutomaticAdvisorStorm']>) {
|
||||
const resolution = g.stormResolution, receipt = parent.receipt;
|
||||
requireRule(g.status === 'playing' && g.phase === 0 && parent.ready && resolution &&
|
||||
resolution.pending.length === 0 && resolution.from === receipt.from &&
|
||||
resolution.distance === receipt.distance && resolution.traversed === receipt.traversed &&
|
||||
parent.cursor === stormAdvisorCursor(g),
|
||||
'The mandatory conversion lost its original completed-sector storm cursor.');
|
||||
}
|
||||
function finishAutomaticAdvisorStorm(g: Game) {
|
||||
const parent = g.pendingAutomaticAdvisorStorm;
|
||||
if (!parent) return;
|
||||
requireRule(!g.pendingAutomaticAdvisorFlips?.length, 'Finish all original sector conversions before storm traversal.');
|
||||
validateStormAdvisorCursor(g, parent);
|
||||
delete g.pendingAutomaticAdvisorStorm;
|
||||
continueStorm(g);
|
||||
}
|
||||
function applyAdvisorReleases(g: Game, released: AdvisorRelease[]) {
|
||||
for (const entry of released)
|
||||
delete getPlayer(g, entry.player).advisors?.[entry.territory];
|
||||
@@ -22613,7 +22685,7 @@ function decideSharedSpice(g: Game, id: string, action: Action) {
|
||||
function victory(g: Game, quote?: VictoryQuote) {
|
||||
requireRule(!g.pendingAutomaticAdvisorFlips?.length && !g.pendingAutomaticAdvisorTransport &&
|
||||
!g.pendingAutomaticAdvisorGamont && !g.pendingAutomaticAdvisorBattle && !g.pendingAutomaticAdvisorHomeworldTransport &&
|
||||
!g.pendingAutomaticAdvisorFaceDance,
|
||||
!g.pendingAutomaticAdvisorFaceDance && !g.pendingAutomaticAdvisorStorm,
|
||||
'Resolve mandatory advisor conversions before checking victory.');
|
||||
quote ??= currentVictoryQuote(g);
|
||||
applyAdvisorReleases(g, quote.released);
|
||||
@@ -28435,7 +28507,7 @@ function finishResponse(g: Game, canceled: boolean, settlement?: BureaucratSettl
|
||||
}
|
||||
if (automaticAdvisorFlip) {
|
||||
if (canceled)
|
||||
log(g, `${bg.name}'s mandatory conversion in ${territory(to).name} was prevented. The completed ${automaticAdvisorFlip.producer === 'guildTransport' || automaticAdvisorFlip.producer === 'homeworldTransport' ? 'transport and payment stay' : automaticAdvisorFlip.producer === 'gamont' ? 'Trip to Gamont effect and card use stay' : automaticAdvisorFlip.producer === 'battleLosses' ? 'battle losses and costs stay' : automaticAdvisorFlip.producer === 'faceDance' ? 'Face Dance replacement and reveal stay' : 'movement stays'} committed; this group remains advisors while continuously alone.`,
|
||||
log(g, `${bg.name}'s mandatory conversion in ${territory(to).name} was prevented. The completed ${automaticAdvisorFlip.producer === 'guildTransport' || automaticAdvisorFlip.producer === 'homeworldTransport' ? 'transport and payment stay' : automaticAdvisorFlip.producer === 'gamont' ? 'Trip to Gamont effect and card use stay' : automaticAdvisorFlip.producer === 'battleLosses' ? 'battle losses and costs stay' : automaticAdvisorFlip.producer === 'faceDance' ? 'Face Dance replacement and reveal stay' : automaticAdvisorFlip.producer === 'storm' ? 'current-sector storm casualties stay' : 'movement stays'} committed; this group remains advisors while continuously alone.`,
|
||||
{ faction: bg.faction, name: 'Mandatory advisor conversion prevented' });
|
||||
g.pendingAutomaticAdvisorFlips!.shift();
|
||||
if (!g.pendingAutomaticAdvisorFlips!.length) delete g.pendingAutomaticAdvisorFlips;
|
||||
@@ -28445,6 +28517,7 @@ function finishResponse(g: Game, canceled: boolean, settlement?: BureaucratSettl
|
||||
finishAutomaticAdvisorBattle(g);
|
||||
finishAutomaticAdvisorHomeworldTransport(g);
|
||||
finishAutomaticAdvisorFaceDance(g);
|
||||
finishAutomaticAdvisorStorm(g);
|
||||
} else openAutomaticAdvisorFlip(g);
|
||||
} else finishAdvisorReaction(g, response);
|
||||
}
|
||||
|
||||
@@ -186,6 +186,7 @@ const cancellationSources = {
|
||||
battle: response.automaticAdvisorFlip.producer === 'battleLosses' ? g.pendingAutomaticAdvisorBattle?.receipt ?? null : null,
|
||||
homeworld: response.automaticAdvisorFlip.producer === 'homeworldTransport' ? g.pendingAutomaticAdvisorHomeworldTransport?.receipt ?? null : null,
|
||||
faceDance: response.automaticAdvisorFlip.producer === 'faceDance' ? g.pendingAutomaticAdvisorFaceDance ?? null : null,
|
||||
storm: response.automaticAdvisorFlip.producer === 'storm' ? g.pendingAutomaticAdvisorStorm?.receipt ?? null : null,
|
||||
group: g.players.find(p => p.id === response.owner)?.advisors?.[response.location ?? ''] ?? null }
|
||||
: response.advisorPreparationSource
|
||||
? { preparation: response.advisorPreparationSource, window: g.advisorPreparation ?? null }
|
||||
|
||||
+13
-6
@@ -4320,7 +4320,8 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
'After actual battle losses leave surviving bystanding advisors alone, Karama may prevent their conversion without undoing losses or costs. Mandatory card disposal, original skills and substitution finish first. Provisionally resolve this before optional winner-card cleanup and later aftermath, then resume the original cleanup once. An explosion that destroys the advisors creates no conversion.',
|
||||
'Native Guild board-to-Homeworld and Junction board transfers can leave advisors alone after payment and physical transfer. Karama prevents only the resulting BG conversion; Junction permission and tariff stay immune. Original income resolves first, then any Junction board arrival resumes once. Original declaration snapshots stay private.',
|
||||
'A completed Face Dance can leave advisors alone at remote board sources or, with zero replacement, at the battle target. Original force returns, replacement, leader death, revelation and any card cycle finish first. Karama can then prevent only the BG conversion; native Face Dance remains immune. Afterward the original aftermath resumes once.',
|
||||
'Provisional cancellation duration: a prevented advisor group stays advisors while continuously alone, including across phase and turn changes. Another faction returning or the group becoming empty ends that held occurrence. Later arrivals joining that same group match its stance. Other non-ground automatic conversions and matching-group flip counters remain unfinished.',
|
||||
'After a storm sector removes the last outsider, surviving advisors elsewhere in that territory must convert. Provisionally finish every current-sector casualty choice first, then offer Karama prevention before the remaining traversal. Completed losses stay committed. Later sectors can still destroy advisors or fighters; advisors destroyed in the same sector create no conversion.',
|
||||
'Provisional cancellation duration: a prevented advisor group stays advisors while continuously alone, including across phase and turn changes. Another faction returning or the group becoming empty ends that held occurrence. Later arrivals joining that same group match its stance. Special Ambassador/Discovery relocation and matching-group flip counters remain unfinished.',
|
||||
'When another faction ships, moves or worm rides into your fighters, immediately choose whether to become advisors. The choice opens a Karama response. Repeated entry can offer a new choice.',
|
||||
'After another faction’s off-planet shipment, choose a free force in the Polar Sink or an accompanying force at the shipment destination. An accompanying advisor group cannot flip to fighters in that turn while other factions remain.',
|
||||
'After Spice Blow and Nexus, use Prepare advisors for battle during Charity, Bidding, Revival or before the first actual shipment. Quiet boundaries retain current mandatory priority. Each whole-territory flip has its original Karama response; its direct continuation preserves the current phase, auction, readiness and active movement. Declining the initial phase-five chooser leaves this window open. Ordinary allied presence, storm and fresh-advisor locks still restrict eligibility; the recorded supported Ecaz co-side composition remains separate.',
|
||||
@@ -4331,7 +4332,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
{
|
||||
"area": "Implementation",
|
||||
"status": "Partial",
|
||||
"detail": "Completed Face Dance now captures alone advisors at all positive board sources and its target, preserving return/replacement, leader death and revelation/cycling before one original aftermath. Its public physical/battle receipt omits Face Dancer identities and does not reuse pre-reveal availability. Existing ground, Guild/Homeworld/Junction transport, Gamont, battle and preparation sources retain their contracts. Storm and other special relocation/matching-group causes remain unfinished.",
|
||||
"detail": "Storm traversal now captures surviving alone advisors after current-sector losses and retains them through original Fremen casualty choices. A public sector receipt binds a private ready/cursor parent; consume it before continuing once. Existing ground, transport, Gamont, battle and Face Dance contracts remain. Special relocation and matching-group causes remain unfinished.",
|
||||
"evidence": [
|
||||
"game/advisor-automatic-flip.ts",
|
||||
"game/advisors.ts",
|
||||
@@ -4346,7 +4347,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
{
|
||||
"area": "Player controls",
|
||||
"status": "Partial",
|
||||
"detail": "Standard Karama/Allow controls distinguish completed Face Dance from the pending BG conversion. Existing transport, ground, Gamont, battle and phase2–5 preparation guidance remain. No new confirmation or timer.",
|
||||
"detail": "Standard Karama/Allow controls identify completed current-sector casualties and the remaining traversal. Prevention keeps actual losses; later sectors may still destroy the group. Existing producer and preparation guidance remains, without a new confirmation or timer.",
|
||||
"evidence": [
|
||||
"docs/ADVISOR_AUTOMATIC_FLIPS.md",
|
||||
"components/advisor-preparation.tsx",
|
||||
@@ -4357,8 +4358,11 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
{
|
||||
"area": "AI",
|
||||
"status": "Partial",
|
||||
"detail": "Eight existing-profile native remote/zero Face Dance response paths resolve legally through actual Collection, preserving completed return, death, revelation and replacement. Earlier producer paths retain separate evidence. No strategy refinement or difficulty calibration.",
|
||||
"detail": "Eight existing-profile native ending/continuing Storm conversion paths resolve legally through the original next phase. Completed current-sector losses remain; a later sector can still destroy BG. Earlier producer paths retain separate evidence. No strategy refinement or difficulty calibration.",
|
||||
"evidence": [
|
||||
"tests/storm-worm-cancellation.test.ts",
|
||||
"tests/fixture-advisor-storm-flip.ts",
|
||||
"tests/advisor-storm-flip-engine.test.ts",
|
||||
"tests/advisor-face-dance-flip-engine.test.ts",
|
||||
"tests/fixture-advisor-face-dance-flip.ts",
|
||||
"tests/advisor-homeworld-flip-engine.test.ts",
|
||||
@@ -4375,7 +4379,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
{
|
||||
"area": "Documentation",
|
||||
"status": "Partial",
|
||||
"detail": "Physical17/28 E composes the group counter with physical38 A after completed return/replacement. Face Dance immunity, actual returns, remote sources, zero replacement, original card cycle and private holdings are distinguished from the BG response. Continuously-alone duration remains provisional; other producers/combinations stay explicit.",
|
||||
"detail": "Physical17/28 E composes the mandatory counter with current-sector losses. The selected priority after all current-sector casualties and before remaining traversal is explicitly provisional, as is continuously-alone cancellation duration. Public sector facts stay separate from private forecasts/protection/continuation.",
|
||||
"evidence": [
|
||||
"docs/ADVISOR_AUTOMATIC_FLIPS.md",
|
||||
"docs/ADVISOR_PREPARATION.md",
|
||||
@@ -4386,8 +4390,11 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
{
|
||||
"area": "Verification",
|
||||
"status": "Partial",
|
||||
"detail": "Face Dance passes frozen16-file201/201, types/lint/build, five native Face Dance/response/GameTable SSR journeys and eight profile paths. Genuine67/66-action sources earn original Ix47, native draw, shipment, moves and battle. Both outcomes preserve nine returned survivors and one leader death; actual BG city income is2/0. Original decline stays intact. A fixture deck-count correction changes no cards/rules. Full cycle/Skills/Ecaz co-side/Homeworld/Nexus combinations, storm/special relocation, complete modes and browser/mobile/deployed/comprehensive acceptance remain open.",
|
||||
"detail": "Storm conversion passes frozen17-file220/220, types/lint/build, five native Storm/response/GameTable SSR journeys and eight profile paths. Genuine48-action sources earn base33, first dials, paid shipment/Intrusion and native turn-two Cards3/4. Ending9 preserves BG10; continuing10 destroys it after either response. Same-sector destruction has no conversion; controlled mixed-Fremen choices retain priority. Type feedback repairs only an empty-array fixture assertion. Wider combinations, special relocation/matching-group interpretation, complete modes and callback/browser/mobile/deployed/comprehensive acceptance remain open.",
|
||||
"evidence": [
|
||||
"tests/storm-worm-cancellation.test.ts",
|
||||
"tests/fixture-advisor-storm-flip.ts",
|
||||
"tests/advisor-storm-flip-engine.test.ts",
|
||||
"tests/advisor-face-dance-flip-engine.test.ts",
|
||||
"tests/fixture-advisor-face-dance-flip.ts",
|
||||
"tests/advisor-homeworld-flip-engine.test.ts",
|
||||
|
||||
Reference in new issue
Block a user