Connect the GF9 FAQ combination: one free physical reserve force accompanies an owned concealed No-Field at its unchanged one-marker price. Preserve zero/three/five token values, actual supply at later reveal, optional human choice and all four legal AI paths. Genuine CHOAM & Richese Leader Skills development setup adds the two sourced battle cards without duplicating their Ix identities. Public mode and publication gates stay closed; allied and remaining combined routes are unfinished. Bind the private declaration across initial No-Field cancellation and later Advanced Guild interception, including saved interruptions. Validate before projection/actions/normalization, hide the internal proof, reject altered declarations immutably, and withdraw the companion once only on successful commitment. Independent rules/privacy/persistence review found no blocker. Preserve other audit setup gates despite expanded deck assembly. Repair the existing randomized recovery test with a genuine seeded Rihani offer and explicit human choice, replacing an inappropriate AI-policy call on a human seat. Initial Karama and later Guild stop retain their distinct existing opportunity outcomes. Validation: 72 focused regressions plus 10 deck tests; typecheck, lint, 4520 offline tests, production build and 43 HTTP tests passed. SQLite restart, concurrent allowance/cancellation and exact physical custody passed. Browser genuine assignment, private token selection, opt-in, one-spice shipment, refresh, five-force reveal and final refresh preserved six physical Arrakeen forces, 14 reserves, zero Tanks, four spice, Crysknife and Soo-Soo Sook. Genuine four-profile Basic/Advanced games completed in 77/116 actions with 3/4 JSON restores and no rejected candidates; Advanced exercised one companion shipment and later reveal. After the outage the existing server was healthy. Preserved all 529 opening rooms and original seat/recovery records (563 after QA/HTTP). SQLite integrity and foreign keys passed. No restart, database reset or automation. Verified source tree: c2aee4138bcb46a05814a340d62eadace3470247ca620844eb0375e78ea4d6c4
40 lines
1.3 KiB
TypeScript
40 lines
1.3 KiB
TypeScript
import type { Game, GameView } from './engine';
|
|
import { quoteSmugglerShipment } from './smuggler-shipment';
|
|
|
|
/** The No-Field remains the priced marker; this is one separate physical force. */
|
|
export type SmugglerNoFieldCompanion = { leader: string; amount: 1 };
|
|
|
|
export function smugglerNoFieldModeSupported(g: Game | GameView): boolean {
|
|
return (
|
|
g.expansions.length === 1 &&
|
|
g.expansions[0] === 'choam' &&
|
|
!g.homeworlds &&
|
|
!g.nexusCards &&
|
|
!g.discoveries &&
|
|
!('discoveryEnabled' in g && g.discoveryEnabled) &&
|
|
!g.strongholdCards &&
|
|
!g.techTokens
|
|
);
|
|
}
|
|
|
|
/** Public custody only: the hidden token value never affects this free companion. */
|
|
export function quoteSmugglerNoField(
|
|
g: Game | GameView,
|
|
player: string,
|
|
destination: string,
|
|
): SmugglerNoFieldCompanion | null {
|
|
const p = g.players.find((p) => p.id === player);
|
|
if (
|
|
!p ||
|
|
p.faction !== 'richese' ||
|
|
p.reserves < 1 ||
|
|
!Number.isSafeInteger(p.reserves) ||
|
|
!smugglerNoFieldModeSupported(g)
|
|
)
|
|
return null;
|
|
// Reuse the normal band's living/native/face-up/whole-territory emptiness checks.
|
|
// Two denotes one priced marker with one free companion, not a reserve withdrawal of two.
|
|
const skill = quoteSmugglerShipment(g, player, destination, 2);
|
|
return skill ? { leader: skill.leader, amount: 1 } : null;
|
|
}
|