ugly-game 0.7.2 → 0.7.4

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.
@@ -1,6 +1,6 @@
1
1
  import type { DeterministicSim } from '../moat';
2
2
  import type { ControlView } from './controlBlock';
3
- import type { CommandQueue } from './commandQueue';
3
+ import type { CommandQueue, Stamped } from './commandQueue';
4
4
  export interface SimClock {
5
5
  now(): number;
6
6
  }
@@ -21,10 +21,11 @@ export interface CoopStepOpts<S, I> {
21
21
  tickOf: (s: S) => number;
22
22
  maxCatchUpFrames?: number;
23
23
  onLate?: (count: number) => void;
24
- /** Called once per tick, with the commands due THIS tick, immediately before `sim.tick`. The seam a game uses
25
- * to drive per-tick co-op diagnostics (a command journal + a checkpoint hash trail) without the primitive
26
- * needing to know their shape. `state` is passed so a checkpoint can hash it (at the top of the tick). */
27
- beforeTick?: (due: I[], state: S) => void;
24
+ /** Called once per tick, with the STAMPED commands due THIS tick (carrying `at`/`seq`/`by`), immediately before
25
+ * `sim.tick`. The seam a game drives per-tick co-op diagnostics through — a command journal (whose digest must
26
+ * fold in `at`/`seq`, so a relay reorder reads as a relay bug, not a sim bug) + a checkpoint hash trail. `state`
27
+ * is passed so a checkpoint can hash it at the top of the tick. */
28
+ beforeTick?: (due: Stamped<I>[], state: S) => void;
28
29
  }
29
30
  export interface SoloStepOpts<S, I> {
30
31
  sim: SimHostSim<S, I>;
@@ -39,7 +40,7 @@ export interface SoloStepOpts<S, I> {
39
40
  fps?: number;
40
41
  maxCatchUpFrames?: number;
41
42
  onLate?: (count: number) => void;
42
- beforeTick?: (due: I[], state: S) => void;
43
+ beforeTick?: (due: Stamped<I>[], state: S) => void;
43
44
  }
44
45
  /** Single-player stepping: derive a target tick from elapsed wall time (a free-run 60-UPS accumulator, clamped by
45
46
  * maxCatchUpFrames), publish it to `ctl.targetTick`, then reuse `coopStepBatch` — so solo and co-op run the identical
@@ -45,12 +45,12 @@ export function coopStepBatch(opts) {
45
45
  if (late && opts.onLate)
46
46
  opts.onLate(late);
47
47
  }
48
- const inputs = due.map((d) => d.c);
49
- // Per-tick diagnostics seam: the game records the applied stream + a checkpoint hash of the top-of-tick state,
50
- // BEFORE the tick mutates it. Fires every tick (solo + co-op) so a journal can't silently skip history.
48
+ // Per-tick diagnostics seam: the game records the applied (stamped) stream + a checkpoint hash of the
49
+ // top-of-tick state, BEFORE the tick mutates it. Fires every tick (solo + co-op) so a journal can't silently
50
+ // skip history. The sim itself only needs the bare commands.
51
51
  if (opts.beforeTick)
52
- opts.beforeTick(inputs, opts.state);
53
- opts.sim.tick(opts.state, inputs);
52
+ opts.beforeTick(due, opts.state);
53
+ opts.sim.tick(opts.state, due.map((d) => d.c));
54
54
  stepped++;
55
55
  };
56
56
  // STEP_BUDGET burst first (forced, deterministic — independent of the target).
@@ -1,6 +1,7 @@
1
1
  import type { Layout } from './layout';
2
2
  import { type ControlView } from './controlBlock';
3
3
  import { type SimHostSim } from './simCore';
4
+ import { type Stamped } from './commandQueue';
4
5
  /** A reply a worker posts back to the main thread. Must be structured-cloneable. Echo the originating request's
5
6
  * `rid` so the main thread's `host.request()` Promise resolves on it; any other fields are the game's payload. */
6
7
  export interface SimWorkerReply {
@@ -17,7 +18,8 @@ export interface SimWorkerConfig<S, I> {
17
18
  layout: Layout<S> | ((init: Record<string, unknown>) => Layout<S>);
18
19
  tickOf: (s: S) => number;
19
20
  mirror?: (s: S, ctl: ControlView<string>) => void;
20
- beforeTick?: (due: I[], state: S) => void;
21
+ beforeTick?: (due: Stamped<I>[], state: S) => void;
21
22
  onMessage?: (m: Record<string, unknown>, ctx: SimWorkerContext<S>) => void;
23
+ onReinit?: (ctx: SimWorkerContext<S>) => void;
22
24
  }
23
25
  export declare function runSimWorker<S, I>(cfg: SimWorkerConfig<S, I>): void;
@@ -3,6 +3,9 @@ import { soloStepBatch, coopStepBatch, } from './simCore';
3
3
  import { CommandQueue } from './commandQueue';
4
4
  export function runSimWorker(cfg) {
5
5
  const ctx = globalThis;
6
+ const post = (msg) => {
7
+ ctx.postMessage(msg);
8
+ };
6
9
  const queue = new CommandQueue();
7
10
  let state = null;
8
11
  let ctl = null;
@@ -98,6 +101,8 @@ export function runSimWorker(cfg) {
98
101
  queue.clear();
99
102
  lastRef.t = clock.now();
100
103
  acc = 0;
104
+ if (ctl)
105
+ cfg.onReinit?.({ state, ctl, post });
101
106
  }
102
107
  else if (m.t === 'cmd') {
103
108
  queue.push(m.s);
@@ -108,13 +113,7 @@ export function runSimWorker(cfg) {
108
113
  }
109
114
  else if (state && ctl) {
110
115
  // A message this runner doesn't own — hand it to the game (diagnostics requests, etc.).
111
- cfg.onMessage?.(m, {
112
- state,
113
- ctl,
114
- post: (msg) => {
115
- ctx.postMessage(msg);
116
- },
117
- });
116
+ cfg.onMessage?.(m, { state, ctl, post });
118
117
  }
119
118
  };
120
119
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ugly-game",
3
- "version": "0.7.2",
3
+ "version": "0.7.4",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {