Open Tleilaxu special Karama throughout Revival

Permit native Advanced quiet any-player declarations alongside the original target-bound pending request. Preserve completed returns and charges. Adopt explicit provisional Ghola inclusion during the remaining Revival and phase expiry; keep printed Emperor special immunity and the existing low-Homeworld guard. Connect persistent controls and minimal legal bots through the shared quote.

Validation: 14 related files148/148, types/lint/build, six actual native control/engine/GameTable SSR journeys and four existing-profile continuations. Private-driver-only feedback reuses passed source-identical checks. Native111/113-action setup, paid shipment and battle sources earn originals. No saved-room/D1/restart/deployment operation; full modes and browser/deployed acceptance remain open.
This commit is contained in:
Quintine committed 2026-10-24 19:30:21 +11:00
1 parent 6ea691255a
commit 1a505525a8
18 files changed
+977 -59

No files matched your search

+5 -17
View File
@@ -55,6 +55,7 @@ import { CharityClaim } from './charity-claim';
import { FremenRevivalGrant } from './fremen-revival-grant';
import { AdvisorPreparation } from './advisor-preparation';
import { AtreidesFullPlan } from './atreides-full-plan';
import { TleilaxuSpecialKarama } from './tleilaxu-special-karama';
import { homeworldRevivalActionBlock } from '@/game/homeworld-revival-deployment-options';
import { GuildHomeworldShipment } from './guild-homeworld-shipment';
import { JunctionTransport } from './junction-transport';
@@ -1977,6 +1978,7 @@ export function GameTable({
<Recruits game={g} act={act} busy={busy} />
<AdvisorPreparation game={g} act={act} busy={transportBusy || !!g.roomControl?.paused || !!g.roomControl?.closed || !!me.autopilot} />
{g.decision?.kind !== 'fullPlanOffer' && <AtreidesFullPlan game={g} act={act} busy={transportBusy || !!g.roomControl?.paused || !!g.roomControl?.closed || !!me.autopilot} />}
{g.decision?.kind !== 'revivalStop' && <TleilaxuSpecialKarama game={g} act={act} busy={transportBusy || !!g.roomControl?.paused || !!g.roomControl?.closed || !!me.autopilot} />}
<NexusTraitors game={g} act={act} busy={transportBusy || (!!g.roomControl?.paused || !!g.roomControl?.closed) || !!me.autopilot} />
<DiscoveryPanel game={g} act={act} busy={transportBusy || (!!g.roomControl?.paused || !!g.roomControl?.closed) || !!me.autopilot} />
<LeaderSkillsPanel skills={g.leaderSkills} leaders={g.allLeaders} players={g.players} act={act} busy={transportBusy || (!!g.roomControl?.paused || !!g.roomControl?.closed) || !!me.autopilot} />
@@ -3161,8 +3163,8 @@ export function GameTable({
declared a{' '}
{g.decision.revival === 'forces' ? 'force' : 'leader'}{' '}
revival. Allow it or use your once-per-game special Karama
to prevent their normal force and leader revivals for this
turn.
to prevent their further force and leader revivals during
this Revival Phase, including Ghola.
</p>
{g.decision.source === 'nexusEmperor' && (
<p className="notice">
@@ -3171,21 +3173,7 @@ export function GameTable({
income; both the Nexus and your special Karama remain spent.
</p>
)}
<HelpTip topic="tleilaxuSpecial" />
{g.revival.specialKaramaBlock && (
<p role="status">{g.revival.specialKaramaBlock}</p>
)}
{me.hand
?.filter(canActivateKarama)
.map((c) => (
<div key={c.id}>
{actionButton(
'Spend special Karama · prevent revival',
{ type: 'card', card: c.id, mode: 'special' },
!!g.revival.specialKaramaBlock,
)}
</div>
))}
<TleilaxuSpecialKarama game={g} act={act} busy={busy} />
{actionButton('Allow this revival', {
type: 'decision',
decline: true,
+93
View File
@@ -0,0 +1,93 @@
'use client';
import { ArrowRight } from 'lucide-react';
import { Button } from '@/components/ui/button';
import type { Action, GameView } from '@/game/engine';
export function TleilaxuSpecialKarama({
game,
act,
busy,
}: {
game: GameView;
act: (action: Action) => void;
busy: boolean;
}) {
const offer = game.tleilaxuSpecialKarama;
const me = game.players.find((player) => player.id === game.me);
if (!offer || game.status !== 'playing' || !game.advanced ||
game.phase !== 4 || me?.faction !== 'tleilaxu') return null;
const ownStop = offer.pending && game.decision?.kind === 'revivalStop' &&
game.decision.player === game.me;
const priorityPending = !!game.response || !!game.phaseOpening || !!game.truthtrance ||
!!game.nexusTraitors?.pending || (!!game.decision && !ownStop) || (offer.pending && !ownStop);
const blocked = offer.blocked || (priorityPending
? 'Finish the current response, decision or phase opening before using this special Karama.'
: null);
return (
<section className="notice space-y-3" aria-label="Tleilaxu special Karama revival prevention">
<h3 className="font-serif text-xl">Prevent revival</h3>
<p className="muted">
Spend your once-per-game special Karama to prevent one faction’s force
and leader revivals for the rest of this Revival, including returns from
Ghola Treachery. You may choose
yourself or an ally, even if the chosen faction is ready or has already
completed revivals.
</p>
<p className="fine">
Completed returns and payments stay unchanged. This power does not
remove returned pieces or refund spice.
</p>
<p className="fine">
Provisional rule: Ghola Treachery is included during the
affected Revival, and this prevention expires when the phase ends. It
does not stop Ghola outside Revival.
</p>
<p className="fine">
The Emperor’s native special Karama remains protected by the printed
immunity of special Karama powers.
</p>
{offer.pending && (
<p className="fine">Only the original recipient can be selected during this pending revival.</p>
)}
{blocked && <p className="notice" id="tleilaxu-special-karama-unavailable">{blocked}</p>}
{!offer.cards.length && (
<p className="muted">No spendable Karama is currently available for this special power.</p>
)}
{!offer.targets.length && (
<p className="muted">No faction is currently available for this revival-prevention power.</p>
)}
{offer.targets.map((target) => {
const name = game.players.find((player) => player.id === target.player)?.name ?? 'the selected faction';
const targetReasonId = `tleilaxu-special-karama-${target.player}-unavailable`;
const describedBy = [
blocked ? 'tleilaxu-special-karama-unavailable' : undefined,
target.blocked ? targetReasonId : undefined,
].filter(Boolean).join(' ') || undefined;
const disabled = busy || !!blocked || !!target.blocked;
return (
<div key={target.player} className="space-y-2">
{target.blocked && <p className="notice" id={targetReasonId}>{name}: {target.blocked}</p>}
<div className="flex flex-wrap gap-2">
{offer.cards.map((card) => (
<Button
key={card}
data-testid={`tleilaxu-special-karama-${card}-${target.player}`}
className="game-action min-h-11 whitespace-normal"
disabled={disabled}
aria-describedby={describedBy}
onClick={() => {
if (!disabled) act({ type: 'card', mode: 'special', card, target: target.player });
}}
>
Spend special Karama · prevent revival for {name}
<ArrowRight size={15} />
</Button>
))}
</div>
</div>
);
})}
</section>
);
}