Preserve committed Guild transport through advisor conversion responses
Offer mandatory advisor conversion prevention after Guild cross-shipment and reserve returns. Keep original permission/rate/income, payment, physical pieces and shipment use; bind the exact group to one transport parent and resume original arrival/Intrusion once. Ordinary movement stays unused and the existing provisional continuously-alone duration remains. Preserve old ground receipts, private projection behavior and all transport/module guards. Validation: 17 files243/243, types/lint/build, eight native transport/response/GameTable SSR journeys and eight existing-profile paths. Genuine36/42-action sources earn original counters/cards and actual paid-shipment Intrusion. Preserve the unaffordable full-price cross outcome and prove the affordable return. No saved-room/D1/restart/deployment operation; broader producers, full modes and browser/deployed acceptance remain open.
This commit is contained in:
1 parent
3aca64412f
commit
d0f52c7bae
13 files changed
+805
-43
No files matched your search
@@ -1,5 +1,5 @@
|
||||
import type { FactionId } from './catalog';
|
||||
import { splitLocation } from './board';
|
||||
import { gameTerritories, splitLocation, type MobileBoard } from './board';
|
||||
import { presenceAt, type ForcePresence } from './force-presence';
|
||||
|
||||
export type AdvisorStance = {
|
||||
@@ -7,23 +7,36 @@ export type AdvisorStance = {
|
||||
aloneFlipHold?: string;
|
||||
};
|
||||
|
||||
export type AutomaticAdvisorTransport = {
|
||||
event: string;
|
||||
player: string;
|
||||
origin: string;
|
||||
to: string;
|
||||
sector: number;
|
||||
amount: number;
|
||||
elite: number;
|
||||
group: Record<string, number>;
|
||||
};
|
||||
|
||||
export type AutomaticAdvisorFlipSource = {
|
||||
event: string;
|
||||
owner: string;
|
||||
territory: string;
|
||||
turn: number;
|
||||
phase: number;
|
||||
producer: 'groundMovement';
|
||||
mover: string;
|
||||
move: number;
|
||||
forces: Record<string, number>;
|
||||
};
|
||||
} & (
|
||||
| { producer: 'groundMovement'; mover: string; move: number }
|
||||
| { producer: 'guildTransport'; transport: AutomaticAdvisorTransport }
|
||||
);
|
||||
|
||||
export type AutomaticAdvisorFlipPlayer = ForcePresence & {
|
||||
id: string;
|
||||
faction: FactionId;
|
||||
advisors?: Readonly<Record<string, AdvisorStance>>;
|
||||
moved?: number;
|
||||
shipped?: boolean;
|
||||
reserves?: number;
|
||||
};
|
||||
|
||||
export type AdvisorSettlementContext = {
|
||||
@@ -31,7 +44,7 @@ export type AdvisorSettlementContext = {
|
||||
players: readonly AutomaticAdvisorFlipPlayer[];
|
||||
};
|
||||
|
||||
export type AutomaticAdvisorFlipContext = AdvisorSettlementContext & {
|
||||
export type AutomaticAdvisorFlipContext = AdvisorSettlementContext & MobileBoard & {
|
||||
status: string;
|
||||
turn: number;
|
||||
phase: number;
|
||||
@@ -48,6 +61,8 @@ const identifier = (value: unknown): value is string =>
|
||||
typeof value === 'string' && value.trim().length > 0;
|
||||
const positive = (value: unknown): value is number =>
|
||||
Number.isSafeInteger(value) && (value as number) > 0;
|
||||
const nonnegative = (value: unknown): value is number =>
|
||||
Number.isSafeInteger(value) && (value as number) >= 0;
|
||||
const record = (value: unknown): value is Record<string, unknown> =>
|
||||
!!value && typeof value === 'object' && !Array.isArray(value);
|
||||
function requireFlip(value: unknown, message: string): asserts value {
|
||||
@@ -68,7 +83,7 @@ function currentWindow(ctx: AutomaticAdvisorFlipContext) {
|
||||
return ctx.advanced === true && ctx.status === 'playing' && ctx.phase === 5 && positive(ctx.turn);
|
||||
}
|
||||
|
||||
/** Only the ground-movement producer opens a response for these current groups.
|
||||
/** A supported committed producer opens a response for these current groups.
|
||||
* No cards, resources, leaders or hidden marker values determine eligibility. */
|
||||
export function quoteAutomaticAdvisorFlips(ctx: AutomaticAdvisorFlipContext): {
|
||||
player: string;
|
||||
@@ -108,18 +123,55 @@ export function advisorStanceAfterSettlement(
|
||||
return stance.aloneFlipHold !== undefined ? { ...stance } : undefined;
|
||||
}
|
||||
|
||||
/** Validate the same committed move and exact held group before either response outcome. */
|
||||
function validateTransportSource(
|
||||
ctx: AutomaticAdvisorFlipContext,
|
||||
source: Extract<AutomaticAdvisorFlipSource, { producer: 'guildTransport' }>,
|
||||
) {
|
||||
const transport = source.transport;
|
||||
requireFlip(record(transport) &&
|
||||
Object.keys(transport).sort().join(',') === 'amount,elite,event,group,origin,player,sector,to' &&
|
||||
identifier(transport.event) && identifier(transport.player) && identifier(transport.origin) &&
|
||||
identifier(transport.to) && nonnegative(transport.sector) && transport.sector <= 18 &&
|
||||
positive(transport.amount) && nonnegative(transport.elite) && transport.elite <= transport.amount &&
|
||||
record(transport.group) && Object.values(transport.group).every(positive) &&
|
||||
Object.values(transport.group).reduce((total, count) => total + count, 0) === transport.amount,
|
||||
'Automatic advisor conversion lost its exact committed Guild transport metadata.');
|
||||
const places = gameTerritories(ctx);
|
||||
const origin = places.find((place) => place.id === transport.origin);
|
||||
const destination = places.find((place) => place.id === transport.to);
|
||||
requireFlip(origin && (transport.to === 'reserves' ||
|
||||
(destination && destination.sectors.includes(transport.sector))) &&
|
||||
Object.keys(transport.group).every((key) => {
|
||||
const point = splitLocation(key);
|
||||
return key === `${point.territory}:${point.sector}` && point.territory === transport.origin &&
|
||||
origin.sectors.includes(point.sector);
|
||||
}) && (source.territory === transport.origin ||
|
||||
(transport.to !== 'reserves' && source.territory === transport.to)),
|
||||
'Automatic advisor conversion must belong to the original transport origin or destination.');
|
||||
const actors = ctx.players.filter((player) => player.id === transport.player);
|
||||
const actor = actors[0];
|
||||
requireFlip(actors.length === 1 && actor.shipped === true,
|
||||
'Automatic advisor conversion lost its completed native transport slot.');
|
||||
const deposited = transport.to === 'reserves'
|
||||
? actor.reserves : actor.forces[`${transport.to}:${transport.sector}`];
|
||||
requireFlip(nonnegative(deposited) && deposited >= transport.amount,
|
||||
'Automatic advisor conversion lost the original transported physical counters.');
|
||||
}
|
||||
|
||||
/** Validate the same committed producer and exact held group before either response outcome. */
|
||||
export function validateAutomaticAdvisorFlip(
|
||||
ctx: AutomaticAdvisorFlipContext,
|
||||
source: AutomaticAdvisorFlipSource,
|
||||
): void {
|
||||
requireFlip(record(source) &&
|
||||
Object.keys(source).sort().join(',') === 'event,forces,move,mover,owner,phase,producer,territory,turn' &&
|
||||
((source.producer === 'groundMovement' && identifier(source.mover) &&
|
||||
Object.keys(source).sort().join(',') === 'event,forces,move,mover,owner,phase,producer,territory,turn') ||
|
||||
(source.producer === 'guildTransport' &&
|
||||
Object.keys(source).sort().join(',') === 'event,forces,owner,phase,producer,territory,transport,turn')) &&
|
||||
identifier(source.event) && identifier(source.owner) && identifier(source.territory) &&
|
||||
identifier(source.mover) && source.producer === 'groundMovement' &&
|
||||
positive(source.turn) && source.turn === ctx.turn && source.phase === 5 &&
|
||||
currentWindow(ctx) && source.phase === ctx.phase,
|
||||
'Automatic advisor conversion lost its original current-turn ground movement.');
|
||||
'Automatic advisor conversion lost its original current-turn ground movement or Guild transport.');
|
||||
const owners = ctx.players.filter((player) => player.id === source.owner);
|
||||
const owner = owners[0];
|
||||
requireFlip(owners.length === 1 && owner?.faction === 'beneGesserit' &&
|
||||
@@ -127,9 +179,11 @@ export function validateAutomaticAdvisorFlip(
|
||||
'Automatic advisor conversion needs its original native owner and held occurrence.');
|
||||
requireFlip(!otherPresence(ctx, owner, source.territory),
|
||||
'Automatic advisor conversion requires the original group to remain alone.');
|
||||
const movers = ctx.players.filter((player) => player.id === source.mover);
|
||||
requireFlip(movers.length === 1 && positive(source.move) && movers[0].moved === source.move,
|
||||
'Automatic advisor conversion lost its original completed movement.');
|
||||
if (source.producer === 'groundMovement') {
|
||||
const movers = ctx.players.filter((player) => player.id === source.mover);
|
||||
requireFlip(movers.length === 1 && positive(source.move) && movers[0].moved === source.move,
|
||||
'Automatic advisor conversion lost its original completed movement.');
|
||||
} else validateTransportSource(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) &&
|
||||
|
||||
+52
-10
@@ -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 } from './advisor-automatic-flip';
|
||||
import { quoteAutomaticAdvisorFlips, validateAutomaticAdvisorFlip, AutomaticAdvisorFlipError, type AdvisorStance, type AutomaticAdvisorFlipSource, type AutomaticAdvisorTransport } 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';
|
||||
@@ -1263,6 +1263,7 @@ type RicheseAllyOffer = {
|
||||
};
|
||||
export type Game = {
|
||||
pendingAutomaticAdvisorFlips?: AutomaticAdvisorFlipSource[];
|
||||
pendingAutomaticAdvisorTransport?: AutomaticAdvisorTransport;
|
||||
advisorPreparation?: AdvisorPreparationWindow;
|
||||
ecazOccupyPreview?: boolean;
|
||||
/** Fresh native Guild/Nexus payment-replacement profile, never inferred from a save. */
|
||||
@@ -20289,6 +20290,9 @@ function currentAutomaticAdvisorFlip(g: Game, response: ResponseWindow) {
|
||||
response.advisorResume === undefined &&
|
||||
JSON.stringify(g.pendingAutomaticAdvisorFlips?.[0]) === JSON.stringify(source),
|
||||
'The mandatory advisor conversion has lost its original movement source.');
|
||||
if (source.producer === 'guildTransport')
|
||||
requireRule(JSON.stringify(source.transport) === JSON.stringify(g.pendingAutomaticAdvisorTransport),
|
||||
'The mandatory conversion has lost its original committed Guild transport.');
|
||||
automaticAdvisorRule(() => validateAutomaticAdvisorFlip(g, source));
|
||||
return source;
|
||||
}
|
||||
@@ -20301,16 +20305,41 @@ function openAutomaticAdvisorFlip(g: Game) {
|
||||
advisors: false, passed: [], automaticAdvisorFlip: source,
|
||||
intent: `${getPlayer(g, source.owner).name}: mandatory advisor-to-fighter conversion in ${territory(source.territory).name}.` };
|
||||
}
|
||||
function prepareAutomaticAdvisorFlips(g: Game, mover: Player) {
|
||||
const groups = automaticAdvisorRule(() => quoteAutomaticAdvisorFlips(g));
|
||||
function prepareAutomaticAdvisorFlips(g: Game, mover: Player,
|
||||
transport?: Omit<AutomaticAdvisorTransport, 'event'>, deferResponse = false) {
|
||||
const groups = automaticAdvisorRule(() => quoteAutomaticAdvisorFlips(g))
|
||||
.filter(group => !transport || group.territory === transport.origin || group.territory === transport.to);
|
||||
if (!groups.length) return false;
|
||||
let parent: AutomaticAdvisorTransport | undefined;
|
||||
if (transport) {
|
||||
requireRule(!g.pendingAutomaticAdvisorTransport && !g.pendingAutomaticAdvisorFlips?.length,
|
||||
'Finish the original advisor conversion before another transport.');
|
||||
parent = { event: crypto.randomUUID(), ...transport };
|
||||
g.pendingAutomaticAdvisorTransport = parent;
|
||||
}
|
||||
for (const group of groups) {
|
||||
const source: AutomaticAdvisorFlipSource = { event: crypto.randomUUID(), owner: group.player,
|
||||
territory: group.territory, turn: g.turn, phase: g.phase, producer: 'groundMovement',
|
||||
mover: mover.id, move: mover.moved, forces: group.forces };
|
||||
territory: group.territory, turn: g.turn, phase: g.phase, forces: group.forces,
|
||||
...(parent ? { producer: 'guildTransport' as const, transport: parent }
|
||||
: { producer: 'groundMovement' as const, mover: mover.id, move: mover.moved }) };
|
||||
getPlayer(g, group.player).advisors![group.territory].aloneFlipHold = source.event;
|
||||
(g.pendingAutomaticAdvisorFlips ??= []).push(source);
|
||||
}
|
||||
openAutomaticAdvisorFlip(g);
|
||||
if (!deferResponse) openAutomaticAdvisorFlip(g);
|
||||
return true;
|
||||
}
|
||||
function finishAutomaticAdvisorTransport(g: Game) {
|
||||
const transport = g.pendingAutomaticAdvisorTransport;
|
||||
if (!transport) return;
|
||||
requireRule(g.phase === 5 && !g.pendingAutomaticAdvisorFlips?.length,
|
||||
'Finish the original conversions before resuming this transport arrival.');
|
||||
delete g.pendingAutomaticAdvisorTransport;
|
||||
const p = getPlayer(g, transport.player);
|
||||
if (transport.to !== 'reserves') {
|
||||
intrusion(g, p, transport.to);
|
||||
if (transport.origin !== transport.to)
|
||||
openTerritoryEntry(g, p, transport.to, transport.sector, transport.amount, transport.elite, 'guildTransport');
|
||||
}
|
||||
}
|
||||
function applyAdvisorReleases(g: Game, released: AdvisorRelease[]) {
|
||||
for (const entry of released)
|
||||
@@ -28141,11 +28170,12 @@ 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 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' ? 'transport and payment 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;
|
||||
openAutomaticAdvisorFlip(g);
|
||||
if (!g.pendingAutomaticAdvisorFlips?.length) finishAutomaticAdvisorTransport(g);
|
||||
else openAutomaticAdvisorFlip(g);
|
||||
} else finishAdvisorReaction(g, response);
|
||||
}
|
||||
} else if (response.kind === 'capture') {
|
||||
@@ -29507,6 +29537,9 @@ function commitGuildTransport(
|
||||
if (!settlement && beginGuildBetrayal(g, {source: 'guildTransport', frame, fromReserves})) return;
|
||||
const { origin, group, eliteGroup, to, sector, amount, elite, cost, allyPayment, advisors } = frame;
|
||||
const sourceLock = p.advisors?.[origin]?.lockedTurn;
|
||||
const advisorArrival = advisors || (!movementPreparation && !fromReserves && to !== 'reserves' &&
|
||||
g.advanced && p.faction === 'beneGesserit' && isAdvisor(p, origin) && !at(p, to) &&
|
||||
!g.players.some(other => other.id !== p.id && at(other, to)));
|
||||
if (guildSecretEvent) recordGuildSecretShipment(g, p.id, guildSecretEvent, 'cross',
|
||||
{ ...frame, guildSecretEvent });
|
||||
bindGuildCunningShipment(g, p, 'guild', frame);
|
||||
@@ -29534,11 +29567,16 @@ function commitGuildTransport(
|
||||
addNativeReserves(g, p, amount, elite);
|
||||
} else {
|
||||
place(p, to, sector, amount, elite);
|
||||
if (advisors) {
|
||||
if (advisorArrival) {
|
||||
const lockedTurn = Math.max(sourceLock ?? 0, p.advisors?.[to]?.lockedTurn ?? 0);
|
||||
(p.advisors ??= {})[to] = { ...p.advisors?.[to], ...(lockedTurn ? { lockedTurn } : {}) };
|
||||
} else if (p.advisors) delete p.advisors[to];
|
||||
}
|
||||
// Capture after physical transfer, before any board-derived benefit. Private
|
||||
// feasibility projections neither create events nor retain response parents.
|
||||
const heldConversion = !movementPreparation && !fromReserves &&
|
||||
prepareAutomaticAdvisorFlips(g, p, { player: p.id, origin, to, sector, amount, elite,
|
||||
group: Object.fromEntries(group) }, true);
|
||||
observeOccupation(g);
|
||||
finishShipmentPromises(g, p, fromReserves ? { territory: to, amount } : null);
|
||||
finishGuildCunningShipment(g, p.id, 'guild', frame);
|
||||
@@ -29550,6 +29588,10 @@ function commitGuildTransport(
|
||||
guildSecretEvent
|
||||
? { faction: 'guild', name: 'Secret Ally transport' }
|
||||
: { faction: p.faction, name: 'Guild transport' });
|
||||
if (heldConversion) {
|
||||
openAutomaticAdvisorFlip(g);
|
||||
return;
|
||||
}
|
||||
if (to !== 'reserves') {
|
||||
intrusion(g, p, to);
|
||||
if (origin !== to)
|
||||
@@ -32045,6 +32087,7 @@ function settleAutomaticContinuations(g: Game) {
|
||||
prepareTupileCleanup(g);
|
||||
if (g.pendingHomeworldTupileCleanup || tupileSourceBlock(g)) return;
|
||||
if (g.truthtrance || g.phaseOpening || g.status === 'finished') return;
|
||||
if (!g.response && !g.decision && g.pendingAutomaticAdvisorFlips?.length) openAutomaticAdvisorFlip(g);
|
||||
if (g.pendingArrivalReaction?.length && !g.response && !g.decision &&
|
||||
!g.pendingTerrorEntry && !g.pendingAmbassador && !g.pendingArrivalOverlap) {
|
||||
const deferred = g.pendingArrivalReaction.shift()!;
|
||||
@@ -32055,7 +32098,6 @@ function settleAutomaticContinuations(g: Game) {
|
||||
deferred.amount, deferred.elite, deferred.cause, deferred.resume);
|
||||
continue;
|
||||
}
|
||||
if (!g.response && !g.decision && g.pendingAutomaticAdvisorFlips?.length) openAutomaticAdvisorFlip(g);
|
||||
const response = g.response;
|
||||
const before = JSON.stringify(g);
|
||||
const bonus = g.pendingHomeworldOccupiedBonus;
|
||||
|
||||
@@ -181,6 +181,7 @@ const cancellationSources = {
|
||||
advisorFlip: (g, response) =>
|
||||
response.automaticAdvisorFlip
|
||||
? { source: response.automaticAdvisorFlip, pending: g.pendingAutomaticAdvisorFlips?.[0] ?? null,
|
||||
transport: response.automaticAdvisorFlip.producer === 'guildTransport' ? g.pendingAutomaticAdvisorTransport ?? null : null,
|
||||
group: g.players.find(p => p.id === response.owner)?.advisors?.[response.location ?? ''] ?? null }
|
||||
: response.advisorPreparationSource
|
||||
? { preparation: response.advisorPreparationSource, window: g.advisorPreparation ?? null }
|
||||
|
||||
+10
-6
@@ -4312,7 +4312,8 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
'In Advanced, advisors may coexist with their ally without triggering the end-turn alliance constraint in either direction. Neither the advisors nor the allied fighters are lost for that coexistence. Bene Gesserit fighters remain subject to ordinary separation; Basic receives no new advisor exception.',
|
||||
'After Fremen setup, choose the starting advisor’s territory and sector. Setup conversions remain automatic. During play, advisors alone must become fighters. A ground move into an empty territory or the last outsider’s ground departure now offers Karama prevention after the physical move, before fighter benefits. Allow to convert once; cancellation leaves the completed movement intact.',
|
||||
'A normal shipment enters as fighters unless joining existing advisors. Moving or shipping into your existing group must match its type. Advisors moved into an occupied territory without another Bene Gesserit group may remain peaceful or request a flip to fighters.',
|
||||
'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. Non-ground automatic conversions and matching-group flip counters remain unfinished.',
|
||||
'A committed Guild cross-shipment or return to reserves can also leave advisors alone. Original transport permission, rate and any Guild income resolve first; payment, force transfer and shipment use stay committed if the later conversion is prevented. The ordinary ground move remains unused. Cross-shipment arrival follows the conversion once; a reserve return has no arrival.',
|
||||
'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.',
|
||||
'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.',
|
||||
@@ -4323,7 +4324,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
{
|
||||
"area": "Implementation",
|
||||
"status": "Partial",
|
||||
"detail": "Ground mandatory conversion is now prototyped: physical advisor arrival/last-outsider departure commits before an exact sourced Karama response. Shared normalization/board quotes retain a prevented continuously-alone stance. Non-ground producers and matching-group conversion remain unfinished. Earlier preparation: Shared native public-board eligibility wraps a current-turn receipt and exact owner/territory/turn source. Direct preparation resolves before-cost and outcome validation without entering movement from earlier phases. Native future declaration queues remain intact; successful shipment commits close the receipt, including markers and positive Sneak Attack.",
|
||||
"detail": "Committed Guild cross/return now binds an exact public transport parent, holds the affected group before board benefits, preserves payment/shipment use, and resumes arrival once after conversion. Private projections/from-reserves do not create occurrences; legacy ground receipt shape remains. Earlier ground/preparation: Ground mandatory conversion is now prototyped: physical advisor arrival/last-outsider departure commits before an exact sourced Karama response. Shared normalization/board quotes retain a prevented continuously-alone stance. Non-ground producers and matching-group conversion remain unfinished. Earlier preparation: Shared native public-board eligibility wraps a current-turn receipt and exact owner/territory/turn source. Direct preparation resolves before-cost and outcome validation without entering movement from earlier phases. Native future declaration queues remain intact; successful shipment commits close the receipt, including markers and positive Sneak Attack.",
|
||||
"evidence": [
|
||||
"game/advisor-automatic-flip.ts",
|
||||
"game/advisors.ts",
|
||||
@@ -4338,7 +4339,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
{
|
||||
"area": "Player controls",
|
||||
"status": "Partial",
|
||||
"detail": "Standard Karama/Allow responses show the completed ground move and explicit provisional continuous-alone duration, with no new confirmation or timer. Earlier preparation controls: Persistent owner-only territory actions and blocked/provisional guidance work in phases2–5 at quiet boundaries, including after initial decline. Busy and mandatory interactions stay blocked; private counts/cards/balances are not read. Original phase5 chooser remains.",
|
||||
"detail": "Transport conversion uses original Karama/Allow controls with completed-payment guidance; later real Intrusion remains its own decision. Earlier controls: Standard Karama/Allow responses show the completed ground move and explicit provisional continuous-alone duration, with no new confirmation or timer. Earlier preparation controls: Persistent owner-only territory actions and blocked/provisional guidance work in phases2–5 at quiet boundaries, including after initial decline. Busy and mandatory interactions stay blocked; private counts/cards/balances are not read. Original phase5 chooser remains.",
|
||||
"evidence": [
|
||||
"docs/ADVISOR_AUTOMATIC_FLIPS.md",
|
||||
"components/advisor-preparation.tsx",
|
||||
@@ -4349,8 +4350,9 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
{
|
||||
"area": "AI",
|
||||
"status": "Partial",
|
||||
"detail": "Eight existing-profile ground-conversion paths accept legal standard responses and continue without a new strategy. Earlier preparation: The existing native preparation preference consumes the projected window; sixteen own-view existing-profile response paths accept16 legal proposals. No strategy tuning or difficulty calibration is claimed.",
|
||||
"detail": "Eight existing-profile transport response paths stay legal without consuming ordinary movement or changing strategy. Earlier paths: Eight existing-profile ground-conversion paths accept legal standard responses and continue without a new strategy. Earlier preparation: The existing native preparation preference consumes the projected window; sixteen own-view existing-profile response paths accept16 legal proposals. No strategy tuning or difficulty calibration is claimed.",
|
||||
"evidence": [
|
||||
"tests/advisor-transport-flip-engine.test.ts",
|
||||
"tests/advisor-automatic-flip-engine.test.ts",
|
||||
"game/bots.ts",
|
||||
"docs/ADVISOR_PREPARATION.md"
|
||||
@@ -4359,7 +4361,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
{
|
||||
"area": "Documentation",
|
||||
"status": "Partial",
|
||||
"detail": "The ground-conversion contract distinguishes the printed counter, automatic setup, provisional continuous-alone duration and remaining producers. Earlier preparation: Supplied physical28 G and publisher timing distinguish the open window from shipment allowance flags. Denied-territory scope is provisional, existing Ecaz co-side composition is retained, and old pending/unknown-clock behavior and remaining forced-flip/combination limits are explicit.",
|
||||
"detail": "Guild transport source/slot/payment and exactly-once arrival are distinguished from permission/rate; continuously-alone duration remains provisional. Earlier guidance: The ground-conversion contract distinguishes the printed counter, automatic setup, provisional continuous-alone duration and remaining producers. Earlier preparation: Supplied physical28 G and publisher timing distinguish the open window from shipment allowance flags. Denied-territory scope is provisional, existing Ecaz co-side composition is retained, and old pending/unknown-clock behavior and remaining forced-flip/combination limits are explicit.",
|
||||
"evidence": [
|
||||
"docs/ADVISOR_AUTOMATIC_FLIPS.md",
|
||||
"docs/ADVISOR_PREPARATION.md",
|
||||
@@ -4370,8 +4372,10 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
{
|
||||
"area": "Verification",
|
||||
"status": "Partial",
|
||||
"detail": "Ground conversion passes frozen16-file199/199, types/lint/build, five native move/response/GameTable SSR journeys and eight legal existing-profile paths. Genuine34/36-action sources retain originals; standard controls render without new callback/browser/deployed certification. Earlier preparation evidence: 16 files229/229, types/lint/build, twelve genuine new-control/gameplay/GameTable SSR journeys and sixteen minimal response paths pass. Public23/29/32/38/39-action sources preserve original setup advisor/Karama; a separate controlled ground/Terror unit proves positive Sneak Attack closure and zero nonclosure. Full combinations, visual/browser/deployed and comprehensive assurance remain open.",
|
||||
"detail": "Transport passes frozen17-file243/243, types/lint/build, eight native transport/response/GameTable SSR journeys and eight existing-profile paths. Genuine36/42-action sources earn original Tueks5 and actual paid destination Intrusion. Affordable full-price return and unfunded cross remain distinct. Broader producers and final assurance stay open. Earlier proof: Ground conversion passes frozen16-file199/199, types/lint/build, five native move/response/GameTable SSR journeys and eight legal existing-profile paths. Genuine34/36-action sources retain originals; standard controls render without new callback/browser/deployed certification. Earlier preparation evidence: 16 files229/229, types/lint/build, twelve genuine new-control/gameplay/GameTable SSR journeys and sixteen minimal response paths pass. Public23/29/32/38/39-action sources preserve original setup advisor/Karama; a separate controlled ground/Terror unit proves positive Sneak Attack closure and zero nonclosure. Full combinations, visual/browser/deployed and comprehensive assurance remain open.",
|
||||
"evidence": [
|
||||
"tests/advisor-transport-flip-engine.test.ts",
|
||||
"tests/fixture-advisor-transport-flip.ts",
|
||||
"tests/advisor-automatic-flip-engine.test.ts",
|
||||
"tests/advisor-automatic-flip-quote.test.ts",
|
||||
"tests/fixture-advisor-automatic-flip.ts",
|
||||
|
||||
Reference in new issue
Block a user