@cyberart-io/engine 0.0.7 → 0.0.8

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.
@@ -0,0 +1,107 @@
1
+ # Frame performance benchmarking
2
+
3
+ Cart-agnostic tools for measuring update vs render cost. Use these for hash / trait investigations instead of one-off vitest probes.
4
+
5
+ Production kaleidoscope / Art Blocks playback **leave profiling unset**. When `onFrameTiming` is unset, the draw loop only does a single null check — no `performance.now()` calls.
6
+
7
+ Back to the [package README](../README.md). Related: [headless harness](headless-harness.md), [deterministic mode](deterministic-mode.md).
8
+
9
+ ## Two surfaces
10
+
11
+ | Surface | Import | When to use |
12
+ |---|---|---|
13
+ | Headless bench | `@cyberart-io/engine/headless` → `benchmarkCartFrames` | CI / agent hash A/B without mounting a live runtime |
14
+ | Live hook | `@cyberart-io/engine` → `runtime.onFrameTiming` | Overlay / collect samples on the real `rAF` or `step` path |
15
+
16
+ ## Headless: `benchmarkCartFrames`
17
+
18
+ Runs a cart's `update` / `render` loop with a virtual frame clock. Does not start audio or construct `AnimationManager`.
19
+
20
+ ```ts
21
+ import {
22
+ benchmarkCartFrames,
23
+ formatFrameBenchmarkResult,
24
+ } from '@cyberart-io/engine/headless';
25
+
26
+ const result = benchmarkCartFrames(artProject, hash, rawParams, {
27
+ width: 1920,
28
+ height: 1080,
29
+ frames: 30,
30
+ warmupFrames: 5,
31
+ prepareState: (state) => {
32
+ // e.g. skip Tone init in jsdom
33
+ (state as { audioContextStarted?: boolean }).audioContextStarted = true;
34
+ },
35
+ });
36
+
37
+ console.table(formatFrameBenchmarkResult(result));
38
+ ```
39
+
40
+ ### Result fields
41
+
42
+ | Field | Meaning |
43
+ |---|---|
44
+ | `updateAvgMs` / `renderAvgMs` | Mean phase time over measured frames |
45
+ | `updateMaxMs` / `renderMaxMs` | Worst frame in the sample (catches sim hitches) |
46
+ | `totalAvgMs` / `estFps` | Combined average and `1000 / totalAvgMs` |
47
+ | `updatePct` / `renderPct` | Share of measured time |
48
+
49
+ `formatFrameBenchmarkResult` rounds for stable console / snapshot logs.
50
+
51
+ ### Options
52
+
53
+ - `frames` (default 30), `warmupFrames` (default 5), `frameStepMs` (default `1000/60`)
54
+ - `width` / `height` (default 1920×1080)
55
+ - `tokenId` for `Random`
56
+ - `prepareState(state, featureState)` after `getDefaultState`
57
+ - `drawingContext` stub (default no-op `putImageData`)
58
+
59
+ One-command reproduce in this repo:
60
+
61
+ ```bash
62
+ pnpm exec vitest run packages/engine/src/canvas/frameBenchmark.spec.ts
63
+ ```
64
+
65
+ ## Live: `onFrameTiming`
66
+
67
+ Optional per-frame sample on the production draw / `step` path.
68
+
69
+ ```ts
70
+ import { createRuntime, type FrameTimingSample } from '@cyberart-io/engine';
71
+
72
+ const samples: FrameTimingSample[] = [];
73
+
74
+ const runtime = createRuntime({
75
+ container,
76
+ // optional at construction:
77
+ onFrameTiming: (sample) => samples.push(sample),
78
+ });
79
+
80
+ // or assign later / clear to restore zero overhead:
81
+ runtime.onFrameTiming = (sample) => {
82
+ console.log(sample.frame, sample.updateMs, sample.renderMs, sample.drawMs);
83
+ };
84
+ runtime.onFrameTiming = undefined;
85
+ ```
86
+
87
+ ### Sample shape
88
+
89
+ ```ts
90
+ type FrameTimingSample = {
91
+ frame: number;
92
+ updateMs: number;
93
+ renderMs: number;
94
+ drawMs: number;
95
+ totalMs: number;
96
+ };
97
+ ```
98
+
99
+ ### Cost when off
100
+
101
+ `createRuntime` assigns the handler **directly** to `AnimationManager`. When the host clears `runtime.onFrameTiming`, the manager property is `undefined` again. `processFrame` only calls `performance.now()` inside `if (timingHook)` branches.
102
+
103
+ Do **not** wrap with an always-on forwarder (`(s) => host?.(s)`): that would keep the hook truthy and force timing every frame.
104
+
105
+ ## Cart-specific helpers
106
+
107
+ This package stays cart-agnostic. Repos that need trait decode beside the bench (e.g. kautomata `hashPerfProbe`) should wrap `benchmarkCartFrames` in the cart tree — not in the engine.
@@ -13,10 +13,14 @@ Back to the [package README](../README.md).
13
13
  | Surface | Import |
14
14
  |---|---|
15
15
  | Carts, Player, kaleidoscope, `/art` | `@cyberart-io/engine` (`createRuntime`, events, contracts, assets, presentation adapter, presentation cue, capability manifest, geometry, runtime group) |
16
- | Vitest / jsdom / CI capture | `@cyberart-io/engine/headless` (`createHeadlessHarness`, `createHeadlessMultiCartHarness`, `installHeadlessCanvas`, `captureFrame`) |
16
+ | Vitest / jsdom / CI capture | `@cyberart-io/engine/headless` (`createHeadlessHarness`, `createHeadlessMultiCartHarness`, `installHeadlessCanvas`, `captureFrame`, `benchmarkCartFrames`) |
17
17
 
18
18
  The main export does not re-export the harness. A browser bundle that only imports `@cyberart-io/engine` must not warn about `node:fs/promises`.
19
19
 
20
+ ## Frame performance
21
+
22
+ For update/render timing without mounting a live runtime, use `benchmarkCartFrames` from this entry. For live `rAF` / `step` samples, use `runtime.onFrameTiming` from `@cyberart-io/engine` (unset = no `performance.now` in the draw loop). Full API: [frame benchmarking](frame-benchmark.md).
23
+
20
24
  ## One-command reproduce (this repo)
21
25
 
22
26
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyberart-io/engine",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "CyberArt host engine: mount carts, events, capability manifests, geometry, runtime groups, and a Node/jsdom headless entry.",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",