What you missed, in facts; a timeline on the reel; room for table talk
A returning player's slip now lists what happened while they were away — "Automaton took your treasure.", "You lost 3 life.", "It's your turn." — each fact opening the catch-up reel at its moment, read from the missed steps as the server redacted them for that seat; when nothing touched them it says what each wizard was up to instead. The reel gains a timeline: a tick per move in the mover's color, taller where a turn begins, a blow lands, or gold changes hands, and any tick jumps the reel there. The chronicle takes a filter — All, Game, Table talk — with an unread count on the talk that arrived while the casting panel covered it (a wizard's own words never count), a peek at the latest line above the composer while the panel is up, and the composer itself no longer leaves when a response is owed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jm2auWk6RP71CjaAb4FMoG
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
80dd7a7865
commit
8582b7feb7
@@ -363,6 +363,8 @@ class Net {
|
||||
chatPending = $state<string | null>(null);
|
||||
/** Table talk that never arrived: handed back to the composer. */
|
||||
chatUnsent = $state<string | null>(null);
|
||||
/** Who spoke last at the table: your own words are never unread. */
|
||||
lastTalkBy = $state<string | null>(null);
|
||||
/** A finished table's call for a rematch: where it went, and who called. */
|
||||
rematchCall = $state<{ roomId: string; by: string } | null>(null);
|
||||
/** A rematch lobby: the last table's wizards not yet seated. */
|
||||
@@ -393,6 +395,11 @@ class Net {
|
||||
onFx: ((events: GameEvent[]) => void) | null = null;
|
||||
/** A catch-up reel delivered by the server. */
|
||||
catchUp = $state<{ seq: number; actor: string; events: GameEvent[]; view: GameView; chat?: { player: string; text: string }[] }[] | null>(null);
|
||||
/** The missed steps, fetched for the summary slip before any reel is opened. */
|
||||
missedSteps = $state<{ seq: number; actor: string; events: GameEvent[]; view: GameView; chat?: { player: string; text: string }[] }[] | null>(null);
|
||||
/** Where the catch-up reel opens: the step a summary line pointed at. */
|
||||
catchUpStart = $state(0);
|
||||
private catchUpWanted: "summary" | "reel" = "reel";
|
||||
/** One turn's reel, summoned from a chronicle line's instant-replay
|
||||
* eye — steps plus the wizard whose eyes the camera wears. */
|
||||
moment = $state<{ steps: { seq: number; actor: string; events: GameEvent[]; view: GameView; chat?: { player: string; text: string }[] }[]; owner: string } | null>(null);
|
||||
@@ -549,13 +556,17 @@ class Net {
|
||||
const last = this.seen[this.roomId] ?? 0;
|
||||
this.missedMoves = Math.max(0, msg.seq - last);
|
||||
if (this.missedMoves === 0) this.markSeen();
|
||||
// The missed steps come now, for the facts on the slip; the
|
||||
// reel waits for a tap.
|
||||
else if (!this.missedSteps) this.requestCatchUp(true);
|
||||
}
|
||||
if (document.visibilityState === "visible") this.watching = this.roomId;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "catchUp":
|
||||
this.catchUp = msg.steps;
|
||||
if (this.catchUpWanted === "summary") this.missedSteps = msg.steps;
|
||||
else this.catchUp = msg.steps;
|
||||
break;
|
||||
case "moment":
|
||||
this.moment = { steps: msg.steps, owner: msg.owner };
|
||||
@@ -569,7 +580,7 @@ class Net {
|
||||
let talk = 0;
|
||||
if (!msg.replayed) this.onFx?.(msg.events as GameEvent[]);
|
||||
for (const e of msg.events as GameEvent[]) {
|
||||
if (e.type === "tableTalk") talk++;
|
||||
if (e.type === "tableTalk") { talk++; this.lastTalkBy = e.player; }
|
||||
if (e.type === "gameStarted" && !msg.replayed) {
|
||||
this.openingRolls = { rolls: e.dieRolls, first: e.firstPlayer, players: e.players };
|
||||
}
|
||||
@@ -627,6 +638,7 @@ class Net {
|
||||
break;
|
||||
case "chat": {
|
||||
if (msg.player === this.you && this.chatPending === msg.text) this.chatPending = null;
|
||||
this.lastTalkBy = msg.player;
|
||||
this.log = [...this.log, { text: `\u{1F4AC} ${msg.player}: ${msg.text}`, turn: null, notable: false, actor: msg.player }];
|
||||
this.chatCount += 1;
|
||||
if (this.roomId) this.markChatSeen();
|
||||
@@ -866,6 +878,8 @@ class Net {
|
||||
* line, and wiping it would erase the only notice of why. */
|
||||
leaveLocal(): void {
|
||||
localStorage.removeItem(SEAT_KEY);
|
||||
this.missedSteps = null;
|
||||
this.catchUp = null;
|
||||
this.pending = null;
|
||||
this.unsent = null;
|
||||
this.unconfirmed = null;
|
||||
@@ -904,18 +918,27 @@ class Net {
|
||||
this.send({ type: "catchUp", sinceSeq: 0, full: true });
|
||||
}
|
||||
|
||||
/** Ask for the reel of everything since we last watched. */
|
||||
requestCatchUp(): void {
|
||||
/** Ask for the steps since we last watched: for the summary slip, or for the reel. */
|
||||
requestCatchUp(summaryOnly = false): void {
|
||||
if (!this.roomId) return;
|
||||
this.catchUpWanted = summaryOnly ? "summary" : "reel";
|
||||
this.send({ type: "catchUp", sinceSeq: this.seen[this.roomId] ?? 0 });
|
||||
}
|
||||
|
||||
/** Open the reel of the missed steps, at the step a fact pointed to. */
|
||||
watchCatchUp(startAt = 0): void {
|
||||
this.catchUpStart = Math.max(0, startAt);
|
||||
if (this.missedSteps) this.catchUp = this.missedSteps;
|
||||
else this.requestCatchUp(false);
|
||||
}
|
||||
|
||||
/** All caught up: remember it and clear the banner. */
|
||||
markSeen(): void {
|
||||
if (!this.roomId) return;
|
||||
this.seen[this.roomId] = this.currentSeq;
|
||||
localStorage.setItem(SEEN_KEY, JSON.stringify(this.seen));
|
||||
this.missedMoves = 0;
|
||||
this.missedSteps = null;
|
||||
}
|
||||
|
||||
closeCatchUp(): void {
|
||||
|
||||
Reference in New Issue
Block a user