Preflight worm-ride arrivals for bot candidates
This commit is contained in:
1 parent
3d95db88cc
commit
583cd97a25
6 files changed
+108
-2
No files matched your search
@@ -96,3 +96,21 @@ zero rejected candidates and 34 JSON continuations. Both actual
|
||||
assassination opportunities were declined; this is not a natural reveal or
|
||||
replacement sample. The unsupported reaction combination and public
|
||||
expansion gate remain unchanged.
|
||||
|
||||
## 29 September: worm ride into Terror with BG intrusion
|
||||
|
||||
Another unmodified six-seat Advanced Moritani game at base seed `20261007`
|
||||
finished in 1,541 accepted actions, two rejected Fremen worm-ride candidates
|
||||
and 41 JSON continuations. Each rejected ride targeted a placed Terror while
|
||||
a Bene Gesserit fighter intrusion would open another arrival decision.
|
||||
|
||||
The owned bot candidate now quotes the same public destination triggers
|
||||
before choosing a worm ride; its current worm decision is the parent, not a
|
||||
second reaction. It leaves legal single reactions and supported Ambassador/
|
||||
Terror overlap available. The focused regression reproduced the engine
|
||||
rejection, then showed all four bot profiles skip the conflicting destination
|
||||
and execute an alternative. The same-seed genuine game completes 1,541
|
||||
accepted attempts, zero rejects and 41 JSON continuations. No hidden Terror
|
||||
face or rival hand enters the quote. Homeworld rides and other special
|
||||
arrivals retain their own gates; this does not implement the conflicting
|
||||
reaction or open public expansion starts.
|
||||
@@ -6,6 +6,14 @@ Expansion starts and full rules/publication acceptance remain gated.**
|
||||
|
||||
## Current checkpoint and work
|
||||
|
||||
Another natural six-seat Moritani game exposed two illegal Fremen worm
|
||||
rides into a placed Terror with a pending BG fighter intrusion. The
|
||||
[worm-arrival bot preflight](BOT_ARRIVAL_PREFLIGHT.md#29-september-worm-ride-into-terror-with-bg-intrusion)
|
||||
now omits only that public reaction conflict. Same-seed replay completes
|
||||
1,541 accepted actions, zero rejects and 41 JSON restores. This does not
|
||||
implement the simultaneous reaction or demonstrate a natural assassination
|
||||
reveal; expansion starts and deployment remain gated.
|
||||
|
||||
A six-seat Advanced Moritani sample exposed six illegal bot Guild
|
||||
cross-planet transport candidates into a placed Terror while Guild income
|
||||
was pending. [Arrival preflight](BOT_ARRIVAL_PREFLIGHT.md#29-september-guild-cross-planet-transport-and-terror)
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
# Dune implementation status
|
||||
|
||||
## 29 September 2026 — worm-ride arrival candidate correction
|
||||
|
||||
A natural six-seat Moritani Advanced sample at base seed `20261007`
|
||||
completed 1,541 actions with two rejected Fremen worm rides into a placed
|
||||
Terror while a BG intrusion would open. Both were rejected without mutating
|
||||
the game. The [owner-view preflight](BOT_ARRIVAL_PREFLIGHT.md#29-september-worm-ride-into-terror-with-bg-intrusion)
|
||||
now quotes that public worm destination and excludes the unsupported
|
||||
combined reaction before the bot selects an action. A focused engine
|
||||
regression reproduced the rejection first and covers four legal AI
|
||||
fallbacks. The same genuine game finishes 1,541 accepted attempts, zero
|
||||
rejects and 41 JSON continuations. Two assassination opportunities were
|
||||
declined; no natural reveal/replacement was proved. The authoritative
|
||||
reaction-order gap and public expansion gate remain.
|
||||
|
||||
`npm run check` passes typecheck, lint and 5,782/5,782 offline cases.
|
||||
No app route or persistence schema changed; build and HTTP checks were not
|
||||
repeated for this bot candidate filter.
|
||||
|
||||
## 29 September 2026 — Guild transport arrival preflight
|
||||
|
||||
The [arrival preflight](BOT_ARRIVAL_PREFLIGHT.md#29-september-guild-cross-planet-transport-and-terror)
|
||||
|
||||
@@ -224,3 +224,37 @@ export function botArrivalBlock(g: GameView, action: Action): string | null {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/** The worm decision itself is replaced before entry reactions; only a new
|
||||
* intrusion or another pending control conflicts with the destination.
|
||||
* Homeworld riders retain their separate unsupported-combination policy.
|
||||
*/
|
||||
export function botWormRideArrivalBlock(g: GameView, to: string, sector: number): string | null {
|
||||
const ride = g.decision;
|
||||
if (g.status !== 'playing' || g.phase !== 1 || ride?.kind !== 'wormRide' ||
|
||||
ride.player !== g.me || g.homeworlds || !validLocation(to, sector))
|
||||
return null;
|
||||
try {
|
||||
quoteCompletedMovementArrival({
|
||||
advanced: g.advanced,
|
||||
overlapSupported: g.arrivalOverlapMode === true,
|
||||
players: g.players,
|
||||
order: { player: g.me, origin: ride.territory, to,
|
||||
advisors: false, wantsFighters: false },
|
||||
ambassadors: (g.ambassadors?.tokens ?? []).map(
|
||||
({ zone, location, effect }) => ({ zone, location, effect })),
|
||||
terror: (g.moritaniTerror?.tokens ?? []).map(
|
||||
({ status, location }) => ({ status, location })),
|
||||
controls: {
|
||||
response: !!g.response, decision: false,
|
||||
pendingTerror: !!g.terrorEntry, pendingAmbassador: !!g.ambassadorEntry,
|
||||
paidBox: false,
|
||||
},
|
||||
flight: null,
|
||||
});
|
||||
return null;
|
||||
} catch (error) {
|
||||
if (error instanceof MovementArrivalError) return error.message;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -100,7 +100,7 @@ import { sandmasterDefaultChoice } from './sandmaster-movement';
|
||||
import { sandmasterWormCollection } from './sandmaster-worm';
|
||||
import { richeseCardDefinition } from './richese-cards';
|
||||
import { presenceAt } from './force-presence';
|
||||
import { botArrivalBlock } from './bot-arrival';
|
||||
import { botArrivalBlock, botWormRideArrivalBlock } from './bot-arrival';
|
||||
import { validateCohortSelection, type OrnithopterMode } from './ornithopter';
|
||||
import { fighterCount, isAdvisor } from './advisors';
|
||||
import { casualtyOptions, maxCombatDial, maxCombatSupport, type CombatForces } from './combat';
|
||||
@@ -2667,7 +2667,8 @@ function policyActions(g: GameView): Action[] {
|
||||
...destinations(g)
|
||||
.filter(
|
||||
(to) =>
|
||||
to.t !== d.territory && botEntryAllowed(g, me, to.t, to.s, 'move'),
|
||||
to.t !== d.territory && botEntryAllowed(g, me, to.t, to.s, 'move') &&
|
||||
!botWormRideArrivalBlock(g, to.t, to.s),
|
||||
)
|
||||
.slice(0, 16)
|
||||
.map(
|
||||
|
||||
@@ -12,6 +12,7 @@ import { baseDeck } from '../game/cards';
|
||||
import { createAmbassadors, placeAmbassador } from '../game/ecaz-ambassadors';
|
||||
import { createTerrorState, placeTerror, type TerrorKind } from '../game/moritani-terror';
|
||||
import { botArrivalBlock } from '../game/bot-arrival';
|
||||
import { botActions } from '../game/bots';
|
||||
|
||||
const seat = (g: Game, id: string) => g.players.find((p) => p.id === id)!;
|
||||
const reload = (g: Game): Game => JSON.parse(JSON.stringify(g));
|
||||
@@ -192,3 +193,29 @@ void test('bots treat the supported overlapping stronghold as a legal ordinary a
|
||||
assert.equal(entered.pendingTerrorEntry?.resume, 'none');
|
||||
assert.equal(seat(entered, 'in').forces['arrakeen:10'], 2);
|
||||
});
|
||||
|
||||
void test('worm-riding bots avoid Terror arrival while BG fighter intrusion is pending', () => {
|
||||
const { g } = table('robbery');
|
||||
g.ecazAmbassadors = createAmbassadors(() => 0.2);
|
||||
const bg = newPlayer('bg', 'Bene Gesserit', 'beneGesserit');
|
||||
bg.forces = { 'arrakeen:10': 1 };
|
||||
bg.reserves = 19;
|
||||
g.players.push(bg);
|
||||
g.order.push(bg.id);
|
||||
g.movementRemaining!.push(bg.id);
|
||||
g.decision = { kind: 'wormRide', player: 'in', territory: 'imperial_basin' };
|
||||
g.wormRides = ['hagga_basin'];
|
||||
const before = JSON.stringify(g);
|
||||
assert.throws(() => action(g, 'in', ride),
|
||||
/Terror combined with another arrival reaction/);
|
||||
for (const bot of ['Easy', 'Medium', 'Hard', 'Brutal'] as const) {
|
||||
const view = viewGame(g, 'in');
|
||||
view.players.find(p => p.id === 'in')!.bot = bot;
|
||||
const choices = botActions(view);
|
||||
assert.ok(choices.length, bot);
|
||||
assert.equal(choices.some(choice => choice.type === 'decision' &&
|
||||
choice.accept === true && choice.territory === 'arrakeen'), false, bot);
|
||||
assert.ok(applyAction(g, 'in', choices[0]));
|
||||
}
|
||||
assert.equal(JSON.stringify(g), before);
|
||||
});
|
||||
Reference in new issue
Block a user