From 3efda2128e5b39c2c5dcedbe939871416da87d84 Mon Sep 17 00:00:00 2001 From: Quintine <15965533+Quintine@users.noreply.github.com> Date: Mon, 21 Sep 2026 14:36:27 +1000 Subject: [PATCH] Connect Basic Ixians to the full Leader Skills prototype Admit genuine Basic Ixian setup with the full fourteen-skill deck, real starting-card choice, optional Tleilaxu/base opponents and first-Storm HMS. Keep public starts, Advanced and other module combinations gated. Share per-origin cyborg movement range across engine, controls and legal bots. Preserve independent Planetologist range after native Karama cancellation. Connect Sandmaster force routes through room-local HMS pointer adjacency. Retain canceled movement's no-op contract while validating actual execution. Resolve Suk rescue before substitution with explicit ambiguous cyborg rescue origins and exact remaining Tank losses. Finish Rihani before native substitution, bind saved physical losses and declared exchange, and resume mandatory/optional winner disposal on allow, decline or cancellation. Admit exact green Ix Thumper, Harvester and Amal as Planetologist substitutes. Validation: types, lint, 5,082 offline tests and production build passed. Five genuine 2-6 player games completed with zero rejected actions and 54 JSON restores. Authenticated in-memory concurrent starting-card recovery, private setup, physical card/force custody and independent review passed. Browser FLGHBSA4 completed real setup, first Storm/HMS placement and a conserved staged Suk battle. Mobile rescue, refresh and one-for-one substitution passed. Screenshot capture timed out; full visual acceptance remains open. All 1,582 prior rooms, 3,350 human seats and auxiliary records were preserved; one new QA room/seat remains. No restart, reset or automation. AI work remains limited to legal participation until every non-AI feature is complete, then targets roughly 75% higher-tier wins for each adjacent pair. --- README.md | 4 +- components/planetologist-movement.tsx | 10 +- components/sandmaster-movement.tsx | 10 +- components/suk-graduate.tsx | 4 +- docs/CURRENT_STATUS.md | 43 +-- docs/IMPLEMENTATION_STATUS.md | 10 + docs/IX_LEADER_SKILLS.md | 84 +++++ docs/LEADER_SKILLS_RUNTIME.md | 9 + docs/RULE_DECISIONS.md | 1 + docs/TLEILAXU_LEADER_SKILLS.md | 3 +- docs/VERIFICATION_WORKFLOW.md | 4 + game/bot-mobility.ts | 20 +- game/engine.ts | 171 ++++++--- game/leader-skill-combat.ts | 12 +- game/leader-skill-profile.ts | 12 +- game/planetologist-movement.ts | 28 ++ game/reference.ts | 25 +- game/sandmaster-movement.ts | 35 +- game/suk-graduate.ts | 39 +- tests/faction-games-cli.test.ts | 20 + tests/ix-skills-battle.test.ts | 266 +++++++++++++ tests/ix-skills-fixture.ts | 197 ++++++++++ tests/ix-skills-movement.test.ts | 439 ++++++++++++++++++++++ tests/ix-skills-recovery.test.ts | 70 ++++ tests/ix-skills-suk.test.ts | 41 ++ tests/planetologist-movement.test.ts | 2 +- tests/tleilaxu-skills-integration.test.ts | 4 +- tools/faction-games.ts | 27 +- 28 files changed, 1461 insertions(+), 129 deletions(-) create mode 100644 docs/IX_LEADER_SKILLS.md create mode 100644 tests/ix-skills-battle.test.ts create mode 100644 tests/ix-skills-fixture.ts create mode 100644 tests/ix-skills-movement.test.ts create mode 100644 tests/ix-skills-recovery.test.ts create mode 100644 tests/ix-skills-suk.test.ts diff --git a/README.md b/README.md index 4a251f46..0fcc1181 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,9 @@ The local Leader Skills prototype also supports Basic Moritani with base opponents and the full fourteen-card deck. See [entry, connected interactions and remaining boundaries](docs/MORITANI_LEADER_SKILLS.md). Basic Tleilaxu also supports the full deck, native Rihani exchanges and saved revival choices; see -[its integration boundary](docs/TLEILAXU_LEADER_SKILLS.md). +[its integration boundary](docs/TLEILAXU_LEADER_SKILLS.md). Basic Ixians now connect +cyborg movement, HMS routes and battle aftermath through the same full deck; see +[Ixian integration](docs/IX_LEADER_SKILLS.md). Above the board, **Treachery draw pile** and **Spice draw pile** show live card counts separately from hands, auction cards and discards. See diff --git a/components/planetologist-movement.tsx b/components/planetologist-movement.tsx index e322c610..072615f1 100644 --- a/components/planetologist-movement.tsx +++ b/components/planetologist-movement.tsx @@ -5,6 +5,7 @@ import type { Action, GameView } from '@/game/engine'; import { planetologistLeader, planetologistMovementModeSupported, + selectedOriginElites, type PlanetologistMoveMode, } from '@/game/planetologist-movement'; import { botGroundMoveAllowed } from '@/game/bot-mobility'; @@ -93,7 +94,8 @@ export function planetologistMoveDraft( if ( selected.some( ([from]) => - !botGroundMoveAllowed(game, me, from, target, elites[from] ?? 0, mode), + !botGroundMoveAllowed(game, me, from, target, + selectedOriginElites(selected, elites, splitLocation(from).territory), mode), ) ) return { @@ -194,6 +196,12 @@ export function PlanetologistMovement({ Destination: {territory(destination).name}, sector {sector}. Select the physical forces to move below.

+ {me.faction === 'ixians' && ( +

+ Each origin uses its own selected cyborgs for movement range. Cyborgs + from one territory cannot carry suboids from the other territory farther. +

+ )} {sources.length ? ( sources.map(([from, owned]) => { const source = splitLocation(from); diff --git a/components/sandmaster-movement.tsx b/components/sandmaster-movement.tsx index f154dcbd..f78bd175 100644 --- a/components/sandmaster-movement.tsx +++ b/components/sandmaster-movement.tsx @@ -3,10 +3,8 @@ import { useId, useState } from 'react'; import type { Action, GameView } from '@/game/engine'; import { - GRAPH, gameTerritories, location, - mobileRouteDistance, splitLocation, } from '@/game/board'; import { @@ -17,6 +15,8 @@ import { sandmasterLeader, sandmasterModeSupported, sandmasterPathBlocked, + sandmasterAdjacent, + sandmasterRouteDistance, type SandmasterChoice, } from '@/game/sandmaster-movement'; import { Button } from './ui/button'; @@ -144,18 +144,18 @@ export function SandmasterMovement({ const extensions = last === destination ? [] - : (GRAPH[last] ?? []).filter( + : sandmasterAdjacent(game, last).filter( (next) => !route.includes(next) && !sandmasterPathBlocked(game, game.me, from, next) && - mobileRouteDistance([...route, next]) <= order.range, + sandmasterRouteDistance(game, [...route, next]) <= order.range, ); return (
{placeName(game, from)} route

{route.map((key) => placeName(game, key)).join(' → ')} ·{' '} - {mobileRouteDistance(route)} / {order.range} territories + {sandmasterRouteDistance(game, route)} / {order.range} territories