From d3559f65adc44b6dd991ad8a74f51c0e70170137 Mon Sep 17 00:00:00 2001
From: Quintine <15965533+Quintine@users.noreply.github.com>
Date: Mon, 21 Sep 2026 14:08:30 +1000
Subject: [PATCH] Connect Basic Tleilaxu to the full Leader Skills prototype
Admit genuine Basic Tleilaxu/base setup with the full fourteen-skill deck,
Ix card inventories and native three Face Dancers. Share public profile guards
across existing skill effects while retaining public-start and Advanced gates.
Record native own-leader skill offers at successful revival commitment so
automatic Tleilaxu response chains preserve the optional private draw. Connect
Rihani to unrevealed Face Dancer exchange with exact physical/status custody,
private controls and existing legal AI choices. Release completed Rihani's
live snapshot only after validated Face Dance admission; keep signed history
and current census checks. Zoal Smuggler uses opposing printed disc value at
reveal, excluding opposing skill bonuses and his separate revival value.
Add genuine setup, response/cancellation, private choice, custody, Face Dance
reset, Zoal, legal AI and authenticated concurrent recovery regressions. Add a
reproducible two-through-six-player sample profile and saved continuation.
Preserve unresolved skill effects, foreign-ghola boundaries and delayed AI tuning.
Validation: types, lint, 5,058 offline tests and production build passed.
Five full games completed with zero rejected actions and 74 JSON restores.
Browser QA CRY4DD4J used genuine setup and an explicitly conserved staged battle;
real Rihani draw/exchange and same-state refresh passed on mobile and desktop.
Screenshot capture timed out; full visual acceptance remains open. No HTTP
endpoint/auth/storage implementation changed; authenticated SQLite recovery was
verified. All 1,581 prior rooms, 3,349 seats and auxiliary records were preserved.
One new QA room/seat remains. No server restart, database reset or automation.
---
README.md | 4 +-
components/rihani-decipherer.tsx | 26 +-
docs/CURRENT_STATUS.md | 36 +--
docs/IMPLEMENTATION_STATUS.md | 10 +
docs/LEADER_BATTLE_EFFECTS.md | 5 +-
docs/LEADER_SKILLS_RUNTIME.md | 9 +-
docs/MORITANI_LEADER_SKILLS.md | 5 +-
docs/RULE_DECISIONS.md | 1 +
docs/TLEILAXU_LEADER_SKILLS.md | 86 ++++++
docs/VERIFICATION_WORKFLOW.md | 1 +
game/bureaucrat-payment.ts | 4 +-
game/engine.ts | 51 +++-
game/leader-skill-profile.ts | 15 +-
game/planetologist-movement.ts | 4 +-
game/reference.ts | 24 +-
game/rihani-decipherer.ts | 42 ++-
game/sandmaster-worm.ts | 4 +-
game/smuggler-battle.ts | 4 +-
tests/faction-games-cli.test.ts | 22 +-
tests/rihani-face-dancers.test.ts | 178 +++++++++++++
tests/sample-custody.test.ts | 4 +-
tests/tleilaxu-skills-fixture.ts | 164 ++++++++++++
tests/tleilaxu-skills-integration.test.ts | 150 +++++++++++
tests/tleilaxu-skills-recovery.test.ts | 69 +++++
tests/tleilaxu-skills-review.test.ts | 138 ++++++++++
tests/tleilaxu-skills-revival.test.ts | 311 ++++++++++++++++++++++
tools/faction-games.ts | 41 +--
tools/sample-custody.ts | 13 +-
28 files changed, 1324 insertions(+), 97 deletions(-)
create mode 100644 docs/TLEILAXU_LEADER_SKILLS.md
create mode 100644 tests/rihani-face-dancers.test.ts
create mode 100644 tests/tleilaxu-skills-fixture.ts
create mode 100644 tests/tleilaxu-skills-integration.test.ts
create mode 100644 tests/tleilaxu-skills-recovery.test.ts
create mode 100644 tests/tleilaxu-skills-review.test.ts
create mode 100644 tests/tleilaxu-skills-revival.test.ts
diff --git a/README.md b/README.md
index da7bb8b9..4a251f46 100644
--- a/README.md
+++ b/README.md
@@ -79,7 +79,9 @@ persists through refresh. See [Ecaz Loyalty](docs/ECAZ_LOYALTY.md).
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).
+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).
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/rihani-decipherer.tsx b/components/rihani-decipherer.tsx
index 322b066b..d1eaa798 100644
--- a/components/rihani-decipherer.tsx
+++ b/components/rihani-decipherer.tsx
@@ -13,13 +13,14 @@ function identity(game: GameView, id: string): LeaderDisplayIdentity | null {
const leader = game.allLeaders.find((l) => l.id === id);
return leader ? { id: leader.id, name: leader.name, factionName: faction(leader.faction).name, strength: leaderStrengthLabel(leader) } : null;
}
-function Card({ game, id }: { game: GameView; id: string }) {
+function Card({ game, id, faceDancer = false }: { game: GameView; id: string; faceDancer?: boolean }) {
const card = identity(game, id);
- return card ?
{card.name}
: Card identity unavailable.
;
+ return card ? {card.name}
: Card identity unavailable.
;
}
export function RihaniHistory({ game }: { game: GameView }) {
const history = game.rihani?.history;
+ const faceDancer = game.players.find(p => p.id === game.me)?.faction === 'tleilaxu';
if (!history?.length) return null;
return
Your private Rihani inspections
@@ -27,7 +28,7 @@ export function RihaniHistory({ game }: { game: GameView }) {
{[...history].reverse().map((r) =>
Turn {r.turn}
{r.peeked.length > 0 && <>
Normal inspection — returned to the shuffled deck
{r.peeked.map((id) =>
)}>}
- {r.drawn.length > 0 && <>
Separate skilled draw
{r.drawn.map((id) =>
)}>}
+ {r.drawn.length > 0 && <>
Separate skilled draw
{r.drawn.map((id) =>
)}>}
{r.kept &&
Kept {identity(game, r.kept)?.name}; revealed and returned {identity(game, r.given!)?.name}.
}
)}
;
@@ -38,24 +39,27 @@ export function RihaniChoice({ game, act, busy }: { game: GameView; act: (action
const pending = game.rihani?.pending;
if (!pending || pending.owner !== game.me) return null;
const blocked = busy || !!game.truthtrance;
+ const faceDancer = game.players.find(p => p.id === game.me)?.faction === 'tleilaxu';
+ const cardName = faceDancer ? 'Face Dancer' : 'Traitor';
+ const oldCard = faceDancer ? 'unrevealed' : 'unused';
if (pending.stage === 'offer') return
- You may draw two Traitor Cards. Once drawn, you must keep one new card by publicly revealing and returning one unused card you already hold.
+ You may draw two {cardName} Cards. Once drawn, you must keep one new card by publicly revealing and returning one {oldCard} card you already hold.
The other drawn card returns secretly. Your normal inspection, when available, has already returned its cards to the shuffled deck.
-
+
;
- return
- Keep one newly drawn card and reveal one unused card from your old hand. The revealed card and the other new card return to the shuffled deck.
-