A refused seat is logged, and the wallet drops a seat only on the second refusal in a row
A lost seat mid-game is the costliest quiet failure this table has: one refusal around a restart cost Kestrel PRPM's seat with nothing in the journal to say why. 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
f738957100
commit
1ebd6dbe47
@@ -717,7 +717,12 @@ wss.on("connection", (socket, req) => {
|
||||
const room = getRoom(roomId);
|
||||
if (!room) return send(socket, { type: "error", message: "no such room" });
|
||||
const result = joinRoom(room, name, typeof msg.token === "string" ? msg.token : null);
|
||||
if ("error" in result) return send(socket, { type: "error", message: result.error });
|
||||
if ("error" in result) {
|
||||
// A refused seat is worth a line: a lost seat mid-game is the
|
||||
// costliest quiet failure this table has.
|
||||
console.warn(`join refused: room ${room.id} name ${JSON.stringify(name)} — ${result.error}`);
|
||||
return send(socket, { type: "error", message: result.error });
|
||||
}
|
||||
leaveGallery(session);
|
||||
session.playerId = name;
|
||||
session.roomId = room.id;
|
||||
@@ -1065,6 +1070,7 @@ wss.on("connection", (socket, req) => {
|
||||
const result = peekSummary(roomId, name, typeof seat.token === "string" ? seat.token : null);
|
||||
if (result === null) continue;
|
||||
if (result === "badToken") {
|
||||
console.warn(`seat voided: room ${roomId} name ${JSON.stringify(name)} — the token did not match`);
|
||||
voided.push(`${roomId}:${name}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -380,6 +380,8 @@ class Net {
|
||||
feedbackReports = $state<FeedbackReportView[]>([]);
|
||||
/** Every seat this browser holds, across rooms. */
|
||||
seats = $state<Seat[]>(loadSeats());
|
||||
/** Seats the last games answer refused: dropped only if refused again. */
|
||||
private voidedOnce = new Set<string>();
|
||||
/** Lobby ledger: one summary per live seat. */
|
||||
stats = $state<Record<string, number | string | null> | null>(null);
|
||||
chatSeen = $state<Record<string, number>>(loadChatSeen());
|
||||
@@ -673,8 +675,13 @@ class Net {
|
||||
// stays — a restarting server, a stale restore, or the wrong
|
||||
// backend all answer with ignorance, and ignorance is not
|
||||
// deletion. The wallet is capped by age instead of by trust.
|
||||
// And a seat is dropped only on the SECOND refusal in a row: one
|
||||
// refusal around a restart or a stale answer must not cost a seat.
|
||||
const voided = new Set((msg.voided as string[] | undefined) ?? []);
|
||||
let kept = this.seats.filter((s) => !voided.has(`${s.roomId}:${s.name}`));
|
||||
const doubted = new Set<string>();
|
||||
for (const key of voided) { if (this.voidedOnce.has(key)) doubted.add(key); }
|
||||
this.voidedOnce = voided;
|
||||
let kept = this.seats.filter((s) => !doubted.has(`${s.roomId}:${s.name}`));
|
||||
if (kept.length > 50) kept = kept.slice(kept.length - 50);
|
||||
if (kept.length !== this.seats.length) {
|
||||
this.seats = kept;
|
||||
|
||||
Reference in New Issue
Block a user