Canonical instructions for AI assistants (Kiro, Cursor, Copilot, Codex, etc.) working in this repo. This is the single source of truth; tool-specific configs (.kiro/steering/, .cursor/rules/) just point here.
Sibling under IWGF: Subspace Lattice (../subspace-lattice/). Shared federation surfaces: ../leaderboard/ → iwgf.org, ../ops/ → ops.iwgf.org. Shared Firebase project warp-12; see ../AGENTS.md and FEDERATION.md.
Warp ships two rulesets under one app:
- Warp 6 — Classic Dominoes (double-6): Draw, Block, and All Fives on a single shared line (optional spinner). Not multi-trail Interstellar Warp. Exhibition only (unrated).
- Warp 9 / 12 / 15 / 18 — Multi-trail Interstellar Dominoes (double-N sets): hub-and-spoke play (Spacedock, personal Warp Trails, Neutral Zone, beacons) plus opt-in modules.
It ships as a web app (warp.iwgf.org) and as a Tauri desktop/mobile app (macOS, iOS, Android).
- Games are sectors/missions; players are Captains; the host runs a fleet (size capped by Warp factor: Classic 4; multi-trail 4 / 8 / 12 / 18).
- Multi-trail objectives, chosen before launch:
- Points campaign (default): Spacedock double descends maxPip→0 (10 / 13 / 16 / 19 rounds), lowest cumulative pip total wins.
- Go out: first captain to empty their hand wins immediately.
- Classic objectives: play to a target score (winner scores opponents’ remaining pips; All Fives also scores live multiples of five on open ends).
- TEI = the OpenSkill-based leaderboard rating — Warp 12 multi-trail only. Warp 6 / 9 / 15 / 18 are exhibition (unrated). Independent Go-out and Points tracks, each split by AI commission track (Ensign / Lieutenant / Commander). Solo unassisted matches and online human-pool sectors are rated on double-12. Using the tactical advisor disqualifies a match from TEI. Online sectors are auto-rated (context B: humans anchored against Ensign–Commander AI) when all human seats are verified, no Class I* is aboard, the host opts in (rated=true), maxPip is 12, and no captain used the advisor. The host may toggle Rated sector before launch on Warp 12; casual / exhibition sectors never update TEI.
- Subspace messaging = in-game comms. Quick-phrase hails (five category groups) are always available. Free-form text + DMs are allowed in the lobby, casual active play, and post-game — but restricted to hails only during live rated play to prevent collusion. Per-user mute and rate-limiting are client-enforced; comms rules are also enforced server-side via Firestore security rules.
Authoritative rules: RULES.tex / RULES.md (Sections I–V = multi-trail core, VI = modules + Official Warp preset, VII = AI/advisor, VIII = TEI/leaderboard, IX = Subspace messaging). Warp 6 Classic is a separate manual: RULES-Classic.tex / RULES-Classic.md (line play, mission profiles, Sector Telemetry house rules). Full architecture/setup: README.md.
- Monorepo: Nx 23 + Yarn 4 (
yarn@4.17.0). Root package@warp12/source(private). Workspaces:apps/*,libs/*,functions,vendor/*. - Language: TypeScript ~5.9,
strict, noany. ESM (module/moduleResolution= esnext/bundler), target es2022. - Frontend: React 19, react-dom 19, react-router-dom 6.30.
- Build: Vite 8 (per-project
vite.config.mts), SWC available,vite-plugin-dtsfor lib d.ts. - Test: Vitest 4 (jsdom +
@testing-library/react) for unit; Playwright for e2e. Tests are co-located*.spec.ts(x). - Styling: SCSS / CSS Modules (
*.module.scss). Nx generators defaultstyle: scss. - State: React Context (
*-context.tsx) + hooks. No Redux/Zustand. Engine state is immutable pure-function data. - Desktop/mobile: Tauri 2 (Rust edition 2021) at
apps/Warp12/src-tauri/. Frontend is a plain SPA — game logic is all in JS; no custom Rust commands. - Backend: Firebase 12 (anonymous Auth, Firestore, Functions, Hosting), project
warp-12. Browser AI inference via onnxruntime-web (WebNN → wasm fallback).
There are no tsconfig paths. Resolution uses package exports with the custom condition @warp12/source pointing to each lib's src/index.ts (source-first). The app vite.config.mts adds resolve.alias for double-eighteen, warp12-engine, warp12-react, warp12-theme → their source.
Scripts call Vite/Vitest directly and avoid nx run orchestration (which can hang). Prefer these over yarn nx run ….
Build (order matters — deps first):
yarn build:all→ double-eighteen → engine → react → theme → bridge → functions (tsc+ staged vendor for Cloud Functions)- Individual:
build:double-eighteen | build:engine | build:react | build:theme | build:bridge | build:leaderboard build:all:hostingadds the leaderboard SPA
Test:
yarn test:engine | test:react | test:bridgeyarn test:functions— pure unit (no Firebase SDK side effects)yarn test:functions:emulator— callable integration vs Auth+Firestore+Functions emulatorsyarn test:e2e(builds bridge, then Playwright; Chromium only — setPLAYWRIGHT_ALL_BROWSERS=1for Firefox/WebKit)yarn test:e2e:firebase— Playwright lobby/play smoke vs Auth+Firestore emulatorsyarn test:all— libs + functions unit + functions emulator callables + UI e2e + leaderboard e2e + firebase Playwright
Serve / preview: yarn serve:bridge (Vite dev :4200), yarn preview:bridge (:4300).
Tauri: tauri:dev, tauri:build, tauri:ios:dev/build, tauri:android:dev/build; packaged build:mac, build:android.
Deploy (requires FIREBASE_PROJECT in .env or ENV): deploy:firestore | deploy:functions | deploy:hosting | deploy:firebase.
AI training/calibration:
calibrate:ai-tei— AI tier calibration (points objective, 200 games)calibrate:ai-tei-dti— with Drop to Impulse house rulecalibrate:openskill/calibrate:openskill:quick— FFA OpenSkill anchor calibrationcalibrate:openskill-squad/calibrate:openskill-squad:quick— Module Zeta 2v2 team-rating orderingcalibrate:modules— Module balance testing (500 games, verbose reporter, runs with parallelism)calibrate:modules:quick— Quick module test (100 games, verbose reporter)calibrate:modules:dot— Dot reporter (compact, shows dots for each completed test)optimize:ai-weights— Heuristic weight optimizationclass1-star:*— Neural training pipeline (PyTorch intools/nn/)fleet-admiral:bench*— Search algorithm benchmarks
Parallel execution:
- Module calibration automatically uses 8-14 threads on M4 Max (configured in
libs/engine/vite.config.mts) - Vitest 4 uses top-level thread options:
minThreads,maxThreads(not nested underpoolOptions) - Luck/skill studies use explicit worker pools (15 workers default, see
tools/nn/run-comprehensive-parallel-fine.sh) - To modify thread count for vitest, edit
minThreadsandmaxThreadsin the engine's vite config
Orphaned Nx processes block runs. Recover with:
yarn nx:unlock # stop daemon + clear cache
pkill -f "Warp12/node_modules/nx" # kill leftovers (safe, repo-scoped)Then use the direct yarn build:* / test:* / serve:bridge scripts.
Module calibration (via vitest) automatically uses parallel threads (4-8 workers):
# Full calibration (500 games per config, ~15-25 min with parallelism)
MODULE_CALIBRATION_REPORT=1 yarn calibrate:modules
# Quick test (100 games per config, ~3-5 min)
yarn calibrate:modules:quickLuck/skill analysis uses CPU-aware worker pools (default: cores − 2):
# Full study (500 games × 38 configs). Workers auto-detect unless overridden.
COMPREHENSIVE_GAMES=500 bash tools/nn/run-comprehensive-parallel-fine.sh
# Adjust worker count for your hardware
COMPREHENSIVE_WORKERS=8 bash tools/nn/run-comprehensive-parallel-fine.sh # conservative
COMPREHENSIVE_WORKERS=32 bash tools/nn/run-comprehensive-parallel-fine.sh # high-endThread pool configuration:
- Module tests: Edit
libs/engine/vite.config.mts→test.minThreadsandtest.maxThreads(Vitest 4 top-level API) - Luck/skill: Use
COMPREHENSIVE_WORKERS/MODULE_WORKERS(or leave unset for cores − 2) - Rule of thumb: Use (CPU cores - 2) for worker count to avoid thrashing
- Org/signing/deploy settings live in repo-root
.env(see.env.example); never commit secrets
apps/
Warp12/ @warp12/Warp12 — "The Bridge" React/Firebase client + Tauri host (private)
Warp12-e2e/ Playwright e2e
libs/
engine/ warp12-engine — rules state machine, AI, advisor (published)
react/ warp12-react — RoundState→trains adapters, tactical coach, hand layout (published)
theme/ warp12-theme — federation domino skins (published)
tei-core/ @warp12/tei-core — TEI/OpenSkill core (private, src-only)
vendor/
double-eighteen/ git submodule, own Nx workspace — domino rendering (opaque dependency)
functions/ @warp12/functions — Firebase Cloud Functions
Warp12-leaderboard/ (moved) → IWGF sibling `../leaderboard/` → iwgf.org
apps/WarpOps/ (moved) → IWGF sibling `../ops/` → ops.iwgf.org
Hosting staging: `federation-hosting/{leaderboard,ops}/` via `scripts/sync-federation-hosting.sh`
tools/nn/ Class I* neural training pipeline (Python/PyTorch → ONNX)
docs/ Jekyll docs site (calibration log, TEI paper)
app/— UI: pages, dialogs, HUD, table view, coach panel, comms panel, contexts (*-context.tsx).firebase/— auth,game-service, sync,schema, stats/rating, serialize,messages(subspace comms).game/— local game orchestration, AI captain wiring, sounds, match stats, presets,quick-comms(phrase catalog),comms-mode,message-rate-limit.ai/— ONNX Class I* model loading (ort-session,load-class1-star-scorer).content/— rules/paper/privacy markdown sources.theme/,test/.
libs/<name>/src/index.ts barrel → src/lib/**. Each lib has tsconfig.json + tsconfig.lib.json + tsconfig.spec.json + vite.config.mts. Files use kebab-case.
vendor/double-eighteen is a submodule with its own Nx workspace. Treat it as opaque: parent only runs build:double-eighteen. Do not run parent nx against its internal projects; develop it standalone (cd vendor/double-eighteen && yarn start).
Deterministic, immutable rules engine. Never mutate state in place — pure functions return new state. All engine types (GameState, RoundState, TableState) are readonly.
types/—game-state.ts,actions.ts,trails.ts,anomalies.ts,player.ts,objective.ts,modules.ts,house-rules.ts,classic-rules.ts,continuum.ts,coordinate.ts.setup/create-game.ts+setup/create-classic-game.ts+constants/setup.ts— deal, hand sizes, Spacedock (multi-trail) / classic deal.engine/— the state machine:apply-action.ts(reducer; routes onruleset),legal-moves.ts, classic siblings (*-classic.ts),beacon.ts,scoring.ts,round-resolution.ts, …table/—table-state.ts(hub-and-spoke),line-table-state.ts(classic line + spinner),pip-inventory.ts,fracture-stabilizers.ts.domino/coordinates.ts— tile model.ai/—create-warp-ai.ts, heuristics, skill/tactical-class profiles, lookahead, ISMCTS, advisor (explain-*), Class I* neural (class1-star-policy.ts,feature-encoder.ts,residual-scorer.ts), calibration/self-play,fleet-admiral.ts.- Classic (Warp 6) has its own AI stack — the multi-trail heuristics and Ω do not apply to a single-line board:
classic-evaluation.ts(ten named tactics + unseen-tile opponent model),classic-skill.ts(mode-aware Ensign/Lieutenant/Commander tiers),create-classic-ai.ts,classic-advisor.ts(ranked plays + reasons),classic-self-play.ts(match harness for strength tests). Classic officers must reason only from their own hand plus the face-up board — never another captain's tiles. No Ω / Class I* training for Classic — exhibition-only heuristic tiers. Do not runomega:*orcalibrate:ai-teiexpecting Classic coverage.
- Classic (Warp 6) has its own AI stack — the multi-trail heuristics and Ω do not apply to a single-line board:
table/line-table-state.ts— Classic line + Multi-Vector Spacedock. ALL_DOUBLES promotes every double into a nested four-way hub (childHub); routes carrypath: LineEnd[]from root to that hub (inbound face blocked). Nested hubs inherit Docking Airlocks (spinnerUnlock: OPEN / EAST_WEST_FIRST / CROSS_FIRST); inbound counts as already attached for flank/cross checks. Depth is unbounded in the engine; binary-v2 encodes paths up toMAX_LINE_PATH_DEPTH(16) viaCHART_LINE_PATH.
Ruleset discriminator: GameState.ruleset is 'multi-trail' | 'classic' (default 'multi-trail' for legacy). Warp 6 always uses classic (line/deque + optional spinner). Warp 9+ always use multi-trail. Do not express classic as “multi-trail with trains disabled” — board topology differs.
| Multi-trail | Warp |
|---|---|
| Domino | Navigational Coordinate |
| Engine/station double | Spacedock |
| Personal train | Warp Trail |
| Community / public train | Neutral Zone |
| Train marker | Distress Beacon (Shields Down) |
| Boneyard | Uncharted Sectors |
Warp-specific mechanics: Red Alert (unsatisfied double), Subspace Fracture (optional chicken-foot on doubles; scope Own Trail / All Captains / All Doubles), Flash / Continuum (Module Alpha anomaly on 0-0), All Stop! (round-win ceremony), Drop to Impulse (uno/knock announce). "Warp 12" = the double-twelve set (max pip). AI officers rated Ensign / Lieutenant / Commander (Commander runs the Ω neural policy); experimental neural tier is Class I*; Fleet Admiral is the benchmark harness.
- Strict typing — no
any. Engine domain types live inwarp12-engine. - Immutability — engine state is never mutated in place.
- Tests co-located as
*.spec.ts(x); add/update tests with behavior changes and run the relevanttest:*script. - Kebab-case filenames; SCSS modules
*.module.scss. - Firestore layout:
games/{gameId}(public) +/hands/{playerId}(private) +/messages/{id}(subspace comms) +/presence/{playerId}(coach presence),playerStats/{uid},playerProfiles/{uid},publishedLogs/{logId},ratedMatches/{code}. Config at root:firebase.json,.firebaserc,firestore.rules,firestore.indexes.json. - Prefer the direct
yarnscripts overnx run. Build lib deps before dependents.