Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/CONTRACTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,8 @@ Bot replies and status (room table `bot_status`, one row):
failed, stays due and is retried every 5 s in reveal order until it is 2 minutes past `revealAt`, then
skipped; a bot command also posts any due result before its own reply. A line Twitch accepted and then
dropped (AutoMod, chat settings) counts as done. Alarm errors are logged and never stop the next alarm.
The alarm's tick saves every expiry it applies (rematch locks included), so nothing past due stays stored and
re-arms the alarm for the current moment.
An unrated duel's line ends with its "Just for fun" text (Game rules). The pair's last ranked duel this stream ends
"Last ranked duel between them this stream."
StreamElements channels get no result line; their `!elo` and `!ranks` show it after `revealAt`.
Expand Down
2 changes: 2 additions & 0 deletions server/game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,9 @@ function expireState(state: GameState, now: number): boolean {
}
}

const locks = state.rematchLocks.length;
state.rematchLocks = state.rematchLocks.filter((lock) => lock.until > now);
if (state.rematchLocks.length !== locks) changed = true; // unsaved, a stale lock re-arms the alarm for now in a loop
const inDuel = new Set(openDuels(state).flatMap((duel) => [duel.a, duel.b]));
const before = state.players.length;
state.players = state.players.filter(
Expand Down
12 changes: 12 additions & 0 deletions tests/channel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,18 @@ test("a subscription stuck awaiting verification is re-checked within minutes an
assert.equal((await r.call("/chat")).body.connected, true);
});

test("an expired rematch lock is saved away, so the alarm doesn't re-arm for now", async () => {
const r = room();
await r.save("u1", "alice");
const s = r.readState("nesszerra");
s.rematchLocks = [{ pair: "u1:u2", until: Date.now() - 1 }];
r.writeState(s);
await r.alarm();
assert.deepEqual(r.readState("nesszerra").rematchLocks, [], "the expired lock is gone from storage");
const next = r.ctx.storage.alarm;
assert.ok(next === null || next > Date.now() + 1000, "no alarm due again right away");
});

test("versioned config editor: history, optimistic version check, rollback", async () => {
const r = room();
const a = await r.call("/admin", {
Expand Down
Loading