Preflight Homeworld-sourced AI arrival overlaps
This commit is contained in:
1 parent
41fd6fe173
commit
b9c8b740ca
6 files changed
+106
-15
No files matched your search
@@ -36,10 +36,14 @@ and Face Dancers are unnecessary. It does not mutate the view or sample
|
|||||||
randomness. Only the known typed arrival error excludes a candidate; unexpected
|
randomness. Only the known typed arrival error excludes a candidate; unexpected
|
||||||
faults propagate. The engine remains authoritative for every actual action.
|
faults propagate. The engine remains authoritative for every actual action.
|
||||||
|
|
||||||
Homeworlds, explicit Nexus shipments, special movement and unrecognized action
|
Homeworld-sourced ordinary shipments now quote the same public arrival triggers,
|
||||||
shapes retain their existing adapters and legal fallback search. They are not
|
including a colocated Ambassador and Terror, after the bot selects its own
|
||||||
classified as illegal by this narrower preview. This is not a complete action
|
physical source. Their off-planet origin is sufficient for the arrival quote;
|
||||||
validator or certification that every AI candidate in every module is legal.
|
Homeworld source eligibility, tariffs and any payment-dependent Guild response
|
||||||
|
remain authoritative engine checks, not duplicated AI rules. Other Homeworld
|
||||||
|
routes, explicit Nexus shipments, special movement and unrecognized actions
|
||||||
|
retain their own adapters and legal fallback search. This is not a complete
|
||||||
|
action validator or certification that every AI candidate is legal.
|
||||||
|
|
||||||
## Evidence
|
## Evidence
|
||||||
|
|
||||||
@@ -53,3 +57,18 @@ stance settlement does not turn a lone advisor group into fighters.
|
|||||||
Final checks and same-seed integrated results are recorded with the source-bound
|
Final checks and same-seed integrated results are recorded with the source-bound
|
||||||
private checkpoint and Git message. Existing saved games remain protected; no
|
private checkpoint and Git message. Existing saved games remain protected; no
|
||||||
server restart or database change is required.
|
server restart or database change is required.
|
||||||
|
|
||||||
|
## 29 September: Homeworld-sourced shipment overlap
|
||||||
|
|
||||||
|
A genuine five-seat Advanced combined Homeworld/Nexus/expansion game on
|
||||||
|
`41fd6fe` completed in 786 actions but proposed two Tleilaxu shipments from
|
||||||
|
its own Homeworld into unsupported Ambassador/Terror arrival combinations.
|
||||||
|
Both were rejected without writes before a legal alternative was chosen. The
|
||||||
|
owner-view preflight formerly skipped every Homeworld shipment. It now passes
|
||||||
|
off-planet Homeworld-source arrivals through the same public quote without
|
||||||
|
assuming a price or hidden Terror face. All four AI profiles exclude the
|
||||||
|
reproduced action while retaining an executable alternative. A same-seed
|
||||||
|
genuine-setup replay completes in 786 accepted attempts, zero rejections,
|
||||||
|
21 JSON continuations and the same fourth-turn shared victory. This repairs
|
||||||
|
candidate legality, not the unsupported arrival-rule composition or public
|
||||||
|
expansion start gate.
|
||||||
@@ -6,6 +6,14 @@ Expansion starts and full rules/publication acceptance remain gated.**
|
|||||||
|
|
||||||
## Current checkpoint and work
|
## Current checkpoint and work
|
||||||
|
|
||||||
|
A five-seat Advanced combined Homeworld/Nexus/expansion sample exposed two
|
||||||
|
known-unsupported Ecaz Ambassador/Moritani Terror shipment candidates from
|
||||||
|
Tleilaxu's own Homeworld. [AI arrival preflight](BOT_ARRIVAL_PREFLIGHT.md#29-september-homeworld-sourced-shipment-overlap)
|
||||||
|
now excludes them using only public entry reactions. The same genuine game
|
||||||
|
completed with 786/786 legal attempts and 21 JSON continuations; this is a
|
||||||
|
candidate-legality repair, not support for the combined arrival or a public
|
||||||
|
expansion start. The source is not in the last verified live image.
|
||||||
|
|
||||||
A bounded [Ecaz Ambassador/Moritani Terror same-entry continuation](MORITANI_ENTRY_TIMING.md#selected-bounded-overlap-order--29-september-2026)
|
A bounded [Ecaz Ambassador/Moritani Terror same-entry continuation](MORITANI_ENTRY_TIMING.md#selected-bounded-overlap-order--29-september-2026)
|
||||||
now snapshots both original optional triggers and offers their owners sequential
|
now snapshots both original optional triggers and offers their owners sequential
|
||||||
choices in current storm order on classic plus Ecaz/Moritani Basic/Advanced
|
choices in current storm order on classic plus Ecaz/Moritani Basic/Advanced
|
||||||
|
|||||||
@@ -1,5 +1,26 @@
|
|||||||
# Dune implementation status
|
# Dune implementation status
|
||||||
|
|
||||||
|
## 29 September 2026 — Homeworld-sourced AI arrival preflight
|
||||||
|
|
||||||
|
A genuine five-seat Advanced combined Homeworld/Nexus/expansion game on
|
||||||
|
`41fd6fe` exposed two rejected Tleilaxu shipments from its own Homeworld
|
||||||
|
into unsupported Ambassador/Terror entry combinations. The public AI
|
||||||
|
[preflight](BOT_ARRIVAL_PREFLIGHT.md#29-september-homeworld-sourced-shipment-overlap)
|
||||||
|
had skipped Homeworld sources. It now quotes their off-planet arrival through
|
||||||
|
the same authoritative public reaction helper, without sampling private
|
||||||
|
Terror faces or recreating source pricing. A failing-before/passing-after
|
||||||
|
real-engine regression exercises all four AI profiles and executable
|
||||||
|
fallbacks. The identical genuine game completes after the fix with
|
||||||
|
786 accepted attempts, no rejected candidates, 21 JSON continuations and
|
||||||
|
a fourth-turn shared victory. Other Homeworld payment-dependent reactions
|
||||||
|
and unsupported combined arrivals remain gated; no public start was opened.
|
||||||
|
|
||||||
|
The initial full check exposed an existing Guild shipment test that asserted
|
||||||
|
payment before a legitimately opened Karama rate response. That test now
|
||||||
|
resolves the response before checking the rounded cost. The final
|
||||||
|
`npm run check` passes typecheck, lint and 5,757/5,757 offline cases. No HTTP
|
||||||
|
contract, database schema or player-facing control changed.
|
||||||
|
|
||||||
## 29 September 2026 — Sapho finite-bid boundary and pending source questions
|
## 29 September 2026 — Sapho finite-bid boundary and pending source questions
|
||||||
|
|
||||||
The existing Once Around Juice of Sapho adapter permits an unacted holder to
|
The existing Once Around Juice of Sapho adapter permits an unacted holder to
|
||||||
|
|||||||
+20
-9
@@ -14,10 +14,11 @@ import {
|
|||||||
import { quoteSmugglerShipment } from './smuggler-shipment';
|
import { quoteSmugglerShipment } from './smuggler-shipment';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Known arrival support for ordinary owner-view candidates, not a complete
|
* Known arrival support for ordinary owner-view candidates, plus the public
|
||||||
* action validator. Homeworld, explicit Nexus and special movement routes keep
|
* overlap guard for Homeworld-sourced shipments. This is not a complete action
|
||||||
* their own adapters and fallbacks. No hidden Terror face or rival hand enters
|
* validator. Explicit Nexus and special movement routes retain their adapters.
|
||||||
* this quote, and unexpected failures are never treated as rejected candidates.
|
* No hidden Terror face or rival hand enters this quote; unexpected failures
|
||||||
|
* are never treated as rejected candidates.
|
||||||
*/
|
*/
|
||||||
export function botArrivalBlock(g: GameView, action: Action): string | null {
|
export function botArrivalBlock(g: GameView, action: Action): string | null {
|
||||||
if (
|
if (
|
||||||
@@ -25,10 +26,8 @@ export function botArrivalBlock(g: GameView, action: Action): string | null {
|
|||||||
g.phase !== 5 ||
|
g.phase !== 5 ||
|
||||||
g.active !== g.me ||
|
g.active !== g.me ||
|
||||||
!['ship', 'move'].includes(action.type) ||
|
!['ship', 'move'].includes(action.type) ||
|
||||||
g.homeworlds ||
|
|
||||||
action.nexus !== undefined ||
|
action.nexus !== undefined ||
|
||||||
action.source !== undefined ||
|
action.source !== undefined ||
|
||||||
action.homeworldSources !== undefined ||
|
|
||||||
typeof action.territory !== 'string' ||
|
typeof action.territory !== 'string' ||
|
||||||
typeof action.sector !== 'number' ||
|
typeof action.sector !== 'number' ||
|
||||||
!validLocation(action.territory, action.sector)
|
!validLocation(action.territory, action.sector)
|
||||||
@@ -45,9 +44,21 @@ export function botArrivalBlock(g: GameView, action: Action): string | null {
|
|||||||
paidBox: g.decision?.kind === 'nullentropy',
|
paidBox: g.decision?.kind === 'nullentropy',
|
||||||
};
|
};
|
||||||
let order: CompletedMovementArrivalInput['order'];
|
let order: CompletedMovementArrivalInput['order'];
|
||||||
if (action.type === 'ship') {
|
if (action.type === 'ship' && g.moritaniAtomics?.territory === to)
|
||||||
if (g.moritaniAtomics?.territory === to)
|
return 'Atomics Aftermath permanently blocks shipments into this territory.';
|
||||||
return 'Atomics Aftermath permanently blocks shipments into this territory.';
|
if (g.homeworlds || action.homeworldSources !== undefined) {
|
||||||
|
if (action.type !== 'ship' || !g.homeworlds || action.homeworldSources === undefined)
|
||||||
|
return null;
|
||||||
|
// Off-planet Homeworld pools have the same arrival triggers. Only the
|
||||||
|
// public reaction overlap is quoted here; the engine owns source and price.
|
||||||
|
order = {
|
||||||
|
player: me.id,
|
||||||
|
origin: 'homeworld',
|
||||||
|
to,
|
||||||
|
advisors: arrivalAsAdvisor(g, me, to),
|
||||||
|
wantsFighters: false,
|
||||||
|
};
|
||||||
|
} else if (action.type === 'ship') {
|
||||||
const amount = action.noField === undefined ? action.amount : 1;
|
const amount = action.noField === undefined ? action.amount : 1;
|
||||||
if (
|
if (
|
||||||
typeof amount !== 'number' ||
|
typeof amount !== 'number' ||
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ import test from 'node:test';
|
|||||||
import { botArrivalBlock } from '../game/bot-arrival';
|
import { botArrivalBlock } from '../game/bot-arrival';
|
||||||
import { botActions } from '../game/bots';
|
import { botActions } from '../game/bots';
|
||||||
import { applyAction, viewGame, type Action, type Game } from '../game/engine';
|
import { applyAction, viewGame, type Action, type Game } from '../game/engine';
|
||||||
|
import { createHomeworldCustody } from '../game/homeworld-custody';
|
||||||
|
import { homeworldContext } from '../game/homeworld-game';
|
||||||
|
import { createTerrorState } from '../game/moritani-terror';
|
||||||
import {
|
import {
|
||||||
arrivalMove,
|
arrivalMove,
|
||||||
arrivalPlayer,
|
arrivalPlayer,
|
||||||
@@ -52,6 +55,35 @@ void test('all four profiles avoid the reproduced Ambassador/Terror entries and
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
void test('Homeworld-sourced AI shipments avoid unsupported paired entry reactions', () => {
|
||||||
|
const game = movementArrivalGame();
|
||||||
|
game.homeworlds = { custody: createHomeworldCustody(homeworldContext(game)) };
|
||||||
|
arrivalPlayer(game, 'tleilaxu').shipped = false;
|
||||||
|
const action: Action = {
|
||||||
|
type: 'ship',
|
||||||
|
territory: 'carthag',
|
||||||
|
sector: 11,
|
||||||
|
amount: 1,
|
||||||
|
elite: 0,
|
||||||
|
homeworldSources: { 'homeworld:tleilaxu': { normal: 1, elite: 0 } },
|
||||||
|
};
|
||||||
|
assert.throws(() => applyAction(game, 'tleilaxu', action), overlap);
|
||||||
|
const before = JSON.stringify(game);
|
||||||
|
for (const profile of profiles) {
|
||||||
|
const view = ownView(game, profile);
|
||||||
|
assert.match(botArrivalBlock(view, action) ?? '', overlap);
|
||||||
|
const choices = botActions(view);
|
||||||
|
assert.ok(choices.length, profile);
|
||||||
|
assert.equal(choices.some(candidate => candidate.type === 'ship' &&
|
||||||
|
candidate.territory === 'carthag'), false, profile);
|
||||||
|
assert.ok(applyAction(game, view.me, choices[0]));
|
||||||
|
}
|
||||||
|
assert.equal(JSON.stringify(game), before);
|
||||||
|
game.moritaniTerror = createTerrorState(() => 0.2);
|
||||||
|
assert.equal(botArrivalBlock(ownView(game), action), null);
|
||||||
|
assert.ok(applyAction(game, 'tleilaxu', action));
|
||||||
|
});
|
||||||
|
|
||||||
void test('single reactions and matching-faction Ambassador immunity remain playable', () => {
|
void test('single reactions and matching-faction Ambassador immunity remain playable', () => {
|
||||||
for (const tokens of ['ambassador', 'terror'] as const) {
|
for (const tokens of ['ambassador', 'terror'] as const) {
|
||||||
const game = movementArrivalGame('tleilaxu', tokens);
|
const game = movementArrivalGame('tleilaxu', tokens);
|
||||||
|
|||||||
@@ -882,10 +882,10 @@ void test('Guild groups from several sectors pay one rounded shipment cost', ()
|
|||||||
type: 'guildShip',
|
type: 'guildShip',
|
||||||
forces: { 'pasty_mesa:5': 1, 'pasty_mesa:6': 2 },
|
forces: { 'pasty_mesa:5': 1, 'pasty_mesa:6': 2 },
|
||||||
};
|
};
|
||||||
const returned = applyAction(g, 'p0', { ...action, territory: 'reserves' });
|
const returned = passResponses(applyAction(g, 'p0', { ...action, territory: 'reserves' }));
|
||||||
assert.equal(returned.players[0].spice, before - 2);
|
assert.equal(returned.players[0].spice, before - 2);
|
||||||
assert.equal(returned.players[0].reserves, 18);
|
assert.equal(returned.players[0].reserves, 18);
|
||||||
g = applyAction(g, 'p0', { ...action, territory: 'sietch_tabr', sector: 14 });
|
g = passResponses(applyAction(g, 'p0', { ...action, territory: 'sietch_tabr', sector: 14 }));
|
||||||
assert.equal(g.players[0].spice, before - 2);
|
assert.equal(g.players[0].spice, before - 2);
|
||||||
assert.equal(g.players[0].forces['sietch_tabr:14'], 3);
|
assert.equal(g.players[0].forces['sietch_tabr:14'], 3);
|
||||||
conservation(g);
|
conservation(g);
|
||||||
|
|||||||
Reference in new issue
Block a user