Connect capped extra range, two-origin gathering, Fremen cancellation and saved replacement choices. Add human controls and actual first-choice AI paths for all four profiles. Admit the exact nine base green Specials in the actual skilled leader's weapon slot for +2 on survival, with mandatory physical discard and no native utility effect. Reject stale dead-disc plans. Independent movement/battle review closed after the saved-dead-disc fix. 84 focused tests passed. Two four-profile Basic/Advanced samples completed (194/186 accepted actions, six JSON restorations each, zero rejected actions). Basic used both movement branches; Advanced used added range. Browser genuine setup/gather/refresh and a separate targeted Special/traitor-loss fixture passed. Required verification: types, lint, 4435 offline tests, build and 43 HTTP tests passed. First attempt stopped at missing explicit test promise markers; corrected before the successful broad run. Final check/build/HTTP durations: 144157/6638/25048 ms. Source fingerprint (1260 files): bb7be69cf3e925bf7338ea444f77279ed7b37faadc3c44ed7db01b941f27b861 Private report: checkpoints/20260913-planetologist/checks-final/report.json. Preserved all 325 opening games and all original application/seat/recovery records; 359 rooms after 34 new QA/HTTP rooms. SQLite quick_check ok and zero foreign-key violations. Healthy existing server reused; no reset/restart. Boundaries: eight other Leader Skills, enemy-only mixed BG gather response, expansion roster/green-card/optional-module combinations remain unfinished. Winning traitor caller discard remains a documented card-specific inference. No mode or publication gates opened. Efficiency measure: aggregate goal counter 67321978 -> 68710948 (1388970), including context and coordination; not billing tokens or a savings claim. One skill's three connected effects, selective implementation/research/review, one interrupted lint attempt followed by the required complete verification.
38 lines
1.1 KiB
TypeScript
38 lines
1.1 KiB
TypeScript
import type { Game, GameView } from './engine';
|
|
|
|
export type PlanetologistMoveMode = 'range' | 'gather';
|
|
export type PlanetologistMovement = {
|
|
leader: string;
|
|
mode: PlanetologistMoveMode;
|
|
};
|
|
|
|
/** Public component ownership suffices; a captive never grants the upper band. */
|
|
export function planetologistLeader(
|
|
g: Game | GameView,
|
|
player: string,
|
|
): string | null {
|
|
const assignment = g.leaderSkills?.assignments.find(
|
|
(a) => a.owner === player && a.skill === 'planetologist',
|
|
);
|
|
if (
|
|
!assignment ||
|
|
('controller' in assignment && assignment.controller !== player) ||
|
|
('faceUp' in assignment && assignment.faceUp === false)
|
|
)
|
|
return null;
|
|
const leader = g.players
|
|
.find((p) => p.id === player)
|
|
?.leaders.find((l) => l.id === assignment.leader);
|
|
if (!leader || leader.dead || leader.capturedBy || leader.gholaBy)
|
|
return null;
|
|
return leader.id;
|
|
}
|
|
|
|
/** The cap belongs to the extra-range alternative, not the two-origin one. */
|
|
export function planetologistRange(
|
|
base: number,
|
|
mode?: PlanetologistMoveMode,
|
|
): number {
|
|
return mode === 'range' ? Math.min(3, base + 1) : base;
|
|
}
|