Preflight Guild transport arrival candidates before bot play
This commit is contained in:
1 parent
882d006b3c
commit
3d95db88cc
5 files changed
+123
-6
No files matched your search
@@ -72,3 +72,27 @@ 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.
|
||||
|
||||
## 29 September: Guild cross-planet transport and Terror
|
||||
|
||||
An unmodified six-seat Advanced Moritani sample at base seed `20260930`
|
||||
completed 1,262 actions and 34 JSON restores but proposed six illegal
|
||||
cross-planet Guild transports into a placed Terror while Guild income was
|
||||
pending. The existing bot preflight covered ordinary shipments, not the
|
||||
single-source `guildShip` action.
|
||||
|
||||
The owner-view adapter now quotes those transports with the submitted source,
|
||||
physical count, Guild tariff and selected ally payment. It excludes only
|
||||
publicly unsupported arrival combinations; payment entirely funded by the
|
||||
Guild ally has no Guild income response and remains eligible. Multi-source,
|
||||
Nexus and special routes still use their separate adapters. No Terror face or
|
||||
rival private field is read. The authoritative engine still decides every
|
||||
actual transport.
|
||||
|
||||
The focused engine regression first reproduced the rejected Guild action,
|
||||
then exercised all four profile filters and a Guild-funded accepted action.
|
||||
Same-seed genuine setup replay completed the same 1,262 accepted actions with
|
||||
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.
|
||||
@@ -6,6 +6,16 @@ Expansion starts and full rules/publication acceptance remain gated.**
|
||||
|
||||
## Current checkpoint and work
|
||||
|
||||
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)
|
||||
now quotes the submitted single-source transport and payment using public
|
||||
arrival triggers. The same genuine game completes 1,262 accepted actions,
|
||||
zero rejected candidates and 34 JSON restores; both assassination choices
|
||||
were declined. This repairs AI candidate legality, not the unsupported
|
||||
arrival composition, natural assassination reveal coverage, normal
|
||||
expansion starts or deployed acceptance.
|
||||
|
||||
The owner-only [administrator accounts](ADMIN_ACCOUNTS.md) prototype now
|
||||
lists accounts, provisions a browser-generated one-time personal key, changes
|
||||
another account’s role and disables access. Server actions repeat live owner
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
# Dune implementation status
|
||||
|
||||
## 29 September 2026 — Guild transport arrival preflight
|
||||
|
||||
The [arrival preflight](BOT_ARRIVAL_PREFLIGHT.md#29-september-guild-cross-planet-transport-and-terror)
|
||||
now filters single-source Guild cross-planet transport candidates before
|
||||
they enter a publicly unsupported Terror/Guild-income reaction combination.
|
||||
A six-seat Advanced Moritani game at base seed `20260930` first completed
|
||||
1,262 accepted actions, six rejected Guild candidates and 34 JSON
|
||||
continuations. Same-seed replay after the fix completed 1,262 accepted
|
||||
actions, no rejected candidates and 34 JSON continuations. Two real
|
||||
assassination offers were declined; neither sample shows a natural reveal.
|
||||
|
||||
A focused engine test reproduced the illegal action before the fix, then
|
||||
covered all four bot filters and accepted Guild-funded transport with no
|
||||
income response. No card face, hidden rival data or unsupported arrival
|
||||
ordering enters the quote. Existing expansion start and publication gates
|
||||
stay closed.
|
||||
|
||||
`npm run check` passes typecheck, lint and 5,781/5,781 offline cases.
|
||||
No app route or persisted database changed; the production build and HTTP
|
||||
suite were not repeated for this bot-only correction.
|
||||
|
||||
## 29 September 2026 — owner account management prototype
|
||||
|
||||
The [account page](ADMIN_ACCOUNTS.md) lists only safe metadata to a live
|
||||
|
||||
+34
-4
@@ -7,6 +7,7 @@ import {
|
||||
type CompletedMovementArrivalInput,
|
||||
} from './karama-movement-preflight';
|
||||
import {
|
||||
guildShipmentCost,
|
||||
guildShipmentIncome,
|
||||
reserveShipmentCost,
|
||||
shipmentPaymentBounds,
|
||||
@@ -15,8 +16,9 @@ import { quoteSmugglerShipment } from './smuggler-shipment';
|
||||
|
||||
/**
|
||||
* Known arrival support for ordinary owner-view candidates, plus the public
|
||||
* overlap guard for Homeworld-sourced shipments. This is not a complete action
|
||||
* validator. Explicit Nexus and special movement routes retain their adapters.
|
||||
* overlap guard for Homeworld-sourced shipments and single-source Guild
|
||||
* transport. This is not a complete action validator. Explicit Nexus and
|
||||
* special movement routes retain their adapters.
|
||||
* No hidden Terror face or rival hand enters this quote; unexpected failures
|
||||
* are never treated as rejected candidates.
|
||||
*/
|
||||
@@ -25,7 +27,7 @@ export function botArrivalBlock(g: GameView, action: Action): string | null {
|
||||
g.status !== 'playing' ||
|
||||
g.phase !== 5 ||
|
||||
g.active !== g.me ||
|
||||
!['ship', 'move'].includes(action.type) ||
|
||||
!['ship', 'guildShip', 'move'].includes(action.type) ||
|
||||
action.nexus !== undefined ||
|
||||
action.source !== undefined ||
|
||||
typeof action.territory !== 'string' ||
|
||||
@@ -46,7 +48,35 @@ export function botArrivalBlock(g: GameView, action: Action): string | null {
|
||||
let order: CompletedMovementArrivalInput['order'];
|
||||
if (action.type === 'ship' && g.moritaniAtomics?.territory === to)
|
||||
return 'Atomics Aftermath permanently blocks shipments into this territory.';
|
||||
if (g.homeworlds || action.homeworldSources !== undefined) {
|
||||
if (action.type === 'guildShip') {
|
||||
if (
|
||||
action.forces !== undefined ||
|
||||
typeof action.from !== 'string' ||
|
||||
typeof action.amount !== 'number' ||
|
||||
!Number.isSafeInteger(action.amount) ||
|
||||
action.amount < 1
|
||||
) return null;
|
||||
const origin = action.from === 'reserves' ? 'reserves' : splitLocation(action.from);
|
||||
if (origin !== 'reserves' && !validLocation(origin.territory, origin.sector))
|
||||
return null;
|
||||
const guild = g.players.find((p) => p.faction === 'guild');
|
||||
const cost = guildShipmentCost(territory(to).type, action.amount,
|
||||
g.karamaShipping?.player === me.id || !g.guildRateCanceled);
|
||||
const bounds = shipmentPaymentBounds(cost, me.spice ?? 0, g.aid.available);
|
||||
const allyPayment = action.allyPayment ?? bounds.minimum;
|
||||
if (typeof allyPayment !== 'number' || !Number.isSafeInteger(allyPayment) ||
|
||||
allyPayment < bounds.minimum || allyPayment > bounds.maximum)
|
||||
return null;
|
||||
controls.response ||= guildShipmentIncome({
|
||||
guild: guild?.id, shipper: me.id, ally: me.ally, cost, allyPayment,
|
||||
bankOnly: g.karamaShipping?.player === me.id,
|
||||
}) > 0;
|
||||
order = {
|
||||
player: me.id,
|
||||
origin: origin === 'reserves' ? origin : origin.territory,
|
||||
to, advisors: arrivalAsAdvisor(g, me, to), wantsFighters: false,
|
||||
};
|
||||
} else 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
|
||||
|
||||
@@ -5,7 +5,7 @@ import { botActions } from '../game/bots';
|
||||
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 { createTerrorState, placeTerror } from '../game/moritani-terror';
|
||||
import {
|
||||
arrivalMove,
|
||||
arrivalPlayer,
|
||||
@@ -81,7 +81,39 @@ void test('Homeworld-sourced AI shipments avoid unsupported paired entry reactio
|
||||
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('Guild cross-planet transport avoids a Terror arrival blocked by its Guild income response', () => {
|
||||
const game = shipmentArrivalGame();
|
||||
const bg = arrivalPlayer(game, 'bg');
|
||||
const guild = arrivalPlayer(game, 'guild');
|
||||
game.active = bg.id;
|
||||
bg.shipped = false;
|
||||
bg.ally = guild.id;
|
||||
guild.ally = bg.id;
|
||||
bg.forces = { 'sietch_tabr:14': 2 };
|
||||
bg.reserves = 18;
|
||||
const terror = createTerrorState(() => 0.2);
|
||||
game.moritaniTerror = placeTerror(terror, terror.tokens[0].id, 'carthag', 2);
|
||||
const action: Action = { type: 'guildShip', from: 'sietch_tabr:14', amount: 2,
|
||||
territory: 'carthag', sector: 11 };
|
||||
assert.throws(() => applyAction(game, bg.id, 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 === 'guildShip' &&
|
||||
candidate.territory === 'carthag'), false, profile);
|
||||
assert.ok(applyAction(game, bg.id, choices[0]));
|
||||
}
|
||||
assert.equal(JSON.stringify(game), before);
|
||||
const funded = JSON.parse(before) as Game;
|
||||
funded.aid[guild.id] = { recipient: bg.id, amount: 1 };
|
||||
const guildPaid = { ...action, allyPayment: 1 };
|
||||
assert.equal(botArrivalBlock(ownView(funded), guildPaid), null);
|
||||
assert.ok(applyAction(funded, bg.id, guildPaid));
|
||||
});
|
||||
|
||||
void test('single reactions and matching-faction Ambassador immunity remain playable', () => {
|
||||
|
||||
Reference in new issue
Block a user