The camera always wears the turn-taker's eyes, and knows whose turn zero is
Eric's rule, applied everywhere: every reel — moment, catch-up, whole tale — follows the wizard whose turn it is, the pov switching at each handover as boundaries scroll past. And the J8LL mystery dies with it: turn zero's opening boundary lives in the DEAL, before any command, so scanning a moment's steps found the NEXT turn's start instead and put the camera in the wrong wizard's head. momentSteps now names the owner from the very boundary that opened the turn — counted through the deal — and carries it to the client and the share page, where it also fixes the same latent mis-titling. Also aboard: the cutaway shot. When the eyes aim at something they cannot see — a wraith summoned across the maze, a spell through walls — the camera cuts to the target's cell for that beat, standing back down its deepest corridor, and cuts home on the next step. The wraith no longer materializes off-screen. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
6788de2f6e
commit
a28d27eedc
@@ -420,13 +420,22 @@ export function redactFor(events: GameEvent[], playerId: PlayerId): GameEvent[]
|
||||
* identically, so a turn number names the same stretch on both ends. */
|
||||
const TURN_BOUNDARY = new Set(["turnStarted", "extraTurnStarted", "turnSkipped"]);
|
||||
|
||||
export interface MomentReel {
|
||||
steps: CatchUpStep[];
|
||||
/** The wizard whose turn this is — the reel's eyes. */
|
||||
owner: PlayerId;
|
||||
round: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* One turn's reel: every command whose events touch turn `turnIndex`
|
||||
* (0-counted across boundary events). A command that ends one turn and
|
||||
* starts the next belongs to both, so a reel opens with the blow that
|
||||
* began it and closes on the handover.
|
||||
* began it and closes on the handover. The owner comes from the very
|
||||
* boundary that opened the turn — which for turn 0 lives in the DEAL,
|
||||
* before any command, so no scan of the steps could find it.
|
||||
*/
|
||||
export function momentSteps(room: Room, playerId: PlayerId, turnIndex: number): CatchUpStep[] | { error: string } {
|
||||
export function momentSteps(room: Room, playerId: PlayerId, turnIndex: number): MomentReel | { error: string } {
|
||||
if (!room.state) return { error: "game not started" };
|
||||
// The SPECTATOR builds share pages: public knowledge only, no seat.
|
||||
if (playerId !== SPECTATOR && !room.players.includes(playerId)) {
|
||||
@@ -438,14 +447,24 @@ export function momentSteps(room: Room, playerId: PlayerId, turnIndex: number):
|
||||
// The deal's own events open the first turn — count them, or every
|
||||
// turn number would sit one behind the chronicle's.
|
||||
let counter = -1;
|
||||
for (const e of dealt) if (TURN_BOUNDARY.has(e.type)) counter++;
|
||||
let owner: PlayerId | null = null;
|
||||
let round = 0;
|
||||
const bump = (e: GameEvent) => {
|
||||
if (!TURN_BOUNDARY.has(e.type)) return;
|
||||
counter++;
|
||||
if (counter === turnIndex && owner === null && "player" in e) {
|
||||
owner = (e as { player: PlayerId }).player;
|
||||
if ("round" in e) round = (e as { round: number }).round;
|
||||
}
|
||||
};
|
||||
for (const e of dealt) bump(e);
|
||||
const steps: CatchUpStep[] = [];
|
||||
for (const entry of room.log) {
|
||||
const result = applyCommand(current, entry.playerId, entry.command);
|
||||
if (!result.ok) return { error: `replay diverged at seq ${entry.seq}` };
|
||||
current = result.state;
|
||||
const before = counter;
|
||||
for (const e of result.events) if (TURN_BOUNDARY.has(e.type)) counter++;
|
||||
for (const e of result.events) bump(e);
|
||||
if (before > turnIndex) break;
|
||||
if (before <= turnIndex && turnIndex <= counter) {
|
||||
const prevAt = entry.seq > 0 ? room.log[entry.seq - 1]!.at : "";
|
||||
@@ -463,7 +482,7 @@ export function momentSteps(room: Room, playerId: PlayerId, turnIndex: number):
|
||||
}
|
||||
}
|
||||
if (steps.length === 0) return { error: "no such turn yet" };
|
||||
return steps;
|
||||
return { steps, owner: owner ?? steps[steps.length - 1]!.actor, round };
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user