Resolve advisor conversion after Gamont before final victory

Commit original Gamont use/return or empty revelation before its separate mandatory-conversion counter. Preserve private fuel provenance beside a public phase8 receipt, retain legacy phase5 sources and defer the genuine final chooser through both direct and Semuta cost completion. Consume the parent once; ordinary Mentat gains no chooser and victory waits for original confirmation. Existing provisional continuously-alone duration remains.

Validation: 18 files277/277, types/lint/build, five native Gamont/response/GameTable SSR journeys and eight existing-profile paths. Genuine52/56-action public Advanced sources earn a real third-stronghold win versus prevented next-turn outcome. Empty revelation remains pure-source proof; full combinations/remaining producers, browser/deployed and final assurance stay open. No saved-room/D1/restart/deployment operation or AI refinement.
This commit is contained in:
Quintine committed 2026-10-24 20:16:05 +11:00
1 parent d0f52c7bae
commit 035be51915
12 files changed
+748 -26

No files matched your search

+50 -5
View File
@@ -18,6 +18,16 @@ export type AutomaticAdvisorTransport = {
group: Record<string, number>;
};
export type AutomaticAdvisorGamont = {
event: string;
player: string;
target: string;
location: string;
elite: number;
returned: boolean;
mentat: boolean;
};
export type AutomaticAdvisorFlipSource = {
event: string;
owner: string;
@@ -28,6 +38,7 @@ export type AutomaticAdvisorFlipSource = {
} & (
| { producer: 'groundMovement'; mover: string; move: number }
| { producer: 'guildTransport'; transport: AutomaticAdvisorTransport }
| { producer: 'gamont'; gamont: AutomaticAdvisorGamont }
);
export type AutomaticAdvisorFlipPlayer = ForcePresence & {
@@ -48,6 +59,7 @@ export type AutomaticAdvisorFlipContext = AdvisorSettlementContext & MobileBoard
status: string;
turn: number;
phase: number;
choamMentatPending?: boolean;
};
export class AutomaticAdvisorFlipError extends Error {
@@ -80,7 +92,8 @@ function otherPresence(ctx: AdvisorSettlementContext, player: AutomaticAdvisorFl
}
function currentWindow(ctx: AutomaticAdvisorFlipContext) {
return ctx.advanced === true && ctx.status === 'playing' && ctx.phase === 5 && positive(ctx.turn);
return ctx.advanced === true && ctx.status === 'playing' &&
(ctx.phase === 5 || ctx.phase === 8) && positive(ctx.turn);
}
/** A supported committed producer opens a response for these current groups.
@@ -158,6 +171,34 @@ function validateTransportSource(
'Automatic advisor conversion lost the original transported physical counters.');
}
function validateGamontSource(
ctx: AutomaticAdvisorFlipContext,
source: Extract<AutomaticAdvisorFlipSource, { producer: 'gamont' }>,
) {
const receipt = source.gamont;
requireFlip(record(receipt) &&
Object.keys(receipt).sort().join(',') === 'elite,event,location,mentat,player,returned,target' &&
identifier(receipt.event) && identifier(receipt.player) && identifier(receipt.target) &&
identifier(receipt.location) && (receipt.elite === 0 || receipt.elite === 1) &&
typeof receipt.returned === 'boolean' && typeof receipt.mentat === 'boolean',
'Automatic advisor conversion lost its exact public Trip to Gamont receipt.');
const point = splitLocation(receipt.location);
requireFlip(receipt.location === `${point.territory}:${point.sector}` &&
gameTerritories(ctx).some((place) => place.id === point.territory && place.sectors.includes(point.sector)) &&
source.territory === point.territory,
'Automatic advisor conversion must use the original current Gamont location.');
const actors = ctx.players.filter((player) => player.id === receipt.player);
const targets = ctx.players.filter((player) => player.id === receipt.target);
requireFlip(actors.length === 1 && actors[0].faction === 'choam' &&
targets.length === 1 && receipt.target !== receipt.player,
'Automatic advisor conversion needs the original native CHOAM actor and distinct Gamont target.');
if (receipt.returned)
requireFlip(nonnegative(targets[0].reserves) && targets[0].reserves >= 1,
'Automatic advisor conversion lost the returned Gamont counter.');
requireFlip(!receipt.mentat || ctx.choamMentatPending === true,
'Automatic advisor conversion lost its final CHOAM Mentat opportunity.');
}
/** Validate the same committed producer and exact held group before either response outcome. */
export function validateAutomaticAdvisorFlip(
ctx: AutomaticAdvisorFlipContext,
@@ -167,11 +208,14 @@ export function validateAutomaticAdvisorFlip(
((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')) &&
Object.keys(source).sort().join(',') === 'event,forces,owner,phase,producer,territory,transport,turn') ||
(source.producer === 'gamont' &&
Object.keys(source).sort().join(',') === 'event,forces,gamont,owner,phase,producer,territory,turn')) &&
identifier(source.event) && identifier(source.owner) && identifier(source.territory) &&
positive(source.turn) && source.turn === ctx.turn && source.phase === 5 &&
positive(source.turn) && source.turn === ctx.turn &&
source.phase === (source.producer === 'gamont' ? 8 : 5) &&
currentWindow(ctx) && source.phase === ctx.phase,
'Automatic advisor conversion lost its original current-turn ground movement or Guild transport.');
'Automatic advisor conversion lost its original current-turn committed producer.');
const owners = ctx.players.filter((player) => player.id === source.owner);
const owner = owners[0];
requireFlip(owners.length === 1 && owner?.faction === 'beneGesserit' &&
@@ -183,7 +227,8 @@ export function validateAutomaticAdvisorFlip(
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);
} else if (source.producer === 'guildTransport') validateTransportSource(ctx, source);
else validateGamontSource(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) &&
+59 -10
View File
@@ -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 } from './advisor-automatic-flip';
import { quoteAutomaticAdvisorFlips, validateAutomaticAdvisorFlip, AutomaticAdvisorFlipError, type AdvisorStance, type AutomaticAdvisorFlipSource, type AutomaticAdvisorTransport, type AutomaticAdvisorGamont } 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';
@@ -1264,6 +1264,7 @@ type RicheseAllyOffer = {
export type Game = {
pendingAutomaticAdvisorFlips?: AutomaticAdvisorFlipSource[];
pendingAutomaticAdvisorTransport?: AutomaticAdvisorTransport;
pendingAutomaticAdvisorGamont?: { receipt: AutomaticAdvisorGamont; card: string; nexusEvent?: string };
advisorPreparation?: AdvisorPreparationWindow;
ecazOccupyPreview?: boolean;
/** Fresh native Guild/Nexus payment-replacement profile, never inferred from a save. */
@@ -8026,8 +8027,10 @@ function finishTreacheryDiscard(g: Game, claim?: { player: string; card: string
log(g, 'The declared move was prevented without moving forces or spending a movement.');
} else if (next.kind === 'choamGamontDiscard') {
completeChoamGamont(g, getPlayer(g, next.player), next.target,
next.location, next.elite ?? 0, next.noFieldEvent);
if (next.mentat) g.decision = { kind: 'choamMentat', player: next.player };
next.location, next.elite ?? 0, next.noFieldEvent,
{ card: next.card, mentat: next.mentat, nexusEvent: next.nexusEvent });
if (next.mentat && !g.pendingAutomaticAdvisorGamont)
g.decision = { kind: 'choamMentat', player: next.player };
} else if (next.kind === 'choamJubbaDiscard') {
completeChoamJubba(g, getPlayer(g, next.player), next.territory);
offerChoamStorm(g);
@@ -20293,6 +20296,9 @@ function currentAutomaticAdvisorFlip(g: Game, response: ResponseWindow) {
if (source.producer === 'guildTransport')
requireRule(JSON.stringify(source.transport) === JSON.stringify(g.pendingAutomaticAdvisorTransport),
'The mandatory conversion has lost its original committed Guild transport.');
if (source.producer === 'gamont')
requireRule(JSON.stringify(source.gamont) === JSON.stringify(g.pendingAutomaticAdvisorGamont?.receipt),
'The mandatory conversion has lost its original completed Trip to Gamont.');
automaticAdvisorRule(() => validateAutomaticAdvisorFlip(g, source));
return source;
}
@@ -20341,6 +20347,40 @@ function finishAutomaticAdvisorTransport(g: Game) {
openTerritoryEntry(g, p, transport.to, transport.sector, transport.amount, transport.elite, 'guildTransport');
}
}
function prepareGamontAdvisorFlips(g: Game, owner: Player, target: Player,
from: string, elite: number, returned: boolean,
completed: { card: string; mentat?: boolean; nexusEvent?: string }) {
const site = splitLocation(from).territory;
const groups = automaticAdvisorRule(() => quoteAutomaticAdvisorFlips(g))
.filter(group => group.territory === site);
if (!groups.length) return false;
requireRule(!g.pendingAutomaticAdvisorFlips?.length && !g.pendingAutomaticAdvisorGamont &&
!g.pendingAutomaticAdvisorTransport, 'Finish the original advisor conversion first.');
// Public effect evidence never includes CHOAM Cunning's private fuel card.
const receipt: AutomaticAdvisorGamont = { event: crypto.randomUUID(), player: owner.id,
target: target.id, location: from, elite, returned, mentat: completed.mentat === true };
g.pendingAutomaticAdvisorGamont = { receipt, card: completed.card,
...(completed.nexusEvent ? { nexusEvent: completed.nexusEvent } : {}) };
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: 'gamont', gamont: receipt };
getPlayer(g, group.player).advisors![group.territory].aloneFlipHold = source.event;
(g.pendingAutomaticAdvisorFlips ??= []).push(source);
}
openAutomaticAdvisorFlip(g);
return true;
}
function finishAutomaticAdvisorGamont(g: Game) {
const parent = g.pendingAutomaticAdvisorGamont;
if (!parent) return;
requireRule(g.status === 'playing' && g.phase === 8 && !g.pendingAutomaticAdvisorFlips?.length &&
(!parent.receipt.mentat || g.choamMentatPending === true),
'Finish the original advisor conversion before resuming Mentat.');
delete g.pendingAutomaticAdvisorGamont;
if (parent.receipt.mentat)
g.decision = { kind: 'choamMentat', player: parent.receipt.player };
}
function applyAdvisorReleases(g: Game, released: AdvisorRelease[]) {
for (const entry of released)
delete getPlayer(g, entry.player).advisors?.[entry.territory];
@@ -22358,7 +22398,10 @@ function decideSharedSpice(g: Game, id: string, action: Action) {
}
}
function victory(g: Game, quote: VictoryQuote = currentVictoryQuote(g)) {
function victory(g: Game, quote?: VictoryQuote) {
requireRule(!g.pendingAutomaticAdvisorFlips?.length && !g.pendingAutomaticAdvisorTransport &&
!g.pendingAutomaticAdvisorGamont, 'Resolve mandatory advisor conversions before checking victory.');
quote ??= currentVictoryQuote(g);
applyAdvisorReleases(g, quote.released);
g.winner = quote.winner;
g.status = quote.status;
@@ -27250,7 +27293,8 @@ function completeChoamJubba(g: Game, owner: Player, location: string) {
}
function completeChoamGamont(
g: Game, owner: Player, targetId: string, from: string,
elite: number, noFieldEvent?: string,
elite: number, noFieldEvent: string | undefined,
completed: { card: string; mentat?: boolean; nexusEvent?: string },
) {
const target = getPlayer(g, targetId);
if (noFieldEvent) revealPlayerNoField(g, target, 'gamont');
@@ -27260,6 +27304,7 @@ function completeChoamGamont(
target.reserves++;
if (target.elites) target.elites.reserves += elite;
}
prepareGamontAdvisorFlips(g, owner, target, from, elite, returned, completed);
observeOccupation(g);
settleAdvisors(g);
log(g, returned
@@ -27789,7 +27834,8 @@ function finishResponse(g: Game, canceled: boolean, settlement?: BureaucratSettl
}
}
completeChoamGamont(g, choam, pending.target!, pending.location!,
pending.elite!, pending.noFieldEvent);
pending.elite!, pending.noFieldEvent,
{ card: used.id, mentat: pending.mentat, nexusEvent: pending.nexusEvent });
} else {
if (g.semutaPreview && (used.name === 'La La La' || pending.nexusEvent) &&
!pending.storm && !pending.movement && !pending.mentat &&
@@ -27830,7 +27876,8 @@ function finishResponse(g: Game, canceled: boolean, settlement?: BureaucratSettl
if (pending.storm && g.stormResolution) offerChoamStorm(g);
if (pending.revival && g.pendingRevival) offerRevivalStop(g);
if (pending.movement && g.pendingChoamMove) resumeChoamMovement(g);
if (pending.mentat) g.decision = { kind: 'choamMentat', player: choam.id };
if (pending.mentat && !g.pendingAutomaticAdvisorGamont)
g.decision = { kind: 'choamMentat', player: choam.id };
} else if (response.kind === 'richeseAuction') {
const round = validateRicheseCacheCancellation(g, response);
if (canceled) {
@@ -28170,12 +28217,14 @@ 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' ? 'transport and payment 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' ? 'transport and payment stay' : automaticAdvisorFlip.producer === 'gamont' ? 'Trip to Gamont effect and card use 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;
if (!g.pendingAutomaticAdvisorFlips?.length) finishAutomaticAdvisorTransport(g);
else openAutomaticAdvisorFlip(g);
if (!g.pendingAutomaticAdvisorFlips?.length) {
finishAutomaticAdvisorTransport(g);
finishAutomaticAdvisorGamont(g);
} else openAutomaticAdvisorFlip(g);
} else finishAdvisorReaction(g, response);
}
} else if (response.kind === 'capture') {
+1
View File
@@ -182,6 +182,7 @@ const cancellationSources = {
response.automaticAdvisorFlip
? { source: response.automaticAdvisorFlip, pending: g.pendingAutomaticAdvisorFlips?.[0] ?? null,
transport: response.automaticAdvisorFlip.producer === 'guildTransport' ? g.pendingAutomaticAdvisorTransport ?? null : null,
gamont: response.automaticAdvisorFlip.producer === 'gamont' ? g.pendingAutomaticAdvisorGamont?.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 }
+10 -5
View File
@@ -3830,6 +3830,7 @@ export const RULE_TOPICS: RuleTopic[] = [
summary:
'Return one other player’s force to reserves before stronghold control is checked.',
steps: [
'If the completed return or empty No-Field reveal leaves Bene Gesserit advisors alone, their mandatory conversion has a separate Karama response before victory. The return and card use stay committed. The provisional continuously-alone cancellation duration still applies; final confirmation resumes only after this counter.',
'In basic or advanced play, CHOAM may use Trip to Gamont during Mentat Pause. Select another player, an occupied board sector and an ordinary or elite force. One physical token returns to that player’s reserves; it is not killed, revived or shipped. If the selected Richese sector has a No-Field, it must reveal first. Return one force if any are present; the card is still used if there are none.',
'You may select forces in storm, peaceful Bene Gesserit advisors, or occupants inside the Hidden Mobile Stronghold. An elite force remains elite in reserves. No spice, revival allowance, battle-loss count or transport income changes. If removing the last opposing force leaves advisors alone, their stance settles before victory.',
'The declared card, recipient, sector and force type are public before a Karama response. Cancellation leaves both card and force in place and prevents retrying that physical card’s effect for the phase. If the card or selected force disappears during an interruption, no return occurs and the card is not discarded for the effect.',
@@ -4312,6 +4313,7 @@ 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.',
'After Trip to Gamont completes during Mentat, the last outsider’s return or empty No-Field reveal can leave advisors alone. Karama can then prevent their conversion without undoing the completed effect or card use. The final CHOAM opportunity resumes afterward; victory waits for its original confirmation. Ordinary Mentat does not gain an extra final chooser.',
'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.',
@@ -4324,7 +4326,7 @@ export const RULE_TOPICS: RuleTopic[] = [
{
"area": "Implementation",
"status": "Partial",
"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.",
"detail": "Gamont now commits cost/return before phase8 conversion, keeps private fuel provenance out of its public event, and defers the original final chooser until the parent is consumed once. Victory waits for original confirmation; ordinary Mentat gains no chooser. Earlier ground/transport/preparation: 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",
@@ -4339,7 +4341,7 @@ export const RULE_TOPICS: RuleTopic[] = [
{
"area": "Player controls",
"status": "Partial",
"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.",
"detail": "Standard Gamont conversion response explains completed effect/card use and the pending victory check. Earlier controls: 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",
@@ -4350,8 +4352,9 @@ export const RULE_TOPICS: RuleTopic[] = [
{
"area": "AI",
"status": "Partial",
"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.",
"detail": "Eight existing-profile ordinary/final Gamont paths resolve legally before the actual final outcome, without strategy changes. Earlier paths: 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-gamont-flip-engine.test.ts",
"tests/advisor-transport-flip-engine.test.ts",
"tests/advisor-automatic-flip-engine.test.ts",
"game/bots.ts",
@@ -4361,7 +4364,7 @@ export const RULE_TOPICS: RuleTopic[] = [
{
"area": "Documentation",
"status": "Partial",
"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.",
"detail": "Gamont scope distinguishes original-effect prevention, empty revelation, private fuel, final/ordinary timing and the unchanged provisional hold. Earlier contracts: 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",
@@ -4372,8 +4375,10 @@ export const RULE_TOPICS: RuleTopic[] = [
{
"area": "Verification",
"status": "Partial",
"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.",
"detail": "Gamont passes frozen18-file277/277, types/lint/build, five native Gamont/response/GameTable SSR journeys and eight profile paths. Genuine52/56-action sources earn two holds and a real final win versus next-turn outcome; empty revelation is pure-source evidence. Full No-Field/Nexus/Semuta and remaining producers remain open. Earlier proof: 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-gamont-flip-engine.test.ts",
"tests/fixture-advisor-gamont-flip.ts",
"tests/advisor-transport-flip-engine.test.ts",
"tests/fixture-advisor-transport-flip.ts",
"tests/advisor-automatic-flip-engine.test.ts",