@chaos-overlords/kernel 0.9.0 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/domain/entities.d.ts +7 -2
  2. package/dist/domain/entities.d.ts.map +1 -1
  3. package/dist/domain/entities.js +6 -0
  4. package/dist/domain/entities.js.map +1 -1
  5. package/dist/logic/canonical-json.js +33 -5
  6. package/dist/logic/canonical-json.js.map +1 -1
  7. package/dist/logic/turn-logic.d.ts +9 -1
  8. package/dist/logic/turn-logic.d.ts.map +1 -1
  9. package/dist/logic/turn-logic.js +14 -1
  10. package/dist/logic/turn-logic.js.map +1 -1
  11. package/dist/ports/runtime.d.ts +2 -0
  12. package/dist/ports/runtime.d.ts.map +1 -1
  13. package/dist/ports/storage.d.ts +25 -8
  14. package/dist/ports/storage.d.ts.map +1 -1
  15. package/dist/services/LobbyService.d.ts +18 -0
  16. package/dist/services/LobbyService.d.ts.map +1 -1
  17. package/dist/services/LobbyService.js +71 -23
  18. package/dist/services/LobbyService.js.map +1 -1
  19. package/dist/services/RateLimiter.d.ts +35 -5
  20. package/dist/services/RateLimiter.d.ts.map +1 -1
  21. package/dist/services/RateLimiter.js +75 -20
  22. package/dist/services/RateLimiter.js.map +1 -1
  23. package/dist/services/RetentionService.d.ts +50 -10
  24. package/dist/services/RetentionService.d.ts.map +1 -1
  25. package/dist/services/RetentionService.js +36 -11
  26. package/dist/services/RetentionService.js.map +1 -1
  27. package/dist/services/TurnService.d.ts +5 -0
  28. package/dist/services/TurnService.d.ts.map +1 -1
  29. package/dist/services/TurnService.js +43 -11
  30. package/dist/services/TurnService.js.map +1 -1
  31. package/dist/testing/InMemoryStorage.d.ts.map +1 -1
  32. package/dist/testing/InMemoryStorage.js +25 -1
  33. package/dist/testing/InMemoryStorage.js.map +1 -1
  34. package/package.json +2 -2
@@ -23,8 +23,11 @@ export interface Match {
23
23
  /** Seats taken in the lobby; capacity is enforced on this counter atomically. */
24
24
  seatCount: number;
25
25
  /**
26
- * Monotonic count of seats ever claimed. It never decreases, so the value handed to each player
27
- * as `joinOrder` is a stable total order even after someone leaves and a newcomer takes the seat.
26
+ * The next position in the match's join sequence: advanced by every lobby seat claimed and every
27
+ * late-join position taken, including claims whose join then failed, so it is an upper bound on
28
+ * the players ever seated rather than a count of them. It never decreases, so the value handed to
29
+ * each player as `joinOrder` is a stable total order even after someone leaves and a newcomer
30
+ * takes the seat; gaps in that order carry no meaning.
28
31
  */
29
32
  joinCounter: number;
30
33
  createdAt: Date;
@@ -156,6 +159,8 @@ export interface TakeoverVote {
156
159
  export type PersistedEvent = MatchEvent;
157
160
  export declare const ACTIVE_PLAYER: PlayerStatus;
158
161
  export declare function activePlayers(players: readonly Player[]): Player[];
162
+ /** A human seat nobody is playing: the ones an absence vote can hand to the computer. */
163
+ export declare const ABSENT_HUMAN_STATUSES: ReadonlyArray<PlayerStatus>;
159
164
  /** A seat a human still holds: present, or absent and waited for while a takeover is voted on. */
160
165
  export declare function isHumanParticipant(player: Pick<Player, 'status'>): boolean;
161
166
  /** Human-controlled seats, including an absent player the lobby elected to keep waiting for. */
@@ -1 +1 @@
1
- {"version":3,"file":"entities.d.ts","sourceRoot":"","sources":["../../src/domain/entities.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"entities.d.ts","sourceRoot":"","sources":["../../src/domain/entities.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,12 @@ export const ACTIVE_PLAYER = 'active';
2
2
  export function activePlayers(players) {
3
3
  return players.filter((player) => player.status === ACTIVE_PLAYER);
4
4
  }
5
+ /** A human seat nobody is playing: the ones an absence vote can hand to the computer. */
6
+ export const ABSENT_HUMAN_STATUSES = [
7
+ 'takeoverPending',
8
+ 'left',
9
+ 'kicked',
10
+ ];
5
11
  /** A seat a human still holds: present, or absent and waited for while a takeover is voted on. */
6
12
  export function isHumanParticipant(player) {
7
13
  return player.status === 'active' || player.status === 'takeoverPending';
@@ -1 +1 @@
1
- {"version":3,"file":"entities.js","sourceRoot":"","sources":["../../src/domain/entities.ts"],"names":[],"mappings":"AAsLA,MAAM,CAAC,MAAM,aAAa,GAAiB,QAAQ,CAAA;AAEnD,MAAM,UAAU,aAAa,CAAC,OAA0B;IACtD,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,KAAK,aAAa,CAAC,CAAA;AACpE,CAAC;AAED,kGAAkG;AAClG,MAAM,UAAU,kBAAkB,CAAC,MAA8B;IAC/D,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,KAAK,iBAAiB,CAAA;AAC1E,CAAC;AAED,gGAAgG;AAChG,MAAM,UAAU,iBAAiB,CAAC,OAA0B;IAC1D,OAAO,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAA;AAC3C,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,YAAY,CAAC,KAA4B;IACvD,OAAO,KAAK,CAAC,MAAM,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,KAAK,UAAU,CAAA;AAClE,CAAC"}
1
+ {"version":3,"file":"entities.js","sourceRoot":"","sources":["../../src/domain/entities.ts"],"names":[],"mappings":"AAyLA,MAAM,CAAC,MAAM,aAAa,GAAiB,QAAQ,CAAA;AAEnD,MAAM,UAAU,aAAa,CAAC,OAA0B;IACtD,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,KAAK,aAAa,CAAC,CAAA;AACpE,CAAC;AAED,yFAAyF;AACzF,MAAM,CAAC,MAAM,qBAAqB,GAAgC;IAChE,iBAAiB;IACjB,MAAM;IACN,QAAQ;CACT,CAAA;AAED,kGAAkG;AAClG,MAAM,UAAU,kBAAkB,CAAC,MAA8B;IAC/D,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,KAAK,iBAAiB,CAAA;AAC1E,CAAC;AAED,gGAAgG;AAChG,MAAM,UAAU,iBAAiB,CAAC,OAA0B;IAC1D,OAAO,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAA;AAC3C,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,YAAY,CAAC,KAA4B;IACvD,OAAO,KAAK,CAAC,MAAM,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,KAAK,UAAU,CAAA;AAClE,CAAC"}
@@ -20,25 +20,53 @@ export class NonCanonicalValueError extends Error {
20
20
  this.name = 'NonCanonicalValueError';
21
21
  }
22
22
  }
23
- function canonicalize(value, path = '') {
23
+ function canonicalize(value, path = []) {
24
24
  if (value === null || typeof value === 'boolean' || typeof value === 'string')
25
25
  return value;
26
26
  if (typeof value === 'number') {
27
27
  if (!Number.isSafeInteger(value) || Object.is(value, -0)) {
28
- throw new NonCanonicalValueError(path, `${value} is not a safe non-negative-zero integer`);
28
+ throw new NonCanonicalValueError(describe(path), `${value} is not a safe non-negative-zero integer`);
29
29
  }
30
30
  return value;
31
31
  }
32
32
  if (Array.isArray(value))
33
- return value.map((item, index) => canonicalize(item, `${path}[${index}]`));
33
+ return value.map((item, index) => descend(path, index, item));
34
34
  if (typeof value === 'object') {
35
35
  const source = value;
36
36
  const sorted = {};
37
37
  for (const key of Object.keys(source).sort()) {
38
- sorted[key] = canonicalize(source[key], path ? `${path}.${key}` : key);
38
+ sorted[key] = descend(path, key, source[key]);
39
39
  }
40
40
  return sorted;
41
41
  }
42
- throw new NonCanonicalValueError(path, `unsupported type ${typeof value}`);
42
+ throw new NonCanonicalValueError(describe(path), `unsupported type ${typeof value}`);
43
+ }
44
+ function descend(path, segment, value) {
45
+ path.push(segment);
46
+ try {
47
+ return canonicalize(value, path);
48
+ }
49
+ finally {
50
+ path.pop();
51
+ }
52
+ }
53
+ /**
54
+ * Renders the accumulated segments the way the C# mirror's `path` parameter spells them
55
+ * (`CanonicalJson.WriteObject`/`WriteArray`): an index is always `[n]`, and a key is dotted unless
56
+ * nothing has been rendered yet. The emptiness of what came before decides the dot, not the
57
+ * segment's position -- an empty-string key renders as nothing, so `{ '': { y: 1.5 } }` reports
58
+ * `y` rather than `.y`, matching both the mirror and the string-building version this replaced.
59
+ *
60
+ * Only a throw reaches here, so the allocation is off the hot path.
61
+ */
62
+ function describe(path) {
63
+ let rendered = '';
64
+ for (const segment of path) {
65
+ if (typeof segment === 'number')
66
+ rendered += `[${segment}]`;
67
+ else
68
+ rendered += rendered.length === 0 ? segment : `.${segment}`;
69
+ }
70
+ return rendered;
43
71
  }
44
72
  //# sourceMappingURL=canonical-json.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"canonical-json.js","sourceRoot":"","sources":["../../src/logic/canonical-json.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,OAAO,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAA;AAC5C,CAAC;AAED,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IAEpC,IAAI;IADf,YACW,IAAY,EACrB,MAAc;QAEd,KAAK,CAAC,uBAAuB,IAAI,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC,CAAA;oBAHjD,IAAI;QAIb,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAA;IACtC,CAAC;CACF;AAED,SAAS,YAAY,CAAC,KAAc,EAAE,IAAI,GAAG,EAAE;IAC7C,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,SAAS,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IAC3F,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACzD,MAAM,IAAI,sBAAsB,CAAC,IAAI,EAAE,GAAG,KAAK,0CAA0C,CAAC,CAAA;QAC5F,CAAC;QACD,OAAO,KAAK,CAAA;IACd,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QACtB,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,GAAG,IAAI,IAAI,KAAK,GAAG,CAAC,CAAC,CAAA;IAC5E,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,MAAM,GAAG,KAAgC,CAAA;QAC/C,MAAM,MAAM,GAA4B,EAAE,CAAA;QAC1C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;YAC7C,MAAM,CAAC,GAAG,CAAC,GAAG,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;QACxE,CAAC;QACD,OAAO,MAAM,CAAA;IACf,CAAC;IACD,MAAM,IAAI,sBAAsB,CAAC,IAAI,EAAE,oBAAoB,OAAO,KAAK,EAAE,CAAC,CAAA;AAC5E,CAAC"}
1
+ {"version":3,"file":"canonical-json.js","sourceRoot":"","sources":["../../src/logic/canonical-json.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,OAAO,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAA;AAC5C,CAAC;AAED,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IAEpC,IAAI;IADf,YACW,IAAY,EACrB,MAAc;QAEd,KAAK,CAAC,uBAAuB,IAAI,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC,CAAA;oBAHjD,IAAI;QAIb,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAA;IACtC,CAAC;CACF;AAID,SAAS,YAAY,CAAC,KAAc,EAAE,IAAI,GAAkB,EAAE;IAC5D,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,SAAS,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IAC3F,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACzD,MAAM,IAAI,sBAAsB,CAC9B,QAAQ,CAAC,IAAI,CAAC,EACd,GAAG,KAAK,0CAA0C,CACnD,CAAA;QACH,CAAC;QACD,OAAO,KAAK,CAAA;IACd,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,CAAA;IACvF,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,MAAM,GAAG,KAAgC,CAAA;QAC/C,MAAM,MAAM,GAA4B,EAAE,CAAA;QAC1C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;YAC7C,MAAM,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;QAC/C,CAAC;QACD,OAAO,MAAM,CAAA;IACf,CAAC;IACD,MAAM,IAAI,sBAAsB,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,oBAAoB,OAAO,KAAK,EAAE,CAAC,CAAA;AACtF,CAAC;AAED,SAAS,OAAO,CAAC,IAAmB,EAAE,OAAoB,EAAE,KAAc;IACxE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IAClB,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;IAClC,CAAC;YAAS,CAAC;QACT,IAAI,CAAC,GAAG,EAAE,CAAA;IACZ,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,QAAQ,CAAC,IAA4B;IAC5C,IAAI,QAAQ,GAAG,EAAE,CAAA;IACjB,KAAK,MAAM,OAAO,IAAI,IAAI,EAAE,CAAC;QAC3B,IAAI,OAAO,OAAO,KAAK,QAAQ;YAAE,QAAQ,IAAI,IAAI,OAAO,GAAG,CAAA;;YACtD,QAAQ,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,OAAO,EAAE,CAAA;IAClE,CAAC;IACD,OAAO,QAAQ,CAAA;AACjB,CAAC"}
@@ -1,6 +1,14 @@
1
- import { type Player, type TurnOrders, type TurnReport } from '../domain/entities';
1
+ import { type Player, type Turn, type TurnOrders, type TurnReport } from '../domain/entities';
2
2
  /** Every human seat still being waited on has marked ready. An empty roster is never "ready". */
3
3
  export declare function allActiveReady(players: readonly Player[], orders: ReadonlyArray<Pick<TurnOrders, 'playerId' | 'ready'>>): boolean;
4
+ /**
5
+ * Whether the turn was sealed by its clock running out rather than by everyone being ready. Read
6
+ * from the stored row alone, so a sweep finishing an interrupted seal reaches the same answer as the
7
+ * call that sealed it: a deadline seal is refused until the deadline has passed, so its `sealedAt`
8
+ * is never earlier. A turn without a deadline (timer off, or paused by an absence vote) can only
9
+ * seal on readiness.
10
+ */
11
+ export declare function sealedByDeadline(turn: Pick<Turn, 'deadlineAt' | 'sealedAt'>): boolean;
4
12
  export type Consensus = {
5
13
  kind: 'pending';
6
14
  } | {
@@ -1 +1 @@
1
- {"version":3,"file":"turn-logic.d.ts","sourceRoot":"","sources":["../../src/logic/turn-logic.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,MAAM,EACX,KAAK,UAAU,EACf,KAAK,UAAU,EAChB,MAAM,oBAAoB,CAAA;AAE3B,iGAAiG;AACjG,wBAAgB,cAAc,CAC5B,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,MAAM,EAAE,aAAa,CAAC,IAAI,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,CAAC,GAC5D,OAAO,CAKT;AAED,MAAM,MAAM,SAAS,GACjB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,GAC3D;IACE,IAAI,EAAE,UAAU,CAAA;IAChB,OAAO,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACvD,gFAAgF;IAChF,oBAAoB,EAAE,MAAM,EAAE,CAAA;CAC/B,CAAA;AAEL;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,OAAO,EAAE,SAAS,UAAU,EAAE,GAC7B,MAAM,EAAE,CAaV;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,OAAO,EAAE,SAAS,UAAU,EAAE,EAC9B,iBAAiB,EAAE,MAAM,GAAG,IAAI,GAC/B,SAAS,CAgCX;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,YAAY,EAAE,MAAM,GACnB,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAQ3C;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,IAAI,EAAE,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAElF"}
1
+ {"version":3,"file":"turn-logic.d.ts","sourceRoot":"","sources":["../../src/logic/turn-logic.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,MAAM,EACX,KAAK,IAAI,EACT,KAAK,UAAU,EACf,KAAK,UAAU,EAChB,MAAM,oBAAoB,CAAA;AAE3B,iGAAiG;AACjG,wBAAgB,cAAc,CAC5B,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,MAAM,EAAE,aAAa,CAAC,IAAI,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,CAAC,GAC5D,OAAO,CAMT;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,YAAY,GAAG,UAAU,CAAC,GAAG,OAAO,CAGrF;AAED,MAAM,MAAM,SAAS,GACjB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,GAC3D;IACE,IAAI,EAAE,UAAU,CAAA;IAChB,OAAO,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACvD,gFAAgF;IAChF,oBAAoB,EAAE,MAAM,EAAE,CAAA;CAC/B,CAAA;AAEL;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,OAAO,EAAE,SAAS,UAAU,EAAE,GAC7B,MAAM,EAAE,CAaV;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,OAAO,EAAE,SAAS,UAAU,EAAE,EAC9B,iBAAiB,EAAE,MAAM,GAAG,IAAI,GAC/B,SAAS,CAgCX;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,YAAY,EAAE,MAAM,GACnB,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAQ3C;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,IAAI,EAAE,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAElF"}
@@ -1,12 +1,25 @@
1
1
  import { activePlayers, humanParticipants, } from '../domain/entities';
2
2
  /** Every human seat still being waited on has marked ready. An empty roster is never "ready". */
3
3
  export function allActiveReady(players, orders) {
4
- const active = humanParticipants(players);
4
+ const asked = new Set(orders.map((row) => row.playerId));
5
+ const active = humanParticipants(players).filter((player) => asked.has(player.id));
5
6
  if (active.length === 0)
6
7
  return false;
7
8
  const readyIds = new Set(orders.filter((row) => row.ready).map((row) => row.playerId));
8
9
  return active.every((player) => readyIds.has(player.id));
9
10
  }
11
+ /**
12
+ * Whether the turn was sealed by its clock running out rather than by everyone being ready. Read
13
+ * from the stored row alone, so a sweep finishing an interrupted seal reaches the same answer as the
14
+ * call that sealed it: a deadline seal is refused until the deadline has passed, so its `sealedAt`
15
+ * is never earlier. A turn without a deadline (timer off, or paused by an absence vote) can only
16
+ * seal on readiness.
17
+ */
18
+ export function sealedByDeadline(turn) {
19
+ if (turn.deadlineAt === null || turn.sealedAt === null)
20
+ return false;
21
+ return turn.sealedAt.getTime() >= turn.deadlineAt.getTime();
22
+ }
10
23
  /**
11
24
  * The state hashes that the most active players reported, tied if more than one.
12
25
  *
@@ -1 +1 @@
1
- {"version":3,"file":"turn-logic.js","sourceRoot":"","sources":["../../src/logic/turn-logic.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"turn-logic.js","sourceRoot":"","sources":["../../src/logic/turn-logic.ts"],"names":[],"mappings":"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"}
@@ -37,6 +37,8 @@ export interface EventStreamOpener {
37
37
  playerId: string;
38
38
  afterSeq: number;
39
39
  signal: AbortSignal;
40
+ /** Lobby streams use a smaller share of a Node process's total stream capacity. */
41
+ lobby?: boolean;
40
42
  }): Promise<Response>;
41
43
  }
42
44
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/ports/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAExD,MAAM,WAAW,KAAK;IACpB,GAAG,IAAI,IAAI,CAAA;CACZ;AAED,MAAM,WAAW,MAAM;IACrB,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IAC1D,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IACzD,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IACzD,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;CAC3D;AAED,eAAO,MAAM,UAAU,EAAE,MAKxB,CAAA;AAED,+EAA+E;AAC/E,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7C;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC/E;AAED;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,KAAK,EAAE;QACV,OAAO,EAAE,MAAM,CAAA;QACf,QAAQ,EAAE,MAAM,CAAA;QAChB,QAAQ,EAAE,MAAM,CAAA;QAChB,MAAM,EAAE,WAAW,CAAA;KACpB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;CACtB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACnE"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/ports/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAExD,MAAM,WAAW,KAAK;IACpB,GAAG,IAAI,IAAI,CAAA;CACZ;AAED,MAAM,WAAW,MAAM;IACrB,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IAC1D,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IACzD,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IACzD,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;CAC3D;AAED,eAAO,MAAM,UAAU,EAAE,MAKxB,CAAA;AAED,+EAA+E;AAC/E,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7C;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC/E;AAED;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,KAAK,EAAE;QACV,OAAO,EAAE,MAAM,CAAA;QACf,QAAQ,EAAE,MAAM,CAAA;QAChB,QAAQ,EAAE,MAAM,CAAA;QAChB,MAAM,EAAE,WAAW,CAAA;QACnB,mFAAmF;QACnF,KAAK,CAAC,EAAE,OAAO,CAAA;KAChB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;CACtB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACnE"}
@@ -3,7 +3,8 @@ import type { Match, MatchSeat, OrderSummary, PersistedEvent, Player, PublicLobb
3
3
  /**
4
4
  * Persistence ports. There are NO transactions: D1 has none, so every invariant that two writers
5
5
  * could race on is a single conditional statement whose row count says who won. Each `transition`
6
- * is such a compare-and-swap; `claimSeat`/`releaseSeat` are atomic counters; `submitOrders` is
6
+ * is such a compare-and-swap; `claimSeat`/`releaseSeat` and `claimLateJoinOrder` are atomic
7
+ * counters; `submitOrders` is
7
8
  * conditional on the turn still being open; `append` allocates its own sequence number.
8
9
  *
9
10
  * Writes that a unique constraint can refuse answer `false` instead of throwing, so the services
@@ -27,6 +28,15 @@ export interface MatchRepository {
27
28
  * position in the match's monotonic join sequence, or null when no seat was available.
28
29
  */
29
30
  claimSeat(matchId: string): Promise<number | null>;
31
+ /**
32
+ * Atomically take the next position in the match's join sequence for a late joiner, while the
33
+ * match is running; null when it is not running or no longer exists. It advances the same
34
+ * `joinCounter` `claimSeat` does, in one conditional UPDATE — a separate sequence, or a read
35
+ * followed by a write, would hand two joiners the same `joinOrder` — and it never returns a
36
+ * position a stored player of the match already holds. It does not test capacity: `createLate`
37
+ * does, and a position taken for a join that `createLate` then refuses stays unused.
38
+ */
39
+ claimLateJoinOrder(matchId: string): Promise<number | null>;
30
40
  releaseSeat(matchId: string): Promise<void>;
31
41
  /** Host-only lobby configuration; false after the match starts or below the occupied seat count. */
32
42
  updateSettings(matchId: string, settings: MatchSettings, updatedAt: Date): Promise<boolean>;
@@ -52,8 +62,8 @@ export interface MatchRepository {
52
62
  * being `active` only through `leave`, `kick` or a missed deadline, and the game's default is an
53
63
  * untimed match, so two friends whose clients both died mid-match leave two `active` rows that
54
64
  * nothing ever clears: with the roster test alone their match is immortal. The longer window drops
55
- * the test, because `updated_at` is refreshed by every turn open and every status change and
56
- * months of silence on both is not something a live match does.
65
+ * the test, because `updated_at` is refreshed by every turn open, every status change and every
66
+ * join or rejoin, and months of silence on all of them is not something a live match does.
57
67
  */
58
68
  deleteAbandonedLive(before: Date, limit: number, requireEmptyRoster: boolean): Promise<number>;
59
69
  /**
@@ -66,7 +76,7 @@ export interface MatchRepository {
66
76
  * on every report and every snapshot upload, so a match that nothing touched since the previous
67
77
  * pass has no new evidence and its verdict cannot have changed; without the bound a public
68
78
  * server's parked desyncs — the documented way a match ends when a host never uploads — were
69
- * re-judged every fifteen seconds for the ninety days retention keeps them. It also ends the
79
+ * re-judged every fifteen seconds for the weeks retention keeps them. It also ends the
70
80
  * starvation of a page ordered by an `updatedAt` that never moves: pass null to sweep everything,
71
81
  * which is what a freshly started process does once to pick up whatever it missed.
72
82
  */
@@ -79,6 +89,8 @@ export interface MatchRepository {
79
89
  hostPlayerId?: string;
80
90
  updatedAt: Date;
81
91
  }): Promise<boolean>;
92
+ /** Advance the current turn in one conditional write; a late sweep may never move it backwards. */
93
+ advanceCurrentTurn(matchId: string, number: number, updatedAt: Date): Promise<boolean>;
82
94
  }
83
95
  export interface PlayerRepository {
84
96
  /**
@@ -132,8 +144,10 @@ export interface PlayerRepository {
132
144
  }
133
145
  export interface TurnRepository {
134
146
  /**
135
- * Insert the turn and one empty orders row per player. False when the turn already exists, which
136
- * is how the seal repair can re-run the open step without publishing a second `turn.opened`.
147
+ * Insert the turn and one empty orders row per player that has none yet. Rows are only added while
148
+ * the stored turn is still `open`: a top-up that races a seal must not land in the sealed turn.
149
+ * False when the turn already exists, which is how the seal repair can re-run the open step
150
+ * without publishing a second `turn.opened`.
137
151
  */
138
152
  open(turn: Turn, playerIds: readonly string[]): Promise<boolean>;
139
153
  get(matchId: string, number: number): Promise<Turn | null>;
@@ -227,7 +241,7 @@ export interface TurnRepository {
227
241
  * which is why a missing turn counts as stalled rather than being skipped.
228
242
  *
229
243
  * `touchedSince` bounds the join to matches something happened to recently. A seal in flight is
230
- * seconds old, but abandoned matches are deliberately kept `running` for ninety days, so the
244
+ * seconds old, but abandoned matches are deliberately kept `running` for weeks, so the
231
245
  * unbounded join walked thousands of rows every fifteen seconds to find nothing. The caller runs
232
246
  * the bounded scan every tick and the unbounded one (null) on a much longer period, which is what
233
247
  * still finds a seal interrupted while the process was down.
@@ -285,7 +299,10 @@ export interface CastVoteInput {
285
299
  * are one indexed read instead of a scan of every event the match ever logged.
286
300
  */
287
301
  export interface TakeoverRepository {
288
- /** Opens a prompt for the seat; false when one is already open, so a caller can announce only the first. */
302
+ /**
303
+ * Opens a prompt only while the seat is a human absence (`ABSENT_HUMAN_STATUSES`) of that match;
304
+ * false when one is already open or the seat is active, computer controlled or not in the match.
305
+ */
289
306
  openPrompt(matchId: string, playerId: string, turn: number, openedAt: Date): Promise<boolean>;
290
307
  /** Closes the prompt and discards its votes. A no-op when none is open. */
291
308
  closePrompt(matchId: string, playerId: string): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/ports/storage.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/ports/storage.ts"],"names":[],"mappings":"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"}
@@ -37,8 +37,12 @@ export declare class LobbyService {
37
37
  createMatch(request: CreateMatchRequest): Promise<MembershipView>;
38
38
  join(request: JoinMatchRequest, caller?: string): Promise<MembershipView>;
39
39
  joinRunning(request: JoinRunningMatchRequest, caller?: string): Promise<MembershipView>;
40
+ /** Why the late-join claim found no running match: it stopped running, or it is gone. */
41
+ private lateJoinClaimRefused;
40
42
  leave(principal: Principal): Promise<void>;
41
43
  rejoin(principal: Principal): Promise<void>;
44
+ /** A seat claim can cross a seal, so use the turn current after the seat was committed. */
45
+ private topUpCurrentTurn;
42
46
  updateSettings(principal: Principal, settings: MatchSettings): Promise<void>;
43
47
  kick(principal: Principal, targetPlayerId: string): Promise<void>;
44
48
  /** The player a host or a voter named, refused unless it is a member of the caller's match. */
@@ -61,12 +65,26 @@ export declare class LobbyService {
61
65
  private retallyOpenPrompts;
62
66
  start(principal: Principal): Promise<void>;
63
67
  private remove;
68
+ /**
69
+ * Restart a running or desynced match's retention age: somebody has just come (back) to it.
70
+ *
71
+ * The abandoned and silent windows both run from `updatedAt`, and neither a join nor a rejoin
72
+ * otherwise moves it — a player returning to an untimed match whose partner is still away opens
73
+ * no turn and changes no status — so a match somebody was demonstrably playing could be collected
74
+ * on the age it had before they came back. A no-op on any other status.
75
+ */
76
+ private refreshRetention;
64
77
  /**
65
78
  * Move the host role of a started match and announce it. `at` is the caller's to supply: `remove`
66
79
  * stamps the handoff with the time it read on entry, and the other callers read the clock as
67
80
  * they call.
68
81
  */
69
82
  private handHostTo;
83
+ /**
84
+ * Hand the host role of a started match to its first active player, if it has one. Answers
85
+ * whether anybody was there to take it.
86
+ */
87
+ private handHostToFirstActive;
70
88
  /**
71
89
  * Hang up a revoked membership's streams. Best effort by design: the token is already gone, so a
72
90
  * fan-out that cannot be reached costs one stale stream until it drops, never the revoke itself.
@@ -1 +1 @@
1
- {"version":3,"file":"LobbyService.d.ts","sourceRoot":"","sources":["../../src/services/LobbyService.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,kBAAkB,EAGvB,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,EAE5B,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACzB,MAAM,4BAA4B,CAAA;AAmBnC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AACxC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAItD,OAAO,EAAc,KAAK,WAAW,EAAE,MAAM,eAAe,CAAA;AA0C5D,MAAM,WAAW,mBAAmB;IAClC,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,MAAM,CAAA;CACrB;AAED,0EAA0E;AAC1E,qBAAa,YAAY;IAOrB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,KAAK;IARxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAmB;IACzC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAc;IACpC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAa;IAC9C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAa;IAE9C,YACmB,IAAI,EAAE,UAAU,EAChB,SAAS,EAAE,cAAc,EACzB,KAAK,EAAE,WAAW,EACnC,OAAO,GAAE,mBAAwB,EAYlC;IAED;;;;;;;;;;;;;;OAcG;YACW,mBAAmB;IAsBjC,OAAO,CAAC,uBAAuB;IAOzB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,cAAc,CAAC,CA4CtE;IAEK,IAAI,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAkD9E;IAEK,WAAW,CAAC,OAAO,EAAE,uBAAuB,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAuE5F;IAEK,KAAK,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/C;IAEK,MAAM,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CA+ChD;IAEK,cAAc,CAAC,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBjF;IAEK,IAAI,CAAC,SAAS,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAUtE;IAED,+FAA+F;YACjF,aAAa;IAQ3B;;;OAGG;IACG,cAAc,CAClB,SAAS,EAAE,SAAS,EACpB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAqCf;IAED;;;;;;;OAOG;YACW,iBAAiB;IAiC/B,8EAA8E;YAChE,kBAAkB;IAM1B,KAAK,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAoC/C;YAEa,MAAM;IAmHpB;;;;OAIG;YACW,UAAU;IAWxB;;;OAGG;YACW,MAAM;IAYpB;;;;;;;;;;;;OAYG;YACW,mBAAmB;IAIjC,OAAO,CAAC,gBAAgB;IASxB;;;;OAIG;YACW,YAAY;YAaZ,OAAO;IAcrB;;;;OAIG;YACW,uBAAuB;YAUvB,UAAU;CAQzB"}
1
+ {"version":3,"file":"LobbyService.d.ts","sourceRoot":"","sources":["../../src/services/LobbyService.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,kBAAkB,EAGvB,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,EAE5B,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACzB,MAAM,4BAA4B,CAAA;AAoBnC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AACxC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAItD,OAAO,EAAc,KAAK,WAAW,EAAE,MAAM,eAAe,CAAA;AAwC5D,MAAM,WAAW,mBAAmB;IAClC,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,MAAM,CAAA;CACrB;AAED,0EAA0E;AAC1E,qBAAa,YAAY;IAOrB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,KAAK;IARxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAmB;IACzC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAc;IACpC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAa;IAC9C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAa;IAE9C,YACmB,IAAI,EAAE,UAAU,EAChB,SAAS,EAAE,cAAc,EACzB,KAAK,EAAE,WAAW,EACnC,OAAO,GAAE,mBAAwB,EAYlC;IAED;;;;;;;;;;;;;;OAcG;YACW,mBAAmB;IAsBjC,OAAO,CAAC,uBAAuB;IAOzB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,cAAc,CAAC,CA4CtE;IAEK,IAAI,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAkD9E;IAEK,WAAW,CAAC,OAAO,EAAE,uBAAuB,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CA4E5F;IAED,yFAAyF;YAC3E,oBAAoB;IAM5B,KAAK,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/C;IAEK,MAAM,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAiDhD;IAED,2FAA2F;YAC7E,gBAAgB;IAKxB,cAAc,CAAC,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBjF;IAEK,IAAI,CAAC,SAAS,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAUtE;IAED,+FAA+F;YACjF,aAAa;IAQ3B;;;OAGG;IACG,cAAc,CAClB,SAAS,EAAE,SAAS,EACpB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAqCf;IAED;;;;;;;OAOG;YACW,iBAAiB;IAgC/B,8EAA8E;YAChE,kBAAkB;IAM1B,KAAK,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAoC/C;YAEa,MAAM;IA+HpB;;;;;;;OAOG;YACW,gBAAgB;IAM9B;;;;OAIG;YACW,UAAU;IAWxB;;;OAGG;YACW,qBAAqB;IAOnC;;;OAGG;YACW,MAAM;IAYpB;;;;;;;;;;;;OAYG;YACW,mBAAmB;IAIjC,OAAO,CAAC,gBAAgB;IASxB;;;;OAIG;YACW,YAAY;YAaZ,OAAO;IAcrB;;;;OAIG;YACW,uBAAuB;YAUvB,UAAU;CAQzB"}
@@ -1,5 +1,5 @@
1
1
  import { foldName, GAME_BOUNDS, LIMITS, } from '@chaos-overlords/contracts';
2
- import { activePlayers, humanParticipants, isInProgress, } from '../domain/entities';
2
+ import { ABSENT_HUMAN_STATUSES, activePlayers, humanParticipants, isInProgress, } from '../domain/entities';
3
3
  import { ConflictError, ForbiddenError, NotFoundError, RateLimitedError, UnauthorizedError, } from '../domain/errors';
4
4
  import { generateJoinCode, generateSeed, generateToken, hashToken } from '../logic/crypto';
5
5
  import { hashPassword, verifyPassword } from '../logic/password';
@@ -18,8 +18,6 @@ const HOST_JOIN_ORDER = 0;
18
18
  const DEFAULT_PORTRAIT_ID = 0;
19
19
  /** Host statuses that mean the seat is genuinely empty and the role may move. */
20
20
  const VACANT_HOST_STATUSES = ['left', 'kicked', 'computer'];
21
- /** A human seat nobody is playing: the ones an absence vote can hand to the computer. */
22
- const ABSENT_HUMAN_STATUSES = ['takeoverPending', 'left', 'kicked'];
23
21
  /**
24
22
  * Passwords one caller will have verified against one match in a window.
25
23
  *
@@ -254,11 +252,17 @@ export class LobbyService {
254
252
  this.assertNameIsFree(existing, request.displayName);
255
253
  const token = generateToken();
256
254
  const seatKey = (await hashToken(`${match.id}:${request.slot}`)).slice(0, 32);
255
+ // The position is taken before the insert because the port has no transactions to take both
256
+ // in one; a join `createLate` then refuses leaves a gap in the sequence, which only has to be
257
+ // unique and increasing. The checks above refuse the requests that are doomed from the start.
258
+ const joinOrder = await this.deps.storage.matches.claimLateJoinOrder(match.id);
259
+ if (joinOrder === null)
260
+ throw await this.lateJoinClaimRefused(match.id);
257
261
  const player = {
258
262
  id: `late-${seatKey}`,
259
263
  matchId: match.id,
260
264
  slot: request.slot,
261
- joinOrder: match.joinCounter,
265
+ joinOrder,
262
266
  displayName: request.displayName,
263
267
  portraitId: request.portraitId ?? DEFAULT_PORTRAIT_ID,
264
268
  tokenHash: await hashToken(token),
@@ -270,15 +274,20 @@ export class LobbyService {
270
274
  reason: 'seat_reserved',
271
275
  });
272
276
  }
273
- const openTurn = await this.deps.storage.turns.get(match.id, match.currentTurn);
274
- if (openTurn?.status === 'open')
275
- await this.deps.storage.turns.open(openTurn, [player.id]);
277
+ await this.refreshRetention(match.id);
278
+ await this.topUpCurrentTurn(match.id, player.id);
276
279
  await this.publisher.publish(match.id, {
277
280
  type: 'match.latePlayerJoined',
278
281
  payload: { playerId: player.id, slot: player.slot },
279
282
  });
280
283
  return this.membership(match, player, token);
281
284
  }
285
+ /** Why the late-join claim found no running match: it stopped running, or it is gone. */
286
+ async lateJoinClaimRefused(matchId) {
287
+ return (await this.deps.storage.matches.get(matchId))
288
+ ? new ConflictError('The match is not running', { reason: 'match_not_running' })
289
+ : new NotFoundError('No match with that id or join code', { reason: 'unknown_match' });
290
+ }
282
291
  async leave(principal) {
283
292
  await this.remove(principal.match, principal.player, 'left');
284
293
  }
@@ -288,17 +297,18 @@ export class LobbyService {
288
297
  if (player.status === 'kicked') {
289
298
  throw new ForbiddenError('A kicked player cannot rejoin', { reason: 'kicked' });
290
299
  }
291
- if (player.status === 'active')
300
+ await this.refreshRetention(match.id);
301
+ if (player.status === 'active') {
302
+ // Nothing to reclaim, but a seat whose row an interrupted turn open never wrote is repaired
303
+ // here too, so the returning client is waited for instead of silently sealed past.
304
+ await this.topUpCurrentTurn(match.id, player.id);
292
305
  return;
306
+ }
293
307
  const replacedComputer = player.status === 'computer';
294
308
  if (!(await this.deps.storage.players.transitionStatus(player.id, ['left', 'takeoverPending', 'computer'], 'active'))) {
295
309
  throw new ConflictError('The player seat could not be reclaimed', { reason: 'rejoin_race' });
296
310
  }
297
- const openTurn = await this.deps.storage.turns.get(match.id, match.currentTurn);
298
- if (openTurn?.status === 'open') {
299
- // `open` is idempotent and tops up missing participant rows even when the turn already exists.
300
- await this.deps.storage.turns.open(openTurn, [player.id]);
301
- }
311
+ await this.topUpCurrentTurn(match.id, player.id);
302
312
  await this.deps.storage.takeovers.closePrompt(match.id, player.id);
303
313
  await this.publisher.publish(match.id, {
304
314
  type: 'match.playerReturned',
@@ -318,14 +328,20 @@ export class LobbyService {
318
328
  // Only a host who is gone is replaced. `takeoverPending` is set on a player who is still
319
329
  // connected and merely missed one timed deadline, so treating it as absence would hand the role
320
330
  // to any former member who called `rejoin` at that moment — and the new host can kick the old
321
- // one, which revokes their token for good. A pending host is still present; the takeover vote is
322
- // the path that decides otherwise.
331
+ // one, which revokes their token for good. A pending host is still present; the takeover vote,
332
+ // or the pending host's own `leave`, is the path that decides otherwise.
323
333
  if (currentHost === undefined || VACANT_HOST_STATUSES.includes(currentHost.status)) {
324
334
  await this.handHostTo(match.id, player.id, this.deps.clock.now());
325
335
  }
326
336
  await this.turns.reevaluate(match.id);
327
337
  await this.turns.resumeAfterTakeoverVotes(match.id);
328
338
  }
339
+ /** A seat claim can cross a seal, so use the turn current after the seat was committed. */
340
+ async topUpCurrentTurn(matchId, playerId) {
341
+ const match = await this.deps.storage.matches.get(matchId);
342
+ if (match)
343
+ await this.turns.topUpSeat(matchId, match.currentTurn, playerId);
344
+ }
329
345
  async updateSettings(principal, settings) {
330
346
  requireHost(principal);
331
347
  if (principal.match.status !== 'lobby') {
@@ -427,9 +443,7 @@ export class LobbyService {
427
443
  payload: { playerId: targetPlayerId },
428
444
  });
429
445
  if (targetPlayerId === match.hostPlayerId) {
430
- const successor = activePlayers(await this.deps.storage.players.listByMatch(match.id))[0];
431
- if (successor)
432
- await this.handHostTo(match.id, successor.id, this.deps.clock.now());
446
+ await this.handHostToFirstActive(match.id, this.deps.clock.now());
433
447
  }
434
448
  await this.turns.reevaluate(match.id);
435
449
  await this.turns.resumeAfterTakeoverVotes(match.id);
@@ -521,9 +535,9 @@ export class LobbyService {
521
535
  if (!(await this.deps.storage.players.delete(target.id)))
522
536
  return;
523
537
  await this.deps.storage.matches.releaseSeat(match.id);
524
- // A kicked member's token is dead with the row, but a stream they already hold is never
525
- // re-authenticated: without this it goes on delivering `match.started` with the seed and
526
- // every seal, desync and roster change for the rest of the match.
538
+ // A kicked member's token is dead with the row, but a stream they already hold only re-checks
539
+ // its membership on the hub's catch-up heartbeat: without this it goes on delivering
540
+ // `match.started` with the seed and every seal, desync and roster change until then.
527
541
  if (reason === 'kicked')
528
542
  await this.hangUp(match.id, target.id);
529
543
  await this.publisher.publish(match.id, {
@@ -543,8 +557,8 @@ export class LobbyService {
543
557
  // controlled, but the person behind it must still lose the token and the streams either way.
544
558
  // Membership is the only thing the token ever proved, so it stops working here: a kicked player
545
559
  // keeps neither the event stream nor the sealed order sets of the turns that follow. The revoke
546
- // closes the next request and the hang-up closes the streams already open, which are never
547
- // re-authenticated and would otherwise outlive the membership for as long as the client liked.
560
+ // closes the next request and the hang-up closes the streams already open, which otherwise
561
+ // outlive the membership until the hub's periodic membership check catches up with them.
548
562
  if (reason === 'kicked') {
549
563
  await this.deps.storage.players.revokeToken(target.id);
550
564
  await this.hangUp(match.id, target.id);
@@ -566,7 +580,17 @@ export class LobbyService {
566
580
  // the turn waiting on a seat that could never answer, with the clock paused by its own prompt
567
581
  // and everyone present already ready. A `left` seat really is idle, and re-running the verdict
568
582
  // for it costs one query.
583
+ //
584
+ // A `takeoverPending` host who leaves is gone, not merely late, so the role moves on the way it
585
+ // does for an active host; otherwise it stayed with a departed seat until a takeover vote or a
586
+ // rejoin happened to move it. With nobody present to take it, the role stays put and the first
587
+ // former member to rejoin becomes host, as below.
569
588
  if (!claimed || !wasActive) {
589
+ if (claimed &&
590
+ target.id === match.hostPlayerId &&
591
+ !(await this.handHostToFirstActive(match.id, now))) {
592
+ await this.turns.pauseAbandonedMatch(match.id);
593
+ }
570
594
  await this.turns.reevaluate(match.id);
571
595
  await this.retallyOpenPrompts(match);
572
596
  return;
@@ -592,6 +616,19 @@ export class LobbyService {
592
616
  await this.turns.reevaluate(match.id);
593
617
  await this.retallyOpenPrompts(match);
594
618
  }
619
+ /**
620
+ * Restart a running or desynced match's retention age: somebody has just come (back) to it.
621
+ *
622
+ * The abandoned and silent windows both run from `updatedAt`, and neither a join nor a rejoin
623
+ * otherwise moves it — a player returning to an untimed match whose partner is still away opens
624
+ * no turn and changes no status — so a match somebody was demonstrably playing could be collected
625
+ * on the age it had before they came back. A no-op on any other status.
626
+ */
627
+ async refreshRetention(matchId) {
628
+ await this.deps.storage.matches.transition(matchId, ['running', 'desynced'], {
629
+ updatedAt: this.deps.clock.now(),
630
+ });
631
+ }
595
632
  /**
596
633
  * Move the host role of a started match and announce it. `at` is the caller's to supply: `remove`
597
634
  * stamps the handoff with the time it read on entry, and the other callers read the clock as
@@ -607,6 +644,17 @@ export class LobbyService {
607
644
  payload: { hostPlayerId: playerId },
608
645
  });
609
646
  }
647
+ /**
648
+ * Hand the host role of a started match to its first active player, if it has one. Answers
649
+ * whether anybody was there to take it.
650
+ */
651
+ async handHostToFirstActive(matchId, at) {
652
+ const successor = activePlayers(await this.deps.storage.players.listByMatch(matchId))[0];
653
+ if (!successor)
654
+ return false;
655
+ await this.handHostTo(matchId, successor.id, at);
656
+ return true;
657
+ }
610
658
  /**
611
659
  * Hang up a revoked membership's streams. Best effort by design: the token is already gone, so a
612
660
  * fan-out that cannot be reached costs one stale stream until it drops, never the revoke itself.