Handle advisor conversion after worm rides
Verify and publish checkpoint container / container (push) Canceled after 0s

This commit is contained in:
Quintine committed 2026-10-29 09:10:53 +11:00
1 parent ad0abfef98
commit c608d6fea6
15 files changed
+507 -14

No files matched your search

+42 -1
View File
@@ -78,6 +78,17 @@ export type AutomaticAdvisorRelocation = {
sector: number;
};
/** Completed native Fremen ride, retained while its original arrival waits. */
export type AutomaticAdvisorWormRide = {
event: string;
player: string;
origin: string;
destination: string;
sector: number;
amount: number;
elite: number;
};
export type AutomaticAdvisorFlipSource = {
event: string;
owner: string;
@@ -94,6 +105,7 @@ export type AutomaticAdvisorFlipSource = {
| { producer: 'faceDance'; faceDance: AutomaticAdvisorFaceDance }
| { producer: 'storm'; storm: AutomaticAdvisorStorm }
| { producer: 'specialRelocation'; relocation: AutomaticAdvisorRelocation }
| { producer: 'wormRide'; wormRide: AutomaticAdvisorWormRide }
| { producer: 'spiritualArrival'; spiritual: SpiritualAdvisorSource }
);
@@ -104,6 +116,7 @@ export type AutomaticAdvisorFlipPlayer = ForcePresence & {
moved?: number;
shipped?: boolean;
reserves?: number;
elites?: { forces: Readonly<Record<string, number>> };
};
export type AdvisorSettlementContext = {
@@ -445,6 +458,31 @@ function validateRelocationSource(
'Automatic advisor conversion lost the original relocated actor at its destination.');
}
function validateWormRideSource(
ctx: AutomaticAdvisorFlipContext,
source: Extract<AutomaticAdvisorFlipSource, { producer: 'wormRide' }>,
) {
const ride = source.wormRide;
requireFlip(record(ride) &&
Object.keys(ride).sort().join(',') === 'amount,destination,elite,event,origin,player,sector' &&
identifier(ride.event) && identifier(ride.player) && identifier(ride.origin) &&
identifier(ride.destination) && ride.origin !== ride.destination &&
nonnegative(ride.sector) && positive(ride.amount) && nonnegative(ride.elite) && ride.elite <= ride.amount,
'Automatic advisor conversion lost its exact completed worm ride.');
const places = gameTerritories(ctx);
requireFlip(places.some(place => place.id === ride.origin) &&
places.some(place => place.id === ride.destination && place.sectors.includes(ride.sector)) &&
(source.territory === ride.origin || source.territory === ride.destination),
'Automatic advisor conversion must belong to the original worm ride origin or destination.');
const riders = ctx.players.filter(player => player.id === ride.player);
const rider = riders[0], location = `${ride.destination}:${ride.sector}`;
const deposited = rider?.forces[location], elite = rider?.elites?.forces[location] ?? 0;
requireFlip(riders.length === 1 && rider.faction === 'fremen' &&
nonnegative(deposited) && nonnegative(elite) && elite <= deposited &&
elite >= ride.elite && deposited - elite >= ride.amount - ride.elite,
'Automatic advisor conversion lost the original native Fremen ride and deposited forces.');
}
function validateSpiritualArrivalSource(
ctx: AutomaticAdvisorFlipContext,
source: Extract<AutomaticAdvisorFlipSource, { producer: 'spiritualArrival' }>,
@@ -484,6 +522,8 @@ export function validateAutomaticAdvisorFlip(
Object.keys(source).sort().join(',') === 'event,forces,owner,phase,producer,storm,territory,turn') ||
(source.producer === 'specialRelocation' &&
Object.keys(source).sort().join(',') === 'event,forces,owner,phase,producer,relocation,territory,turn') ||
(source.producer === 'wormRide' &&
Object.keys(source).sort().join(',') === 'event,forces,owner,phase,producer,territory,turn,wormRide') ||
(source.producer === 'spiritualArrival' &&
Object.keys(source).sort().join(',') === 'event,forces,owner,phase,producer,spiritual,territory,turn')) &&
identifier(source.event) && identifier(source.owner) && identifier(source.territory) &&
@@ -492,7 +532,7 @@ export function validateAutomaticAdvisorFlip(
? record(source.relocation) && (source.relocation.kind === 'discovery'
? source.phase === 0 : source.relocation.kind === 'ambassador' && [1, 3, 5, 8].includes(source.phase))
: source.producer === 'spiritualArrival' ? [1, 3, 4, 5, 6, 8].includes(source.phase)
: source.phase === (source.producer === 'storm' ? 0 : source.producer === 'gamont' ? 8 :
: source.phase === (source.producer === 'storm' ? 0 : source.producer === 'wormRide' ? 1 : 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.');
@@ -514,6 +554,7 @@ export function validateAutomaticAdvisorFlip(
else if (source.producer === 'faceDance') validateFaceDanceSource(ctx, source);
else if (source.producer === 'storm') validateStormSource(ctx, source);
else if (source.producer === 'specialRelocation') validateRelocationSource(ctx, source);
else if (source.producer === 'wormRide') validateWormRideSource(ctx, source);
else validateSpiritualArrivalSource(ctx, source);
const forces = physicalGroup(owner, source.territory);
requireFlip(record(source.forces) && Object.keys(forces).length > 0 &&