hayao 0.3.0 → 0.4.0

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.
package/dist/world.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Clock, type ClockConfig } from './core/clock';
2
2
  import { EventBus } from './core/events';
3
3
  import { Rng } from './core/rng';
4
- import { InputState } from './input/actions';
4
+ import { InputState, type AxisFrame } from './input/actions';
5
5
  import { Node, type WorldContext } from './scene/node';
6
6
  import type { Camera2D } from './scene/nodes';
7
7
  import { type Transform, type Vec2 } from './core/math';
@@ -14,12 +14,18 @@ export interface WorldConfig {
14
14
  height?: number;
15
15
  /** Resolved tuning values (see app/tuning.ts). Sim state: hashed + snapshotted. */
16
16
  tuning?: Record<string, number | string>;
17
+ /**
18
+ * Dev guard: during each step(), `Math.random`/`Date.now` are wrapped so a
19
+ * stray call inside the sim warns ONCE (with a stack hint) instead of
20
+ * silently breaking determinism. Never throws; safe to leave on in tests.
21
+ */
22
+ guardDeterminism?: boolean;
17
23
  }
18
24
  /** Default engine event map; games extend it with their own keys. */
19
25
  export interface CoreEvents {
20
26
  [key: string]: unknown;
21
27
  }
22
- export declare class World implements WorldContext {
28
+ export declare class World<TState extends Record<string, unknown> = Record<string, unknown>> implements WorldContext {
23
29
  readonly rng: Rng;
24
30
  readonly clock: Clock;
25
31
  readonly input: InputState;
@@ -31,15 +37,32 @@ export declare class World implements WorldContext {
31
37
  * `hash()` and `snapshot()`, so hidden state here cannot escape determinism
32
38
  * checks the way ad-hoc module variables can.
33
39
  */
34
- state: Record<string, unknown>;
40
+ state: TState;
35
41
  readonly width: number;
36
42
  readonly height: number;
43
+ /**
44
+ * Pause switch. While true, only `pauseMode: 'always'` subtrees update —
45
+ * rendering, input sampling, and the clock keep ticking, so a pause menu
46
+ * (an 'always' subtree) stays live. Sim state: hashed + snapshotted, but
47
+ * only when true, so pinned hashes of unpaused games are unchanged.
48
+ */
49
+ paused: boolean;
50
+ /**
51
+ * Sim-time multiplier: the tree receives `clock.dt * timeScale` (slow-mo /
52
+ * fast-forward), while `'always'` subtrees get the unscaled dt so UI keeps
53
+ * realtime feel. The clock itself ticks normally. Hashed/snapshotted only
54
+ * when ≠ 1.
55
+ */
56
+ timeScale: number;
37
57
  root: Node;
38
58
  activeCamera: Camera2D | null;
39
59
  private seed;
40
60
  private freeQueue;
41
61
  private started;
42
62
  private tuningValues;
63
+ private guardDeterminism;
64
+ private warnedClamp;
65
+ private warnedNondet;
43
66
  constructor(config?: WorldConfig);
44
67
  /**
45
68
  * Read a tuning value resolved at world creation (declared default unless
@@ -47,6 +70,12 @@ export declare class World implements WorldContext {
47
70
  * silently read `undefined` into the sim.
48
71
  */
49
72
  tune<T extends number | string = number>(key: string): T;
73
+ /**
74
+ * Read a shared resource by key. Throws on a missing key (listing what IS
75
+ * available) — a typo here would otherwise silently read `undefined` into
76
+ * the sim, exactly like an undeclared tuning knob.
77
+ */
78
+ resource<T>(key: string): T;
50
79
  get time(): number;
51
80
  get frame(): number;
52
81
  /** Replace the scene root, entering the tree. */
@@ -57,7 +86,15 @@ export declare class World implements WorldContext {
57
86
  * Advance exactly one fixed step with the given actions held down.
58
87
  * This is THE deterministic transition — call it from Node or the browser loop.
59
88
  */
60
- step(actionsDown?: Iterable<string>): void;
89
+ step(actionsDown?: Iterable<string>, axes?: AxisFrame): void;
90
+ /**
91
+ * Wrap `Math.random`/`Date.now` for the duration of a step so a stray call
92
+ * inside the sim warns ONCE per world (with a stack hint) — the values still
93
+ * flow through, so nothing breaks mid-frame. Returns the restore function;
94
+ * properly nested re-entry (a world stepped inside another's step) is safe
95
+ * because each install restores exactly what it replaced.
96
+ */
97
+ private installGuard;
61
98
  /**
62
99
  * Feed REAL elapsed ms; runs 0+ fixed steps. Returns steps run. This is the
63
100
  * realtime driver entry point — it CLAMPS realMs to the clock's maxFrameMs
@@ -65,14 +102,38 @@ export declare class World implements WorldContext {
65
102
  * not 72. For headless tests/harnesses that want to fast-forward an exact
66
103
  * number of steps, use `runSteps(n)` or `step()` — never `advance` with a big ms.
67
104
  */
68
- advance(realMs: number, actionsDown?: Iterable<string>): number;
105
+ advance(realMs: number, actionsDown?: Iterable<string>, axes?: AxisFrame): number;
69
106
  /**
70
107
  * Run exactly `n` fixed steps — the deterministic fast-forward for tests and
71
108
  * headless drivers (unlike `advance`, no realtime clamp). Pass `actionsFor(i)`
72
109
  * to script the input per step; omit it to hold nothing down.
73
110
  */
74
- runSteps(n: number, actionsFor?: (i: number) => Iterable<string>): void;
75
- private flushFree;
111
+ runSteps(n: number, actionsFor?: (i: number) => Iterable<string>, axesFor?: (i: number) => AxisFrame | undefined): void;
112
+ /**
113
+ * Run pending frees NOW instead of at the end of the step. `free()` is
114
+ * deferred (safe during iteration), which means freed nodes survive until
115
+ * the step ends — during a scene swap that lets old nodes contaminate the
116
+ * first frame of the new scene. Pattern: `oldRoot.free(); world.flushFree();
117
+ * buildNewScene()`. Called automatically at the end of every step.
118
+ */
119
+ flushFree(): void;
120
+ /** Visit every live node depth-first (root first, child-index order). */
121
+ walk(fn: (node: Node) => void): void;
122
+ /** Total live nodes in the tree (root included). */
123
+ get nodeCount(): number;
124
+ /**
125
+ * One indented line per node — `name (type) [flags] @x,y` — for quick tree
126
+ * audits in a REPL or test failure message.
127
+ */
128
+ debugTree(): string;
129
+ /** The active camera's world position + zoom, or null (see WorldContext.camera). */
130
+ camera(): {
131
+ pos: {
132
+ x: number;
133
+ y: number;
134
+ };
135
+ zoom: number;
136
+ } | null;
76
137
  /** The view transform (inverse of the active camera), mapping world → screen. */
77
138
  viewTransform(): Transform;
78
139
  /**
@@ -108,4 +169,8 @@ export interface WorldSnapshot {
108
169
  tree: ReturnType<Node['serialize']>;
109
170
  /** Resolved tuning values (absent in pre-tuning saves). */
110
171
  tuning?: Record<string, number | string>;
172
+ /** Pause flag (absent when false — keeps old snapshots byte-identical). */
173
+ paused?: boolean;
174
+ /** Sim-time multiplier (absent when 1). */
175
+ timeScale?: number;
111
176
  }
package/docs/API.md CHANGED
@@ -3,9 +3,9 @@
3
3
  > Auto-generated by `npm run api`. Everything importable from `@hayao`.
4
4
  > Grep here for the real surface — never guess an API from memory.
5
5
 
6
- **664 exports.**
6
+ **717 exports.**
7
7
 
8
- ## classs (52)
8
+ ## classs (59)
9
9
 
10
10
  - `AmbientField`
11
11
  - `AnimationPlayer`
@@ -17,13 +17,17 @@
17
17
  - `Canvas2DRenderer`
18
18
  - `CinematicPlayer`
19
19
  - `Clock`
20
+ - `Coroutines`
21
+ - `DepthSort`
20
22
  - `EventBus`
21
23
  - `FloatingText`
22
24
  - `Fsm`
25
+ - `GamepadSource`
23
26
  - `HeadlessRenderer`
24
27
  - `InputBuffer`
25
28
  - `InputRecorder`
26
29
  - `InputState`
30
+ - `IsoPrism`
27
31
  - `KeyboardSource`
28
32
  - `LocalStorageAdapter`
29
33
  - `LockstepSession`
@@ -57,10 +61,13 @@
57
61
  - `Text`
58
62
  - `TextureSprite`
59
63
  - `Timer`
64
+ - `TouchControls`
60
65
  - `UndoStack`
66
+ - `VerletChain`
67
+ - `WebGL2Renderer`
61
68
  - `World`
62
69
 
63
- ## functions (291)
70
+ ## functions (310)
64
71
 
65
72
  - `abilitiesOf`: (world: WorldGraphDef, taken: readonly string[]) => Set<string>
66
73
  - `addBody`: (rw: RigidWorld, def: RigidBodyDef) => number
@@ -68,7 +75,8 @@
68
75
  - `addRevoluteJoint`: (rw: RigidWorld, def: RevoluteJointDef) => number
69
76
  - `addTransition`: (parent: Node, config?: TransitionConfig) => ScreenTransition
70
77
  - `albumTrack`: (id: string) => AlbumTrack | undefined
71
- - `analyzePlaytest`: (def: GameDefinition, session: PlaytestSession, opts?: AnalyzeOptions) => PlaytestReport
78
+ - `all`: (...waits: Wait[]) => Wait
79
+ - `analyzePlaytest`: (def: GameDefinition<Record<string, unknown>>, session: PlaytestSession, opts?: AnalyzeOptions) => PlaytestReport
72
80
  - `applyImpulse`: (rw: RigidWorld, id: number, ix: number, iy: number, px?: number | undefined, py?: number | undefined) => void
73
81
  - `applyReverb`: (buf: StereoBuffer, opts?: ReverbOptions) => void
74
82
  - `applyTransform`: (m: Transform, p: Vec2) => Vec2
@@ -88,6 +96,7 @@
88
96
  - `blobPath`: (rng: Rng, radius: number, wobble?: number, lobes?: number) => string
89
97
  - `bodyAABB`: (b: RigidBody) => Rect
90
98
  - `bodyContains`: (b: RigidBody, x: number, y: number) => boolean
99
+ - `bootDom`: (def: GameDefinition<Record<string, unknown>>, mount?: HTMLElement | undefined) => DomHarness
91
100
  - `cadenceResolves`: (romans: string[]) => boolean
92
101
  - `cameraIssues`: (samples: readonly Vec2[], opts?: CameraOptions) => string[]
93
102
  - `canvasGradient`: (ctx: { createLinearGradient(x0: number, y0: number, x1: number, y1: number): CanvasGradient; createRadialGradient(x0: number, y0: number, r0: number, x1: nu…
@@ -111,7 +120,7 @@
111
120
  - `createPlatformerState`: (x: number, y: number) => PlatformerState
112
121
  - `createRigidWorld`: (opts?: RigidWorldOpts) => RigidWorld
113
122
  - `createStereo`: (durationSec: number, sampleRate?: number) => StereoBuffer
114
- - `createWorld`: (def: GameDefinition, opts?: number | CreateWorldOptions | undefined) => World
123
+ - `createWorld`: <TState extends Record<string, unknown> = Record<string, unknown>>(def: GameDefinition<TState>, opts?: number | CreateWorldOptions | undefined) => World<...>
115
124
  - `crestFactorDb`: (sig: Samples) => number
116
125
  - `dashJumpDistance`: (cfg?: PlatformerConfig) => number
117
126
  - `datan`: (x: number) => number
@@ -123,18 +132,19 @@
123
132
  - `decodeMessage`: (data: string) => NetMessage | null
124
133
  - `decodeRLE`: (runs: readonly number[], width: number, height: number) => PixelBuffer
125
134
  - `defaultStorage`: (prefix?: string | undefined) => StorageAdapter
126
- - `defineGame`: (def: GameDefinition) => Required<Pick<GameDefinition, "seed" | "width" | "height" | "inputMap" | "background">> & GameDefinition
135
+ - `defineGame`: <TState extends Record<string, unknown> = Record<string, unknown>>(def: GameDefinition<TState>) => Required<Pick<GameDefinition<TState>, "seed" | "width" | "
127
136
  - `defineLevel`: (spec: LevelSpec) => LevelData
128
137
  - `deserializeNode`: (data: SerializedNode) => Node
129
138
  - `dexp`: (x: number) => number
130
139
  - `dexp2`: (x: number) => number
131
140
  - `dhypot`: (x: number, y: number) => number
141
+ - `diamondPoints`: (w: number, h: number) => number[]
132
142
  - `diatonicChord`: (tonic: number, mode: "major" | "minor" | "harmonicMinor" | "melodicMinor" | "dorian" | "phrygian" | "lydian" | "mixolydian" | "locrian" | "majorPentatonic" …
133
143
  - `diffLevels`: (a: LevelData, b: LevelData) => LevelChange[]
134
144
  - `distanceGain`: (model: DistanceModel, distance: number, refDistance?: number, maxDistance?: number, rolloff?: number) => number
135
145
  - `dlog`: (x: number) => number
136
146
  - `dpow`: (base: number, exp: number) => number
137
- - `drive`: (world: World, script: InputScript, until?: ((probe: Record<string, unknown>) => boolean) | undefined) => DriveResult
147
+ - `drive`: (world: World<Record<string, unknown>>, script: InputScript, until?: ((probe: Record<string, unknown>) => boolean) | undefined) => DriveResult
138
148
  - `dropShadow`: (color: string, blur: number, dx?: number, dy?: number) => Shadow
139
149
  - `dsin`: (x: number) => number
140
150
  - `duckGain`: (active: boolean, amountDb?: number) => number
@@ -149,12 +159,14 @@
149
159
  - `fft`: (re: Float32Array<ArrayBufferLike>, im: Float32Array<ArrayBufferLike>) => void
150
160
  - `findSoftlocks`: (world: WorldGraphDef, mode?: CompletionMode) => SoftlockReport
151
161
  - `firstFrame`: (timeline: ProbeFrame[], pred: (p: ProbeFrame) => boolean) => number
162
+ - `fitViewport`: (rect: { width: number; height: number; }, width: number, height: number) => Viewport
152
163
  - `floodFill`: (start: Cell, passable: Passable, opts: { cols: number; rows: number; diagonal?: boolean | undefined; }) => Cell[]
153
164
  - `fluxEnvelope`: (sig: Samples, frameSize?: number, hop?: number) => Float32Array<ArrayBufferLike>
154
165
  - `forgivenessIssues`: (spec: ForgivenessSpec, opts?: ForgivenessOptions) => string[]
155
166
  - `fractalNoise`: (x: number, y: number, seed?: number, opts?: FractalOptions) => number
156
167
  - `frameActions`: (log: InputLog, i: number) => string[]
157
- - `gameInputMap`: (def: GameDefinition) => InputMap
168
+ - `frameAxes`: (log: InputLog, i: number) => AxisFrame | undefined
169
+ - `gameInputMap`: (def: GameDefinition<Record<string, unknown>>) => Readonly<Record<string, readonly string[]>>
158
170
  - `generateCave`: (rng: Rng, opts: CaveOptions) => Grid
159
171
  - `generateDungeon`: (rng: Rng, opts: DungeonOptions) => Dungeon
160
172
  - `generateLevels`: <State, Move>(factory: (rng: Rng) => Puzzle<State, Move>, opts: GenerateOptions<State, Move>) => GeneratedLevel<State, Move>[]
@@ -171,6 +183,7 @@
171
183
  - `gridSet`: (g: Grid, x: number, y: number, v: number) => void
172
184
  - `gridToTilemap`: (g: Grid, tileSize?: number) => TilemapData
173
185
  - `groundAt`: (map: TilemapData, x: number, y: number, w: number, h: number, solids?: SolidRect[], dropThrough?: boolean) => { grounded: boolean; solid: number; }
186
+ - `hashNoise`: (...values: number[]) => number
174
187
  - `hashString`: (str: string) => number
175
188
  - `hashValue`: (value: unknown) => string
176
189
  - `hexToHsl`: (hex: string) => HSL
@@ -182,17 +195,19 @@
182
195
  - `initDirector`: (waves: readonly WaveDef[]) => DirectorState
183
196
  - `inputDensity`: (frames: string[][]) => number
184
197
  - `installCapture`: (target: CaptureTarget) => HayaoCapture
198
+ - `invalidCommandReason`: (c: DrawCommand) => string | null
185
199
  - `invertTransform`: (m: Transform) => Transform
186
200
  - `inVisionCone`: (map: TilemapData, ex: number, ey: number, faceX: number, faceY: number, fov: number, range: number, tx: number, ty: number) => boolean
187
201
  - `isCaptureMode`: () => boolean
188
202
  - `isGround`: (col: number, row: number, opts: TerrainOptions) => boolean
189
203
  - `isMonotonic`: (values: number[], dir: "up" | "down", slack?: number) => boolean
204
+ - `iso`: (cfg: IsoConfig) => IsoProjection
190
205
  - `joinRoom`: (opts: RoomClientOptions) => Promise<NetGame>
191
206
  - `jumpAirtime`: (cfg?: PlatformerConfig) => number
192
207
  - `jumpDistance`: (cfg?: PlatformerConfig) => number
193
208
  - `jumpHeight`: (cfg?: PlatformerConfig) => number
194
209
  - `keyMentions`: (code: string) => string[]
195
- - `keysToActions`: (map: InputMap, keysDown: Set<string>) => string[]
210
+ - `keysToActions`: (map: Readonly<Record<string, readonly string[]>>, keysDown: Set<string>) => string[]
196
211
  - `layerGains`: (layers: MusicLayer[], intensity: number) => Record<string, number>
197
212
  - `layoutIssues`: (commands: DrawCommand[], opts?: LayoutOptions) => string[]
198
213
  - `layoutText`: (font: BitmapFont, input: string | readonly RichChar[], options?: TextLayoutOptions) => TextLayout
@@ -219,13 +234,14 @@
219
234
  - `mergePlayerFrames`: (players: readonly string[], inputs: ReadonlyMap<string, readonly string[]>) => string[]
220
235
  - `midiToFreq`: (midi: number, a4?: number) => number
221
236
  - `midiToName`: (midi: number) => string
222
- - `missingControlHints`: (world: World, inputMap: InputMap) => string[]
237
+ - `missingControlHints`: (world: World<Record<string, unknown>>, inputMap: Readonly<Record<string, readonly string[]>>) => string[]
223
238
  - `mix`: (a: string, b: string, t: number) => string
224
239
  - `mixLinear`: (a: string, b: string, t: number) => string
225
240
  - `mixMono`: (bus: StereoBuffer, sig: Samples, startSample: number, gain?: number, pan?: number) => void
226
241
  - `moveRect`: (map: TilemapData, rect: Rect, dx: number, dy: number, opts?: MoveOpts) => MoveResult
227
242
  - `mutateColor`: (rng: Rng, hex: string, amounts?: DriftAmounts) => string
228
243
  - `netMessage`: <T extends Omit<NetMessage, "v">>(msg: T) => T & { v: number; }
244
+ - `nextStep`: () => Wait
229
245
  - `nineSlice`: (rect: Rect, style: NineSliceStyle, z?: number, transform?: Transform) => DrawCommand[]
230
246
  - `normalize`: (buf: StereoBuffer, target?: number) => void
231
247
  - `noteToMidi`: (name: string) => number
@@ -251,7 +267,9 @@
251
267
  - `progressionPuzzle`: (world: WorldGraphDef, mode?: CompletionMode) => Puzzle<ProgressState, ProgressMove>
252
268
  - `proveCompletable`: (world: WorldGraphDef) => SolveResult<ProgressMove>
253
269
  - `proveFullCompletion`: (world: WorldGraphDef) => SolveResult<ProgressMove>
254
- - `pump`: (world: World, frames: number, actions?: string[]) => void
270
+ - `pump`: (world: World<Record<string, unknown>>, frames: number, actions?: string[]) => void
271
+ - `quantizeAngle`: (radians: number, buckets: number) => number
272
+ - `race`: (...waits: Wait[]) => Wait
255
273
  - `radialGradient`: (stops: readonly StopInput[], opts?: { cx?: number | undefined; cy?: number | undefined; r?: number | undefined; }) => RadialGradient
256
274
  - `rampIssues`: (difficulty: readonly number[], opts?: RampOptions) => string[]
257
275
  - `rampStats`: (difficulty: readonly number[]) => RampStats
@@ -259,23 +277,25 @@
259
277
  - `raycastTiles`: (map: TilemapData, x0: number, y0: number, x1: number, y1: number) => RayHit
260
278
  - `reachableRegions`: (world: WorldGraphDef, abilities: Iterable<string>) => string[]
261
279
  - `reconstructPath`: <N>(cameFrom: Map<string, N>, key: NodeKey<N>, start: N, goal: N) => N[]
262
- - `recordTimeline`: (world: World, frames: string[][]) => ProbeFrame[]
280
+ - `recordTimeline`: (world: World<Record<string, unknown>>, frames: string[][]) => ProbeFrame[]
263
281
  - `rectBlocked`: (map: TilemapData, x: number, y: number, w: number, h: number, solids?: SolidRect[]) => boolean
264
282
  - `rectContains`: (r: Rect, p: Vec2) => boolean
265
283
  - `rectsOverlap`: (a: Rect, b: Rect) => boolean
266
284
  - `registerNode`: (type: string, factory: NodeFactory) => void
267
285
  - `regularPolygon`: (sides: number, radius: number, rotation?: number) => number[]
286
+ - `regularPolyPoints`: (sides: number, r: number, rotation?: number) => number[]
268
287
  - `relLuminance`: (hex: string) => number
269
288
  - `removeBody`: (rw: RigidWorld, id: number) => void
270
289
  - `removeJoint`: (rw: RigidWorld, id: number) => void
271
290
  - `renderAudioFilmstrip`: (buf: StereoBuffer, opts?: AudioFilmstripOptions) => string
272
- - `renderFilmstrip`: (world: World, frames: string[][], opts: FilmstripOptions) => string
291
+ - `renderFilmstrip`: (world: World<Record<string, unknown>>, frames: string[][], opts: FilmstripOptions) => string
273
292
  - `renderSong`: (song: Song, opts?: { sampleRate?: number | undefined; normalizePeak?: number | undefined; }) => StereoBuffer
274
293
  - `renderSound`: (spec: SoundSpec, opts?: { rng?: Rng | undefined; sampleRate?: number | undefined; }) => Samples
275
294
  - `renderToSVGString`: (commands: DrawCommand[], width: number, height: number, background?: string) => string
276
295
  - `replay`: (makeWorld: WorldFactory, log: InputLog) => { finalHash: string; hashes: string[]; }
277
- - `replaySession`: (def: GameDefinition, session: PlaytestSession, opts?: number | ReplayOptions | undefined) => World
296
+ - `replaySession`: (def: GameDefinition<Record<string, unknown>>, session: PlaytestSession, opts?: number | ReplayOptions | undefined) => World<...>
278
297
  - `resetNodeIds`: (start?: number) => void
298
+ - `resetZzfxWarnings`: () => void
279
299
  - `resolveTuning`: (spec: TuningSpec | undefined, overrides?: TuningValues | undefined) => TuningValues
280
300
  - `rigidStep`: (rw: RigidWorld, dt: number) => ContactEvent[]
281
301
  - `rleDecode`: (input: string) => string
@@ -283,11 +303,11 @@
283
303
  - `rms`: (sig: Samples) => number
284
304
  - `roomCenter`: (r: Room) => { x: number; y: number; }
285
305
  - `rootless`: (chord: number[], targetMidi?: number | undefined) => number[]
286
- - `runBrowser`: (def: GameDefinition, mount: HTMLElement, opts?: RunOptions) => GameHandle
287
- - `runBrowserNet`: (def: GameDefinition, mount: HTMLElement, opts: NetRunOptions) => NetGameHandle
306
+ - `runBrowser`: (def: GameDefinition<Record<string, unknown>>, mount: HTMLElement, opts?: RunOptions) => GameHandle
307
+ - `runBrowserNet`: (def: GameDefinition<Record<string, unknown>>, mount: HTMLElement, opts: NetRunOptions) => NetGameHandle
288
308
  - `runFeelGates`: (spec: FeelSpec, ctx?: FeelContext) => FeelReport
289
- - `runHeadless`: (def: GameDefinition, inputLog?: InputLog | undefined) => HeadlessResult
290
- - `runStudio`: (baseDef: GameDefinition, mount: HTMLElement, opts?: StudioOptions) => StudioHandle
309
+ - `runHeadless`: <TState extends Record<string, unknown> = Record<string, unknown>>(def: GameDefinition<TState>, inputLog?: InputLog | undefined) => HeadlessResult<TState>
310
+ - `runStudio`: (baseDef: GameDefinition<Record<string, unknown>>, mount: HTMLElement, opts?: StudioOptions) => StudioHandle
291
311
  - `salienceIssues`: (commands: DrawCommand[], avatarFill: string, background: string, opts?: SalienceOptions) => string[]
292
312
  - `sampleGradient`: (stops: readonly string[], t: number) => string
293
313
  - `scaleMidis`: (tonic: number, mode?: "major" | "minor" | "harmonicMinor" | "melodicMinor" | "dorian" | "phrygian" | "lydian" | "mixolydian" | "locrian" | "majorPentatonic"…
@@ -295,20 +315,23 @@
295
315
  - `scatter`: (x: number, y: number, probability: number, seed?: number) => boolean
296
316
  - `scatterCells`: (x0: number, y0: number, cols: number, rows: number, probability: number, seed?: number) => { x: number; y: number; }[]
297
317
  - `scoreTrack`: (buf: StereoBuffer, profile: GenreProfile) => QualityScore
298
- - `scriptedPlaythrough`: (world: World, script: Segment[]) => PlaythroughResult
318
+ - `scriptedPlaythrough`: (world: World<Record<string, unknown>>, script: Segment[]) => PlaythroughResult
299
319
  - `scriptToFrames`: (script: InputScript) => string[][]
300
- - `scrubTo`: (world: World, def: GameDefinition, ring: SnapshotRing, inputFrames: readonly string[][], axesLog: readonly (readonly [number, string, number])[], knobEvents
320
+ - `scrubTo`: (world: World<Record<string, unknown>>, def: GameDefinition<Record<string, unknown>>, ring: SnapshotRing, inputFrames: readonly string[][], axesLog: readonly…
301
321
  - `serializeSnapshot`: (snap: WorldSnapshot) => string
302
322
  - `series`: (timeline: ProbeFrame[], key: string) => unknown[]
303
323
  - `setOverlayHost`: (el: HTMLElement) => void
304
324
  - `setScreenObserver`: (cb: ((kind: "show" | "hide", title?: string | undefined) => void) | null) => void
325
+ - `shadeHex`: (hex: string, f: number) => string
305
326
  - `shadowDef`: (s: Shadow, id: string) => string
306
327
  - `shapeBBox`: (c: DrawCommand) => BBox | null
307
328
  - `shapeBox`: (cmd: DrawCommand) => { x: number; y: number; w: number; h: number; } | null
308
329
  - `showScreen`: (spec: ScreenSpec) => ScreenHandle
309
330
  - `signalHash`: (sig: Samples) => number
331
+ - `sleep`: (seconds: number) => Wait
310
332
  - `smoothClosedPath`: (points: Vec2[], tension?: number) => string
311
333
  - `smoothOpenPath`: (points: Vec2[], tension?: number) => string
334
+ - `snapAxis`: (value: number, buckets: number, min?: number, max?: number) => number
312
335
  - `softClip`: (x: number) => number
313
336
  - `softClipInPlace`: (buf: StereoBuffer) => void
314
337
  - `solidNeighbours`: (g: Grid, x: number, y: number) => number
@@ -318,6 +341,7 @@
318
341
  - `songDuration`: (song: Song) => number
319
342
  - `sortCommands`: (cmds: DrawCommand[]) => DrawCommand[]
320
343
  - `spatialMix`: (dx: number, dy: number, hearing?: number, rolloff?: number) => SpatialMix
344
+ - `specFromZzfx`: (p: readonly (number | undefined)[]) => SoundSpec
321
345
  - `spectralCentroid`: (sig: Samples, sampleRate?: number) => number
322
346
  - `springStep`: (s: SpringState, target: number, omega: number, dt: number) => SpringState
323
347
  - `star`: (points: number, outer: number, inner: number, rotation?: number) => number[]
@@ -344,8 +368,10 @@
344
368
  - `vnorm`: (a: Vec2) => Vec2
345
369
  - `voiceLead`: (chords: number[][]) => number[][]
346
370
  - `wait`: (frames: number) => Segment
371
+ - `waitFor`: (cond: () => boolean) => Wait
347
372
  - `wakeBody`: (b: RigidBody) => void
348
373
  - `wangTile`: (mask: number) => WangTile
374
+ - `warnCommandOnce`: (kind: string, reason: string, detail?: unknown) => void
349
375
  - `weatherEnvelope`: (t: number, keys: readonly WeatherKey[]) => number
350
376
  - `weightedIndex`: (rng: Rng, weights: readonly number[]) => number
351
377
  - `weightedPick`: <T>(rng: Rng, items: readonly T[], weights: readonly number[]) => T
@@ -354,7 +380,7 @@
354
380
  - `worldPoints`: (b: RigidBody) => number[]
355
381
  - `zcr`: (sig: Samples, sampleRate?: number) => number
356
382
 
357
- ## interfaces (222)
383
+ ## interfaces (240)
358
384
 
359
385
  - `AcceptedProof`
360
386
  - `ActBounds`
@@ -364,6 +390,7 @@
364
390
  - `AnalyzeOptions`
365
391
  - `Annotation`
366
392
  - `AnnotationContext`
393
+ - `ArcCommand`
367
394
  - `AudioAssertion`
368
395
  - `AudioExpectation`
369
396
  - `AudioFeatures`
@@ -396,18 +423,22 @@
396
423
  - `ContourOptions`
397
424
  - `ContourSegment`
398
425
  - `CoreEvents`
426
+ - `CoroutineHandle`
399
427
  - `CreateWorldOptions`
400
428
  - `DeathCluster`
429
+ - `DepthSortConfig`
401
430
  - `DesyncInfo`
402
431
  - `DeterminismReport`
403
432
  - `DifficultyBand`
404
433
  - `DirectorState`
405
434
  - `DistanceJoint`
406
435
  - `DistanceJointDef`
436
+ - `DomHarness`
407
437
  - `DriftAmounts`
408
438
  - `DriveResult`
409
439
  - `Dungeon`
410
440
  - `DungeonOptions`
441
+ - `EllipseCommand`
411
442
  - `FeatureDelta`
412
443
  - `FeedbackOptions`
413
444
  - `FeedbackResponse`
@@ -422,6 +453,7 @@
422
453
  - `FutileVerb`
423
454
  - `GameDefinition`
424
455
  - `GameHandle`
456
+ - `GamepadSourceOptions`
425
457
  - `GeneratedLevel`
426
458
  - `GenerateOptions`
427
459
  - `GenerateReport`
@@ -436,6 +468,10 @@
436
468
  - `HSL`
437
469
  - `ImageCommand`
438
470
  - `InputLog`
471
+ - `InputSource`
472
+ - `IsoConfig`
473
+ - `IsoPrismConfig`
474
+ - `IsoProjection`
439
475
  - `JointScratch`
440
476
  - `KnobEvent`
441
477
  - `LayoutOptions`
@@ -480,8 +516,10 @@
480
516
  - `PlaytestSession`
481
517
  - `PlaythroughResult`
482
518
  - `PointerReadout`
519
+ - `PointerSourceOptions`
483
520
  - `PointerTarget`
484
521
  - `PolyCommand`
522
+ - `PostProcessPass`
485
523
  - `ProgressState`
486
524
  - `Puzzle`
487
525
  - `QualityScore`
@@ -552,6 +590,9 @@
552
590
  - `TimelineEntry`
553
591
  - `TimerConfig`
554
592
  - `Tone`
593
+ - `TouchButton`
594
+ - `TouchControlsLayout`
595
+ - `TouchStick`
555
596
  - `Track`
556
597
  - `Transform`
557
598
  - `Transition`
@@ -563,11 +604,14 @@
563
604
  - `Variant`
564
605
  - `VariantRef`
565
606
  - `Vec2`
607
+ - `VerletChainConfig`
608
+ - `Viewport`
566
609
  - `Volumes`
567
610
  - `WallClockMark`
568
611
  - `WangTile`
569
612
  - `WaveDef`
570
613
  - `WeatherKey`
614
+ - `WebGLRendererConfig`
571
615
  - `WeightedEdge`
572
616
  - `WeightedEntry`
573
617
  - `WipeOptions`
@@ -579,8 +623,9 @@
579
623
  - `WorldRegion`
580
624
  - `WorldSnapshot`
581
625
 
582
- ## types (41)
626
+ ## types (46)
583
627
 
628
+ - `AxisFrame`
584
629
  - `BodyKind`
585
630
  - `BoolGrid`
586
631
  - `CompletionMode`
@@ -592,6 +637,7 @@
592
637
  - `EndReason`
593
638
  - `FeedbackChannel`
594
639
  - `FeedbackContract`
640
+ - `GamepadMap`
595
641
  - `Gradient`
596
642
  - `InputMap`
597
643
  - `InputScript`
@@ -603,6 +649,7 @@
603
649
  - `NodeFactory`
604
650
  - `NodeKey`
605
651
  - `Passable`
652
+ - `PauseMode`
606
653
  - `Pitch`
607
654
  - `PlayerId`
608
655
  - `ProbeFrame`
@@ -617,19 +664,23 @@
617
664
  - `TileId`
618
665
  - `TuningValue`
619
666
  - `TuningValues`
667
+ - `UniformValue`
668
+ - `Wait`
620
669
  - `WangFrame`
621
670
  - `Wave`
622
671
  - `WeightedNeighbours`
623
672
  - `WipeKind`
624
673
  - `WorldFactory`
625
674
 
626
- ## consts (57)
675
+ ## consts (61)
627
676
 
628
677
  - `ALBUM`: { title: string; subtitle: string; concept: string; tracks: AlbumTrack[]; }
629
678
  - `AMBIENT_PRESETS`: { readonly snow: (colors?: string[]) => AmbientStyle; readonly rain: (colors?: string[]) => AmbientStyle; readonly ash: (colors?: string[]) => AmbientStyle; }
630
679
  - `audio`: AudioBus
680
+ - `BLOOM_PIPELINE`: PostProcessPass[]
631
681
  - `clamp`: (v: number, lo: number, hi: number) => number
632
- - `DEFAULT_INPUT_MAP`: InputMap
682
+ - `DEFAULT_GAMEPAD_MAP`: GamepadMap
683
+ - `DEFAULT_INPUT_MAP`: Readonly<Record<string, readonly string[]>>
633
684
  - `DEFAULT_PLATFORMER`: PlatformerConfig
634
685
  - `DEFAULT_SESSION_CONFIG`: NetSessionConfig
635
686
  - `DEFAULT_TILE_CHARS`: Record<string, TileId>
@@ -652,6 +703,7 @@
652
703
  - `mapHeight`: (map: TilemapData) => number
653
704
  - `mapWidth`: (map: TilemapData) => number
654
705
  - `MEADOW`: Palette
706
+ - `MOUSE_ACTIONS`: readonly ["mouse.left", "mouse.right", "mouse.middle"]
655
707
  - `NEIGHBORS_4`: readonly Cell[]
656
708
  - `NEIGHBORS_8`: readonly Cell[]
657
709
  - `NET_PROTOCOL_VERSION`: 1
@@ -661,7 +713,7 @@
661
713
  - `PAPER`: Palette
662
714
  - `PARTICLE_PRESETS`: { readonly dust: (colors?: string[]) => ParticleStyle; readonly burst: (colors?: string[]) => ParticleStyle; readonly hit: (colors?: string[]) => ParticleSty…
663
715
  - `playerAction`: (player: string, action: string) => string
664
- - `playerInput`: (world: World, player: string) => PlayerInput
716
+ - `playerInput`: (world: World<Record<string, unknown>>, player: string) => PlayerInput
665
717
  - `rad2deg`: (r: number) => number
666
718
  - `remap`: (v: number, a0: number, a1: number, b0: number, b1: number) => number
667
719
  - `SAMPLE_RATE`: 44100
@@ -678,10 +730,11 @@
678
730
  - `vdist`: (a: Vec2, b: Vec2) => number
679
731
  - `vdot`: (a: Vec2, b: Vec2) => number
680
732
  - `vec2`: (x?: number, y?: number) => Vec2
681
- - `VERSION`: "0.3.0"
733
+ - `VERSION`: "0.4.0"
682
734
  - `vlen`: (a: Vec2) => number
683
735
  - `vscale`: (a: Vec2, s: number) => Vec2
684
736
  - `vsub`: (a: Vec2, b: Vec2) => Vec2
737
+ - `WEBGL_EFFECTS`: { readonly passthrough: "#version 300 es\nprecision mediump float;\nin vec2 v_uv;\nuniform sampler2D u_scene;\nuniform sampler2D u_prev;\nout vec4 fragColor;…
685
738
 
686
739
  ## exports (1)
687
740