@moku-labs/game 0.0.1 → 0.0.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.
@@ -0,0 +1,2840 @@
1
+ import { D as Hint, I as Events$2, K as Snapshot, L as Json, M as Time, P as Api$3, R as Root, X as Api$5, k as Api$2, lt as Require, nt as Api$6, st as Config$2, t as Api$4 } from "./types-JNc_UQBo.mjs";
2
+ import { Log } from "@moku-labs/common/browser";
3
+ import { PluginCtx } from "@moku-labs/core";
4
+
5
+ //#region src/plugins/world/ecs/types.d.ts
6
+ /**
7
+ * Entity id: `generation * 2 ** 20 + index`, a safe integer. A freed index comes back with a
8
+ * higher generation, so an id is never silently reused.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * const entity: Entity = 1_048_576; // generation 1, index 0
13
+ * ```
14
+ */
15
+ type Entity = number;
16
+ /**
17
+ * Who owns an entity. Every `spawn` names one, so `despawnOwnedBy` can free exactly one owner's
18
+ * entities and `snapshot()` can say where an entity came from.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * const owner: Owner = { kind: "projection", name: "board.items" };
23
+ * ```
24
+ */
25
+ type Owner = {
26
+ kind: "projection" | "node" | "plugin";
27
+ name: string;
28
+ };
29
+ /**
30
+ * One entity as `snapshot()` reads it: its id and the two halves of the id, its owner, every
31
+ * component value that is plain JSON, and the names of the ones that are not.
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * const entity: EntitySnapshot = {
36
+ * id: 1_048_576, index: 0, generation: 1, owner: { kind: "projection", name: "board.items" },
37
+ * components: { Layer: { name: "items" } }, skipped: ["Display"]
38
+ * };
39
+ * ```
40
+ */
41
+ type EntitySnapshot = {
42
+ id: Entity;
43
+ index: number;
44
+ generation: number;
45
+ owner: Owner;
46
+ components: Record<string, Json>;
47
+ skipped: string[];
48
+ };
49
+ /**
50
+ * The whole world as plain JSON: the effective mode, every entity sorted by index and every
51
+ * resource that is JSON.
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * const empty: WorldSnapshot = { mode: "live", entities: [], resources: {} };
56
+ * ```
57
+ */
58
+ type WorldSnapshot = {
59
+ mode: WorldMode;
60
+ entities: EntitySnapshot[];
61
+ resources: Record<string, Json>;
62
+ };
63
+ /**
64
+ * The four phases systems run in, in frame order.
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * const phase: WorldPhase = "animate";
69
+ * ```
70
+ */
71
+ type WorldPhase = "input" | "animate" | "layout" | "sync";
72
+ /**
73
+ * World mode. `"paused"` and `"fast"` skip the phases `input`, `animate` and `layout`; phase
74
+ * `sync` runs in every mode.
75
+ *
76
+ * @example
77
+ * ```ts
78
+ * const mode: WorldMode = "fast";
79
+ * ```
80
+ */
81
+ type WorldMode = "live" | "paused" | "fast";
82
+ /**
83
+ * One component value ready to be written: the storage key and the frozen data.
84
+ *
85
+ * @example
86
+ * ```ts
87
+ * const value: ComponentValue<{ level: number }> = { type: Item, value: { level: 2 } };
88
+ * ```
89
+ */
90
+ type ComponentValue<Value extends object> = {
91
+ readonly type: ComponentType<Value>;
92
+ readonly value: Readonly<Value>;
93
+ };
94
+ /**
95
+ * A tag value. A tag carries no data and is stored as `true`.
96
+ *
97
+ * @example
98
+ * ```ts
99
+ * const held: TagValue = { type: Held, value: true };
100
+ * ```
101
+ */
102
+ type TagValue = {
103
+ readonly type: TagType;
104
+ readonly value: true;
105
+ };
106
+ /**
107
+ * Any component or tag value, as `spawn` and `view` return them.
108
+ *
109
+ * @example
110
+ * ```ts
111
+ * const values: AnyComponentValue[] = [Held()];
112
+ * ```
113
+ */
114
+ type AnyComponentValue = {
115
+ readonly type: AnyComponentType;
116
+ readonly value: object | true;
117
+ };
118
+ /**
119
+ * A component type whose value shape is erased to a plain record: what the projection writes when
120
+ * it only knows the component by name.
121
+ *
122
+ * @example
123
+ * ```ts
124
+ * const erased: AnyComponent = component("Transform", { x: 0, y: 0 }) as AnyComponent;
125
+ * erased.componentName; // "Transform"
126
+ * ```
127
+ */
128
+ type AnyComponent = ComponentType<Record<string, unknown>>;
129
+ /**
130
+ * A component type made by `component()`: callable to build a value, and carrying the storage
131
+ * name plus the defaults that give the TypeScript type, the JSON shape and the inspector schema.
132
+ *
133
+ * @example
134
+ * ```ts
135
+ * const Item: ComponentType<{ kind: string; level: number }> = component("Item", {
136
+ * kind: "",
137
+ * level: 1
138
+ * });
139
+ * Item({ level: 2 }); // { type: Item, value: { kind: "", level: 2 } }
140
+ * Item.owned; // []: a projection view writes every field
141
+ * ```
142
+ */
143
+ type ComponentType<Value extends object> = {
144
+ (patch?: Partial<Value>): ComponentValue<Value>;
145
+ readonly componentName: string;
146
+ readonly defaults: Readonly<Value>;
147
+ readonly kind: "component"; /** Fields a plugin writes and a projection view never does, so a view never corrects them. */
148
+ readonly owned: readonly string[];
149
+ };
150
+ /**
151
+ * A tag type made by `tag()`: a component with no data.
152
+ *
153
+ * @example
154
+ * ```ts
155
+ * const Held: TagType = tag("Held");
156
+ * Held(); // { type: Held, value: true }
157
+ * ```
158
+ */
159
+ type TagType = {
160
+ (): TagValue;
161
+ readonly componentName: string;
162
+ readonly kind: "tag";
163
+ };
164
+ /**
165
+ * What the world stores about a component or tag type, independent of its data type.
166
+ *
167
+ * @example
168
+ * ```ts
169
+ * const registered: AnyComponentType = { componentName: "Held", kind: "tag" };
170
+ * ```
171
+ */
172
+ type AnyComponentType = {
173
+ readonly componentName: string;
174
+ readonly kind: "component" | "tag";
175
+ };
176
+ /**
177
+ * What the ECS needs from a component type to read and write its store: the name and the
178
+ * defaults, never the call signature. A component bound to a game's keys by a `…For` binder
179
+ * (`Narrowed`) is the same object with a narrower call, so it passes here unchanged.
180
+ *
181
+ * @example
182
+ * ```ts
183
+ * const ref: ComponentHandle<{ x: number }> = component("Pos", { x: 0 });
184
+ * ref.defaults; // { x: 0 }
185
+ * ```
186
+ */
187
+ type ComponentHandle<Value extends object> = {
188
+ readonly componentName: string;
189
+ readonly defaults: Readonly<Value>;
190
+ readonly kind: "component";
191
+ };
192
+ /**
193
+ * A component type whose call signature takes a narrower patch: the same object, the same
194
+ * store, the compiler refuses what the game's generated keys do not know.
195
+ *
196
+ * @example
197
+ * ```ts
198
+ * const Sprite: Narrowed<{ texture: string }, { texture?: "board.cell" }> = component("Sprite", { texture: "" });
199
+ * Sprite({ texture: "board.cell" }).value.texture; // "board.cell"
200
+ * ```
201
+ */
202
+ type Narrowed<Value extends object, Patch> = Omit<ComponentType<Value>, never> & ((patch?: Patch) => ComponentValue<Value>);
203
+ /**
204
+ * A resource type made by `resource()`: one mutable object per world, created from a deep clone
205
+ * of the defaults on first read.
206
+ *
207
+ * @example
208
+ * ```ts
209
+ * const Pointer: ResourceType<{ x: number; y: number }> = resource("Pointer", { x: 0, y: 0 });
210
+ * Pointer.resourceName; // "Pointer"
211
+ * ```
212
+ */
213
+ type ResourceType<Value extends object> = {
214
+ readonly resourceName: string;
215
+ readonly defaults: Readonly<Value>;
216
+ };
217
+ /**
218
+ * A query term marked as written by `mut()`. Its only effect is change detection.
219
+ *
220
+ * @example
221
+ * ```ts
222
+ * const written: Mut<{ x: number }> = mut(Transform);
223
+ * written.kind; // "mut"
224
+ * ```
225
+ */
226
+ type Mut<Value extends object> = {
227
+ readonly kind: "mut";
228
+ readonly of: ComponentHandle<Value>;
229
+ };
230
+ /**
231
+ * A `mut()` term with its data type erased, as the world stores it.
232
+ *
233
+ * @example
234
+ * ```ts
235
+ * const term: AnyMut = { kind: "mut", of: { componentName: "Transform", kind: "component" } };
236
+ * ```
237
+ */
238
+ type AnyMut = {
239
+ readonly kind: "mut";
240
+ readonly of: AnyComponentType;
241
+ };
242
+ /**
243
+ * One term of a query: a component type, a tag type, or a `mut()` of a component type.
244
+ *
245
+ * @example
246
+ * ```ts
247
+ * const terms: QueryTerm[] = [mut(Transform), Sprite, Held];
248
+ * ```
249
+ */
250
+ type QueryTerm = AnyComponentType | AnyMut;
251
+ /**
252
+ * The value a query yields for one term: writable for `mut()`, `true` for a tag, read-only for a
253
+ * plain component.
254
+ *
255
+ * @example
256
+ * ```ts
257
+ * type Written = ValueOf<Mut<{ x: number }>>; // { x: number }
258
+ * ```
259
+ */
260
+ type ValueOf<Term> = Term extends Mut<infer Value> ? Value : Term extends TagType ? true : Term extends ComponentHandle<infer Value> ? Readonly<Value> : never;
261
+ /**
262
+ * The value part of a query row, one entry per term, in term order.
263
+ *
264
+ * @example
265
+ * ```ts
266
+ * type Values = QueryValues<[TagType]>; // [true]
267
+ * ```
268
+ */
269
+ type QueryValues<Terms extends readonly QueryTerm[]> = { -readonly [Index in keyof Terms]: ValueOf<Terms[Index]> };
270
+ /**
271
+ * One row a query yields: the entity, then one value per term.
272
+ *
273
+ * @example
274
+ * ```ts
275
+ * type Row = QueryTuple<[ComponentType<{ x: number }>]>; // [Entity, Readonly<{ x: number }>]
276
+ * ```
277
+ */
278
+ type QueryTuple<Terms extends readonly QueryTerm[]> = [Entity, ...QueryValues<Terms>];
279
+ /**
280
+ * What a system receives next to its entities: the world, the mutable resources, the frozen model
281
+ * snapshot of the frame and the frame's `Time`. A system may read pure game rules; it never
282
+ * writes the model.
283
+ *
284
+ * @example
285
+ * ```ts
286
+ * const run = (entities: Iterable<[Entity]>, ctx: SystemContext): void => {
287
+ * ctx.res(Pointer).x = 0;
288
+ * ctx.time.delta; // 16
289
+ * };
290
+ * ```
291
+ */
292
+ type SystemContext = {
293
+ readonly world: EcsApi;
294
+ res<Value extends object>(resource: ResourceType<Value>): Value;
295
+ readonly snapshot: Snapshot;
296
+ readonly time: Readonly<Time>;
297
+ };
298
+ /**
299
+ * A system definition, typed over its query tuple.
300
+ *
301
+ * @example
302
+ * ```ts
303
+ * const bob: SystemDefinition<[ComponentType<{ y: number }>]> = {
304
+ * name: "bob",
305
+ * phase: "animate",
306
+ * query: [Transform],
307
+ * run: entities => {
308
+ * for (const [, transform] of entities) transform.y; // read-only
309
+ * }
310
+ * };
311
+ * ```
312
+ */
313
+ type SystemDefinition<Terms extends readonly QueryTerm[]> = {
314
+ readonly name: string;
315
+ readonly phase: WorldPhase;
316
+ readonly query: Terms;
317
+ run(entities: Iterable<QueryTuple<Terms>>, ctx: SystemContext): void;
318
+ };
319
+ /**
320
+ * A system with its query tuple erased, as the world stores it.
321
+ *
322
+ * @example
323
+ * ```ts
324
+ * const entry: AnySystem = { name: "bob", phase: "animate", query: [], run: () => {} };
325
+ * ```
326
+ */
327
+ type AnySystem = {
328
+ readonly name: string;
329
+ readonly phase: WorldPhase;
330
+ readonly query: readonly QueryTerm[];
331
+ run(entities: Iterable<readonly unknown[]>, ctx: SystemContext): void;
332
+ };
333
+ /**
334
+ * One registered system and the frame it was registered in. A system registered during a frame
335
+ * runs from the next frame on.
336
+ *
337
+ * @example
338
+ * ```ts
339
+ * const entry: SystemEntry = { definition: bobSystem, frame: 12 };
340
+ * ```
341
+ */
342
+ type SystemEntry = {
343
+ readonly definition: AnySystem;
344
+ readonly frame: number;
345
+ };
346
+ /**
347
+ * A structural change queued while a phase runs. Applied in call order when the phase ends.
348
+ *
349
+ * @example
350
+ * ```ts
351
+ * const command: Command = { kind: "remove", entity: 1_048_576, component: "Held" };
352
+ * ```
353
+ */
354
+ type Command = {
355
+ kind: "attach";
356
+ entity: Entity;
357
+ components: readonly AnyComponentValue[];
358
+ } | {
359
+ kind: "despawn";
360
+ entity: Entity;
361
+ } | {
362
+ kind: "despawnOwnedBy";
363
+ owner: Owner;
364
+ } | {
365
+ kind: "add";
366
+ entity: Entity;
367
+ value: AnyComponentValue;
368
+ } | {
369
+ kind: "remove";
370
+ entity: Entity;
371
+ component: string;
372
+ };
373
+ /**
374
+ * A structural listener registered with `onAdded` or `onRemoved`.
375
+ *
376
+ * @example
377
+ * ```ts
378
+ * const hook: StructuralHook = (entity, value) => void entity + String(value);
379
+ * ```
380
+ */
381
+ type StructuralHook = (entity: Entity, value: unknown) => void;
382
+ /**
383
+ * The frozen model snapshot of one frame, kept so four phases read it once.
384
+ *
385
+ * @example
386
+ * ```ts
387
+ * const cached: FrameSnapshot = { frame: 12, snapshot: { player: {}, session: {}, rng: rngState } };
388
+ * ```
389
+ */
390
+ type FrameSnapshot = {
391
+ frame: number;
392
+ snapshot: Snapshot;
393
+ };
394
+ /**
395
+ * ecs module state.
396
+ */
397
+ type EcsState = {
398
+ generations: number[];
399
+ free: number[];
400
+ owners: Map<Entity, Owner>; /** Key is `"kind:name"` of the owner. */
401
+ byOwner: Map<string, Set<Entity>>;
402
+ types: Map<string, AnyComponentType>;
403
+ stores: Map<string, Map<Entity, object | true>>;
404
+ resources: Map<string, object>;
405
+ systems: Record<WorldPhase, SystemEntry[]>;
406
+ running: WorldPhase | undefined;
407
+ commands: Command[];
408
+ changed: Map<string, Set<Entity>>;
409
+ added: Map<string, StructuralHook[]>;
410
+ removed: Map<string, StructuralHook[]>;
411
+ ownerLeft: Array<(owner: Owner) => void>;
412
+ mode: WorldMode;
413
+ offFrame: Array<() => void>;
414
+ frameSnapshot: FrameSnapshot | undefined;
415
+ };
416
+ /**
417
+ * ecs module API, `app.world.ecs`: the screen as data. Plain-object components in one store per
418
+ * component type, generational entity ids, a mandatory owner, a command buffer applied between
419
+ * phases, coarse change detection and fixed phases driven by `time`.
420
+ *
421
+ * @example
422
+ * ```ts
423
+ * // A test owns its entities and reads them back.
424
+ * const entity = app.world.ecs.spawn({ kind: "plugin", name: "test" }, [Transform({ x: 10 })]);
425
+ * app.world.ecs.get(entity, Transform); // { x: 10, y: 0, rotation: 0, scale: 1, pivot: { x: 0, y: 0 } }
426
+ * ```
427
+ */
428
+ type EcsApi = {
429
+ /**
430
+ * Registers a system at the end of its phase list. A system registered during a frame runs
431
+ * from the next frame on.
432
+ *
433
+ * @param definition - The system, built with `system()`.
434
+ * @returns The remover; calling it twice is a no-op.
435
+ * @throws {Error} When a system of that name is already registered.
436
+ * @example
437
+ * ```ts
438
+ * // `text` registers its layout system when the plugin starts.
439
+ * const world = ctx.require(worldPlugin);
440
+ * const off = world.ecs.system(measureLabels); // runs from the next frame
441
+ *
442
+ * off(); // the plugin stops
443
+ * ```
444
+ */
445
+ system(definition: AnySystem): () => void;
446
+ /**
447
+ * Creates an entity. The id is reserved and returned at once, also inside a phase, where the
448
+ * components are attached when the phase ends.
449
+ *
450
+ * @param owner - Who owns the entity. Required by the type.
451
+ * @param components - Component and tag values the entity starts with.
452
+ * @returns The new entity id.
453
+ * @example
454
+ * ```ts
455
+ * // `ui` spawns a popup entity it owns, so closing the node frees it in one call.
456
+ * const entity = app.world.ecs.spawn({ kind: "node", name: "reward" }, [
457
+ * Transform({ x: 540, y: 960 })
458
+ * ]);
459
+ * app.world.ecs.has(entity, Transform); // true
460
+ * ```
461
+ */
462
+ spawn(owner: Owner, components: readonly AnyComponentValue[]): Entity;
463
+ /**
464
+ * Removes every component of an entity, each firing `onRemoved`, frees the index and bumps the
465
+ * generation. A stale id is a no-op.
466
+ *
467
+ * @param entity - The entity to remove.
468
+ * @example
469
+ * ```ts
470
+ * // `ui` closes the popup it spawned. Asking again answers with the empty world.
471
+ * app.world.ecs.despawn(popup);
472
+ * app.world.ecs.has(popup, Transform); // false
473
+ * ```
474
+ */
475
+ despawn(entity: Entity): void;
476
+ /**
477
+ * Despawns exactly the entities of one owner and tells `projection` that the owner left.
478
+ *
479
+ * @param owner - The owner whose entities go.
480
+ * @example
481
+ * ```ts
482
+ * // A test cleans up after itself without touching the projections of the game.
483
+ * app.world.ecs.despawnOwnedBy({ kind: "plugin", name: "test" });
484
+ * app.world.ecs.snapshot(); // { mode: "live", entities: [], resources: {} }
485
+ * ```
486
+ */
487
+ despawnOwnedBy(owner: Owner): void;
488
+ /**
489
+ * Reads a component value. A stale id or a missing component gives `undefined`.
490
+ *
491
+ * @param entity - The entity to read.
492
+ * @param component - The component type.
493
+ * @returns The stored value, read-only, or `undefined`.
494
+ * @example
495
+ * ```ts
496
+ * // `input` asks whether the entity under the finger may be dragged.
497
+ * const world = ctx.require(worldPlugin);
498
+ * world.ecs.get(hit, Draggable); // { payload: { id: "i7" } }
499
+ * world.ecs.get(hit, DropTarget); // undefined: this item takes no drop
500
+ * ```
501
+ */
502
+ get<Value extends object>(entity: Entity, component: ComponentHandle<Value>): Readonly<Value> | undefined;
503
+ /**
504
+ * Shallow-merges a patch into a stored component and marks the entity changed for it. Never
505
+ * queued: a `set` inside a phase is visible to the next system of that phase.
506
+ *
507
+ * @param entity - The entity to write.
508
+ * @param component - The component type.
509
+ * @param patch - The fields to overwrite.
510
+ * @throws {Error} When the entity is stale or does not carry the component.
511
+ * @example
512
+ * ```ts
513
+ * // `input` moves the held view with the finger.
514
+ * const world = ctx.require(worldPlugin);
515
+ * world.ecs.set(held, Transform, { x: 420, y: 810 });
516
+ * world.ecs.get(held, Transform); // { x: 420, y: 810, rotation: 0, scale: 1, pivot: { x: 0, y: 0 } }
517
+ * ```
518
+ */
519
+ set<Value extends object>(entity: Entity, component: ComponentHandle<Value>, patch: Partial<Value>): void;
520
+ /**
521
+ * Adds a component value. On an entity that already carries it the value is replaced and the
522
+ * entity is marked changed; no `onAdded` fires.
523
+ *
524
+ * @param entity - The entity to write.
525
+ * @param value - The component or tag value, built by calling its type.
526
+ * @example
527
+ * ```ts
528
+ * // `text` gives the label entity its display object once the atlas is there.
529
+ * const world = ctx.require(worldPlugin);
530
+ * world.ecs.add(label, Display({ object: bitmapText }));
531
+ * world.ecs.has(label, Display); // true
532
+ * ```
533
+ */
534
+ add(entity: Entity, value: AnyComponentValue): void;
535
+ /**
536
+ * Removes a component. Fires `onRemoved` when the entity carried it.
537
+ *
538
+ * @param entity - The entity to write.
539
+ * @param component - The component or tag type to drop.
540
+ * @example
541
+ * ```ts
542
+ * // `text` drops the display object when the label leaves the screen.
543
+ * const world = ctx.require(worldPlugin);
544
+ * world.ecs.remove(label, Display);
545
+ * world.ecs.has(label, Display); // false
546
+ * ```
547
+ */
548
+ remove(entity: Entity, component: AnyComponentType): void;
549
+ /**
550
+ * Tells whether an entity carries a component or tag. False for a stale id.
551
+ *
552
+ * @param entity - The entity to read.
553
+ * @param component - The component or tag type.
554
+ * @returns True when the entity carries it.
555
+ * @example
556
+ * ```ts
557
+ * // `input` never hit-tests a view that is on its way out.
558
+ * const world = ctx.require(worldPlugin);
559
+ * world.ecs.has(candidate, Exiting); // true while the merge animation plays
560
+ * ```
561
+ */
562
+ has(entity: Entity, component: AnyComponentType): boolean;
563
+ /**
564
+ * Adds a tag. Structural and idempotent.
565
+ *
566
+ * @param entity - The entity to write.
567
+ * @param tagType - The tag type.
568
+ * @example
569
+ * ```ts
570
+ * // `input` marks the view under the finger while the drag runs.
571
+ * const world = ctx.require(worldPlugin);
572
+ * world.ecs.tag(dragged, Held);
573
+ * world.ecs.has(dragged, Held); // true
574
+ * ```
575
+ */
576
+ tag(entity: Entity, tagType: TagType): void;
577
+ /**
578
+ * Removes a tag. Structural and idempotent.
579
+ *
580
+ * @param entity - The entity to write.
581
+ * @param tagType - The tag type.
582
+ * @example
583
+ * ```ts
584
+ * // `input` releases the view on pointer up.
585
+ * const world = ctx.require(worldPlugin);
586
+ * world.ecs.untag(dragged, Held);
587
+ * world.ecs.has(dragged, Held); // false
588
+ * ```
589
+ */
590
+ untag(entity: Entity, tagType: TagType): void;
591
+ /**
592
+ * Walks the store of the FIRST term in insertion order and keeps the entities that carry every
593
+ * term. Deterministic. A `mut()` term marks every yielded entity changed for that component.
594
+ *
595
+ * @param terms - Component types, tag types and `mut()` terms.
596
+ * @returns The matching rows: the entity and one value per term.
597
+ * @example
598
+ * ```ts
599
+ * // `renderer.sync` walks every sprite that has a place on the screen.
600
+ * for (const [entity, sprite, transform] of app.world.ecs.query(Sprite, Transform)) {
601
+ * display.get(entity)?.position.set(transform.x, transform.y);
602
+ * sprite.texture; // "board.item-red-1"
603
+ * }
604
+ * ```
605
+ */
606
+ query<const Terms extends readonly QueryTerm[]>(...terms: Terms): Iterable<QueryTuple<Terms>>;
607
+ /**
608
+ * Returns the one mutable value of a resource, created from a deep clone of its defaults on
609
+ * first read.
610
+ *
611
+ * @param resourceType - The resource type.
612
+ * @returns The mutable resource value.
613
+ * @example
614
+ * ```ts
615
+ * // `input` writes the pointer once per frame; every game system reads the same object.
616
+ * const world = ctx.require(worldPlugin);
617
+ * const pointer = world.ecs.resource(Pointer);
618
+ * pointer.down = true;
619
+ * ```
620
+ */
621
+ resource<Value extends object>(resourceType: ResourceType<Value>): Value;
622
+ /**
623
+ * Registers a listener for the moment a component is added, called when the structural change
624
+ * is applied. A throwing listener is logged and the others still run.
625
+ *
626
+ * @param component - The component type to watch.
627
+ * @param fn - Called with the entity and the stored value.
628
+ * @returns The remover; calling it twice is a no-op.
629
+ * @example
630
+ * ```ts
631
+ * // `renderer.sync` creates a Pixi object for every new sprite.
632
+ * const world = ctx.require(worldPlugin);
633
+ * const off = world.ecs.onAdded(Sprite, (entity, sprite) => {
634
+ * objects.set(entity, makeSprite(sprite.texture));
635
+ * });
636
+ *
637
+ * off(); // the renderer stops
638
+ * ```
639
+ */
640
+ onAdded<Value extends object>(component: ComponentHandle<Value>, fn: (entity: Entity, value: Readonly<Value>) => void): () => void;
641
+ /**
642
+ * Registers a listener for the moment a tag is added: `tag` on an entity without it, or a spawn
643
+ * that carries it. A tag has no value, so the listener gets the entity only.
644
+ *
645
+ * @param tagType - The tag type to watch.
646
+ * @param fn - Called with the entity that got the tag.
647
+ * @returns The remover; calling it twice is a no-op.
648
+ * @example
649
+ * ```ts
650
+ * // `ui` shows the pressed look while `input` holds the `Pressed` tag on a button.
651
+ * const world = ctx.require(worldPlugin);
652
+ * const off = world.ecs.onAdded(Pressed, entity => markPointer(entity, "pressed", true));
653
+ *
654
+ * world.ecs.tag(button, Pressed); // markPointer(button, "pressed", true)
655
+ * off(); // ui stops
656
+ * ```
657
+ */
658
+ onAdded(tagType: TagType, fn: (entity: Entity) => void): () => void;
659
+ /**
660
+ * Registers a listener for the moment a component is removed.
661
+ *
662
+ * @param component - The component type to watch.
663
+ * @param fn - Called with the entity and the value it had.
664
+ * @returns The remover; calling it twice is a no-op.
665
+ * @example
666
+ * ```ts
667
+ * // `renderer.sync` destroys the Pixi object of a sprite that left.
668
+ * const world = ctx.require(worldPlugin);
669
+ * const off = world.ecs.onRemoved(Sprite, entity => {
670
+ * objects.get(entity)?.destroy();
671
+ * objects.delete(entity);
672
+ * });
673
+ *
674
+ * off(); // the renderer stops
675
+ * ```
676
+ */
677
+ onRemoved<Value extends object>(component: ComponentHandle<Value>, fn: (entity: Entity, value: Readonly<Value>) => void): () => void;
678
+ /**
679
+ * Registers a listener for the moment a tag is removed: `untag` on an entity that had it, or a
680
+ * despawn. The listener gets the entity only.
681
+ *
682
+ * @param tagType - The tag type to watch.
683
+ * @param fn - Called with the entity that lost the tag.
684
+ * @returns The remover; calling it twice is a no-op.
685
+ * @example
686
+ * ```ts
687
+ * // `ui` drops the pressed look when `input` releases the button.
688
+ * const world = ctx.require(worldPlugin);
689
+ * const off = world.ecs.onRemoved(Pressed, entity => markPointer(entity, "pressed", false));
690
+ *
691
+ * world.ecs.untag(button, Pressed); // markPointer(button, "pressed", false)
692
+ * off(); // ui stops
693
+ * ```
694
+ */
695
+ onRemoved(tagType: TagType, fn: (entity: Entity) => void): () => void;
696
+ /**
697
+ * The coarse change set of the frame, cleared in `time` phase `signals` after every `sync`
698
+ * system ran.
699
+ *
700
+ * @param component - The component type to ask about.
701
+ * @returns The entities marked changed for it this frame.
702
+ * @example
703
+ * ```ts
704
+ * // `renderer.sync` moves only what moved.
705
+ * for (const entity of app.world.ecs.changed(Transform)) {
706
+ * const transform = app.world.ecs.get(entity, Transform);
707
+ * display.get(entity)?.position.set(transform?.x ?? 0, transform?.y ?? 0);
708
+ * }
709
+ * ```
710
+ */
711
+ changed(component: AnyComponentType): Iterable<Entity>;
712
+ /**
713
+ * The effective mode: `"fast"` while the flow walks fast, otherwise the stored one.
714
+ *
715
+ * @returns The mode the frame runs in.
716
+ * @example
717
+ * ```ts
718
+ * // A test checks that a fast walk froze the tweens.
719
+ * app.flow.setMode("fast");
720
+ * app.world.ecs.mode(); // "fast", even though setMode was never called on the world
721
+ * ```
722
+ */
723
+ mode(): WorldMode;
724
+ /**
725
+ * Stores the world mode. `"fast"` finishes every motion and flushes every despawn queue at once.
726
+ *
727
+ * @param mode - The new stored mode.
728
+ * @example
729
+ * ```ts
730
+ * // A headless test wants the final picture without stepping frames.
731
+ * app.world.ecs.setMode("fast");
732
+ * app.world.ecs.mode(); // "fast"
733
+ * ```
734
+ */
735
+ setMode(mode: WorldMode): void;
736
+ /**
737
+ * The component type behind a name. A type registers itself on first use, so a name nobody
738
+ * wrote, read or queried yet is unknown.
739
+ *
740
+ * @param name - The storage name of the component.
741
+ * @returns The type, or `undefined` when the world never met it.
742
+ * @example
743
+ * ```ts
744
+ * // `text` resolves the component a label binds its number to.
745
+ * const world = ctx.require(worldPlugin);
746
+ * const type = world.ecs.typeOf("Coins"); // the Coins component, registered when it was written
747
+ * const pose = type === undefined ? undefined : world.ecs.get(counter, type);
748
+ *
749
+ * pose?.amount; // 120
750
+ * ```
751
+ */
752
+ typeOf(name: string): AnyComponent | undefined;
753
+ /**
754
+ * The whole world as plain JSON, sorted by entity index. A component value that is not JSON is
755
+ * left out and its name is listed in `skipped`.
756
+ *
757
+ * @returns The world as JSON: the mode, the entities and the resources.
758
+ * @example
759
+ * ```ts
760
+ * // A test asserts what the board holds after the first reconcile.
761
+ * app.world.ecs.snapshot();
762
+ * // { mode: "live", entities: [{ id: 1048576, index: 0, generation: 1,
763
+ * // owner: { kind: "projection", name: "board.items" },
764
+ * // components: { Layer: { name: "items" } }, skipped: [] }], resources: {} }
765
+ * ```
766
+ */
767
+ snapshot(): WorldSnapshot;
768
+ };
769
+ /**
770
+ * ecs methods injected into `projection`. Not public.
771
+ */
772
+ type EcsInternal = {
773
+ /**
774
+ * Registers a listener for `despawnOwnedBy`, so `projection` can drop the views of an owner
775
+ * that left.
776
+ *
777
+ * @param fn - Called with the owner whose entities were despawned.
778
+ * @returns The remover.
779
+ */
780
+ onOwnerLeft(fn: (owner: Owner) => void): () => void;
781
+ /**
782
+ * The owner of an entity, which is also the liveness check: a stale id has none. `projection`
783
+ * asks before it hands out a view handle for an entity another plugin owns.
784
+ *
785
+ * @param entity - The entity to ask about.
786
+ * @returns The owner, or `undefined` when the id is stale.
787
+ */
788
+ ownerOf(entity: Entity): Owner | undefined;
789
+ /**
790
+ * Runs the systems of one phase, then flushes the command buffer.
791
+ *
792
+ * @param phase - The phase to run.
793
+ * @param time - The frame's `Time`.
794
+ */
795
+ runPhase(phase: WorldPhase, time: Readonly<Time>): void;
796
+ /**
797
+ * Applies the queued structural commands in call order.
798
+ */
799
+ flush(): void;
800
+ /**
801
+ * Clears every change set. Called in `time` phase `signals`.
802
+ */
803
+ clearChanges(): void;
804
+ /**
805
+ * Drops every entity, component, resource, system and hook.
806
+ */
807
+ clear(): void;
808
+ };
809
+ //#endregion
810
+ //#region src/plugins/world/projection/types.d.ts
811
+ /**
812
+ * How a layer orders the entities drawn in it.
813
+ *
814
+ * @example
815
+ * ```ts
816
+ * const sort: LayerSort = "y";
817
+ * ```
818
+ */
819
+ type LayerSort = "none" | "y" | "order";
820
+ /**
821
+ * One named layer of a scene. The order of the list is draw order.
822
+ *
823
+ * @example
824
+ * ```ts
825
+ * const layer: LayerSpec = { name: "items", sort: "y" };
826
+ * ```
827
+ */
828
+ type LayerSpec = {
829
+ name: string;
830
+ sort: LayerSort;
831
+ };
832
+ /**
833
+ * Easing of a tween: one of the named curves the driver knows, or a function that gets the
834
+ * normalised time and returns the eased fraction.
835
+ *
836
+ * @example
837
+ * ```ts
838
+ * const ease: Ease = "outBack";
839
+ * ```
840
+ */
841
+ type Ease = "linear" | "in" | "out" | "inOut" | "inCubic" | "outCubic" | "inOutCubic" | "inBack" | "outBack" | ((t: number) => number);
842
+ /**
843
+ * What every motion returns: it can be finished, cancelled and asked whether it still runs.
844
+ * V3 `anim` hands out the same shape.
845
+ *
846
+ * @example
847
+ * ```ts
848
+ * const handle: MotionHandle = view.toRest(Transform);
849
+ * handle.active(); // true until the last frame wrote the exact target
850
+ * ```
851
+ */
852
+ type MotionHandle = {
853
+ finish(): void;
854
+ cancel(): void;
855
+ active(): boolean;
856
+ };
857
+ /**
858
+ * What a motion hook returns. `void` means the hook wrote the component directly.
859
+ *
860
+ * @example
861
+ * ```ts
862
+ * const motion: Motion = undefined; // the hook called view.set and is already done
863
+ * ```
864
+ */
865
+ type Motion = MotionHandle | void;
866
+ /**
867
+ * Only the numeric fields of a component value: what a tween can interpolate.
868
+ *
869
+ * @example
870
+ * ```ts
871
+ * type Numbers = NumericFields<{ x: number; texture: string }>; // { x: number }
872
+ * ```
873
+ */
874
+ type NumericFields<Value extends object> = { [Key in keyof Value as Value[Key] extends number ? Key : never]: Value[Key] };
875
+ /**
876
+ * One keyframe segment of a track: the fraction of the track's `ms` it ends at (`0..1`), the
877
+ * curve it eases with (the track's `ease` when left out) and the numeric fields it reaches. A
878
+ * field it does not name holds where the segment before left it.
879
+ *
880
+ * @example
881
+ * ```ts
882
+ * // The swing of a popup board: past the rest pose at 42 % of the track, then home.
883
+ * const segments: TrackSegment[] = [
884
+ * { at: 0.42, ease: "out", to: { y: 974, rotation: 0.087 } },
885
+ * { at: 1, ease: "inOut", to: { y: 960, rotation: 0 } }
886
+ * ];
887
+ * ```
888
+ */
889
+ type TrackSegment = {
890
+ at: number;
891
+ ease?: Ease;
892
+ to: Record<string, number>;
893
+ };
894
+ /**
895
+ * Options of `ViewHandle.tween`. An additive tween adds its delta over the value the absolute
896
+ * writer of the field holds, instead of owning the field. `segments` turns the tween into a
897
+ * keyframe walk: the segments run one after another over `ms` on one clock, and `to` is where the
898
+ * last one ends. `repeat` runs the walk again from its first segment when it ends: a number
899
+ * counts the extra runs, `"forever"` never ends. The world passes it to the driver unchanged;
900
+ * without a driver the target is written once.
901
+ *
902
+ * @example
903
+ * ```ts
904
+ * const options: TweenOptions = { ms: 350, ease: "out", delayMs: 40 };
905
+ * const walk: TweenOptions = { ms: 1000, segments: [{ at: 0.42, to: { y: 974 } }, { at: 1, to: { y: 960 } }] };
906
+ * const sway: TweenOptions = { ms: 2400, additive: true, repeat: "forever", segments: [{ at: 0.5, to: { rotation: 0.05 } }, { at: 1, to: { rotation: 0 } }] };
907
+ * ```
908
+ */
909
+ type TweenOptions = {
910
+ ms: number;
911
+ ease?: Ease;
912
+ delayMs?: number;
913
+ additive?: boolean;
914
+ segments?: readonly TrackSegment[];
915
+ repeat?: number | "forever";
916
+ };
917
+ /**
918
+ * Options of `ViewHandle.toRest`. Defaults: the plugin's `settleMs` and ease `"out"`. `delayMs`
919
+ * lets a hook wait, for example for a flight to land, before the view comes home.
920
+ *
921
+ * @example
922
+ * ```ts
923
+ * const options: RestOptions = { ms: 200, delayMs: 120 };
924
+ * ```
925
+ */
926
+ type RestOptions = {
927
+ ms?: number;
928
+ ease?: Ease;
929
+ delayMs?: number;
930
+ };
931
+ /**
932
+ * What the driver is told about one track: how long it runs, how it eases, how long it waits,
933
+ * whether it adds to the field or owns it, and the keyframe segments it walks. A track with
934
+ * segments walks them over `ms` on one clock and claims every field a segment names; its target
935
+ * `to` is the last segment's target. A track with `repeat` starts its walk again when it ends
936
+ * (a number counts the extra runs, `"forever"` never ends); the instant driver ignores it and
937
+ * writes the end pose once.
938
+ *
939
+ * @example
940
+ * ```ts
941
+ * const options: TrackOptions = { ms: 250, ease: "outBack", delayMs: 0, additive: false };
942
+ * const walk: TrackOptions = { ms: 1000, segments: [{ at: 0.42, to: { y: 974 } }, { at: 1, to: { y: 960 } }] };
943
+ * const twice: TrackOptions = { ms: 400, ease: "inOut", delayMs: 0, additive: true, repeat: 1 };
944
+ * ```
945
+ */
946
+ type TrackOptions = {
947
+ ms: number;
948
+ ease?: Ease;
949
+ delayMs?: number;
950
+ additive?: boolean;
951
+ segments?: readonly TrackSegment[];
952
+ repeat?: number | "forever";
953
+ };
954
+ /**
955
+ * The tween engine behind `ViewHandle.tween`, `toRest` and `all`. `anim` installs one with
956
+ * `setDriver`; without it the world writes every target at once.
957
+ *
958
+ * A track reads its start values when its delay ends, writes the exact target on its last frame
959
+ * and goes through `ecs.set`, so `changed()` sees it. Muted fields are never written, also not on
960
+ * `finish()`.
961
+ *
962
+ * @example
963
+ * ```ts
964
+ * // `anim` hands the world its own tween core in `onStart`.
965
+ * const driver: TweenDriver = { track: startTrack, cancelAll: cancelTracksOf };
966
+ * const off = ctx.require(worldPlugin).projection.setDriver(driver);
967
+ *
968
+ * off(); // `anim` stopped: the world writes instantly again
969
+ * ```
970
+ */
971
+ type TweenDriver = {
972
+ /**
973
+ * Starts one track on one component of one entity.
974
+ *
975
+ * @param entity - The entity to animate.
976
+ * @param component - The component to animate.
977
+ * @param to - The numeric target fields.
978
+ * @param options - Duration, easing, delay and the additive flag.
979
+ * @param muted - The fields another writer owns, read at every write.
980
+ * @returns The motion handle of the track.
981
+ */
982
+ track(entity: Entity, component: AnyComponent, to: Record<string, number>, options: TrackOptions, muted: () => ReadonlySet<string>): MotionHandle;
983
+ /**
984
+ * Ends every track of one entity. Called when a view despawns and on every flush.
985
+ *
986
+ * @param entity - The entity whose tracks end.
987
+ */
988
+ cancelAll(entity: Entity): void;
989
+ };
990
+ /**
991
+ * One node of an element description, as `ui` builds it. The world never imports `ui`: it knows
992
+ * the shape structurally, carries it in `Tree` and hands it back unchanged.
993
+ *
994
+ * @example
995
+ * ```ts
996
+ * const node: DescriptionNode = { type: "box", props: { gap: 8 }, children: [] };
997
+ * ```
998
+ */
999
+ type DescriptionNode = {
1000
+ type: string;
1001
+ key?: string;
1002
+ props: object;
1003
+ children: readonly unknown[];
1004
+ };
1005
+ /**
1006
+ * What one `view(item)` call returns: the components of the item, or one description node that
1007
+ * the world wraps as `Tree`.
1008
+ *
1009
+ * @example
1010
+ * ```ts
1011
+ * const components: ViewOutput = [Level({ level: 2 })];
1012
+ * ```
1013
+ */
1014
+ type ViewOutput = readonly AnyComponentValue[] | DescriptionNode;
1015
+ /**
1016
+ * What a motion hook is handed: the one view it animates. A view never holds a reference to
1017
+ * another view — `peer` answers with the other key's item, never with its entity.
1018
+ *
1019
+ * @example
1020
+ * ```ts
1021
+ * const enter = (view: ViewHandle<Item>, item: Item): Motion => {
1022
+ * view.set(Transform, { scale: 0 });
1023
+ * return view.toRest(Transform, { ms: 250 });
1024
+ * };
1025
+ * ```
1026
+ */
1027
+ type ViewHandle<Item> = {
1028
+ readonly entity: Entity;
1029
+ readonly key: string;
1030
+ get<Value extends object>(component: ComponentType<Value>): Readonly<Value> | undefined;
1031
+ rest<Value extends object>(component: ComponentType<Value>): Readonly<Value> | undefined;
1032
+ set<Value extends object>(component: ComponentType<Value>, patch: Partial<Value>): void;
1033
+ tween<Value extends object>(component: ComponentType<Value>, to: Partial<NumericFields<Value>>, options: TweenOptions): MotionHandle;
1034
+ toRest<Value extends object>(component: ComponentType<Value>, options?: RestOptions): MotionHandle;
1035
+ all(handles: readonly Motion[]): MotionHandle;
1036
+ peer(key: string): Item | undefined;
1037
+ };
1038
+ /**
1039
+ * One `motion.change` hook, keyed by component name. `previous` and `next` are both passed, so a
1040
+ * rollback that lowers a level can pick another motion than a level-up.
1041
+ *
1042
+ * @example
1043
+ * ```ts
1044
+ * const slide: ChangeHook<Item> = view => view.toRest(Transform, { ms: 350 });
1045
+ * ```
1046
+ */
1047
+ type ChangeHook<Item> = (view: ViewHandle<Item>, previous: Item, next: Item, hint?: Hint) => Motion;
1048
+ /**
1049
+ * The `change` table of a projection: one hook per component name.
1050
+ *
1051
+ * @example
1052
+ * ```ts
1053
+ * const change: ChangeHooks<Item> = { Transform: view => view.toRest(Transform) };
1054
+ * ```
1055
+ */
1056
+ type ChangeHooks<Item> = {
1057
+ readonly [component: string]: ChangeHook<Item>;
1058
+ };
1059
+ /**
1060
+ * The motion hooks of a projection. Every one is optional; without a hook the component diff is
1061
+ * written directly. `loop` starts what moves a view for its whole life (an additive sway, a spin):
1062
+ * it is played wherever `enter` plays, next to it, and its motion is kept apart from the view's
1063
+ * other motions, so a change never cancels it; it ends when the view dies.
1064
+ *
1065
+ * @example
1066
+ * ```ts
1067
+ * const motion: ProjectionMotion<Item> = {
1068
+ * change: { Transform: view => view.toRest(Transform, { ms: 350 }) }
1069
+ * };
1070
+ * // An order card that sways forever once it entered.
1071
+ * const swaying: ProjectionMotion<Item> = {
1072
+ * loop: view =>
1073
+ * view.tween(Transform, { rotation: 0.05 }, { ms: 1200, additive: true, repeat: "forever" })
1074
+ * };
1075
+ * ```
1076
+ */
1077
+ type ProjectionMotion<Item> = {
1078
+ enter?(view: ViewHandle<Item>, item: Item, hint?: Hint): Motion;
1079
+ loop?(view: ViewHandle<Item>): Motion;
1080
+ exit?(view: ViewHandle<Item>, item: Item, hint?: Hint): Motion;
1081
+ readonly change?: ChangeHooks<Item>;
1082
+ settle?(view: ViewHandle<Item>, components: readonly string[]): Motion;
1083
+ };
1084
+ /**
1085
+ * The motion hooks with the item type erased, as the world stores them.
1086
+ *
1087
+ * @example
1088
+ * ```ts
1089
+ * const stored: AnyProjectionMotion = { change: {} };
1090
+ * ```
1091
+ */
1092
+ type AnyProjectionMotion = {
1093
+ enter?(view: ViewHandle<unknown>, item: unknown, hint?: Hint): Motion;
1094
+ loop?(view: ViewHandle<unknown>): Motion;
1095
+ exit?(view: ViewHandle<unknown>, item: unknown, hint?: Hint): Motion;
1096
+ readonly change?: ChangeHooks<never>;
1097
+ settle?(view: ViewHandle<unknown>, components: readonly string[]): Motion;
1098
+ };
1099
+ /**
1100
+ * One projection: keyed items of the model turned into entities. `Item` is inferred from the
1101
+ * return of `from`; `layer` and `lift` keep their literal types so a scene can check them.
1102
+ *
1103
+ * @example
1104
+ * ```ts
1105
+ * const spec: ProjectionSpec<Item, "items", "lifted", Player, Session> = {
1106
+ * name: "board.items",
1107
+ * layer: "items",
1108
+ * lift: "lifted",
1109
+ * from: player => player.board.items,
1110
+ * key: item => item.id,
1111
+ * view: item => [Item({ level: item.level })]
1112
+ * };
1113
+ * ```
1114
+ */
1115
+ type ProjectionSpec<Item, LayerName extends string, LiftName extends string, Player, Session> = {
1116
+ readonly name: string;
1117
+ readonly layer: LayerName;
1118
+ readonly lift?: LiftName;
1119
+ from(player: Player, session: Session): readonly Item[] | Item; /** Omitted when `from` returns one plain object: the key is then the name of the projection. */
1120
+ key?(item: Item): string;
1121
+ view(item: Item): ViewOutput;
1122
+ readonly motion?: ProjectionMotion<Item>;
1123
+ };
1124
+ /**
1125
+ * A projection spec with its item and layer types erased, as the world stores it.
1126
+ *
1127
+ * @example
1128
+ * ```ts
1129
+ * const stored: AnyProjectionSpec = {
1130
+ * name: "board.items",
1131
+ * layer: "items",
1132
+ * from: () => [],
1133
+ * key: () => "",
1134
+ * view: () => []
1135
+ * };
1136
+ * ```
1137
+ */
1138
+ type AnyProjectionSpec = {
1139
+ readonly name: string;
1140
+ readonly layer: string;
1141
+ readonly lift?: string;
1142
+ from(player: Json, session: Json): unknown;
1143
+ key?(item: unknown): string;
1144
+ view(item: unknown): ViewOutput;
1145
+ readonly motion?: AnyProjectionMotion;
1146
+ };
1147
+ /**
1148
+ * Why a reconcile runs. Merged over the frame; the cause table decides play or direct.
1149
+ *
1150
+ * @example
1151
+ * ```ts
1152
+ * const cause: Cause = "edge";
1153
+ * ```
1154
+ */
1155
+ type Cause = "edge" | "rollback" | "restore" | "load" | "mount" | "rerun";
1156
+ /**
1157
+ * One live or exiting view: the entity of one key of one projection, its rest pose and the
1158
+ * motions that still run on it.
1159
+ *
1160
+ * @example
1161
+ * ```ts
1162
+ * const view: View = {
1163
+ * entity: 1_048_576,
1164
+ * projection: "board.items",
1165
+ * key: "i7",
1166
+ * item: { id: "i7" },
1167
+ * rest: new Map(),
1168
+ * handles: [],
1169
+ * lifted: false,
1170
+ * dropWhenStill: false,
1171
+ * exiting: false
1172
+ * };
1173
+ * ```
1174
+ */
1175
+ type View$1 = {
1176
+ entity: Entity;
1177
+ projection: string;
1178
+ key: string;
1179
+ item: unknown; /** The last output of `view(item)`, by component name: what the picture equals at rest. */
1180
+ rest: Map<string, AnyComponentValue>;
1181
+ handles: MotionHandle[];
1182
+ lifted: boolean; /** `lift(false)` is waiting for the last motion to end. */
1183
+ dropWhenStill: boolean;
1184
+ exiting: boolean;
1185
+ };
1186
+ /**
1187
+ * One mounted projection: its owner, the live views by key, the despawn queue and the items of
1188
+ * the last reconcile.
1189
+ *
1190
+ * @example
1191
+ * ```ts
1192
+ * const mounted: Mounted = {
1193
+ * owner: { kind: "projection", name: "board.items" },
1194
+ * live: new Map(),
1195
+ * queue: [],
1196
+ * items: new Map()
1197
+ * };
1198
+ * ```
1199
+ */
1200
+ type Mounted = {
1201
+ owner: Owner;
1202
+ live: Map<string, View$1>;
1203
+ queue: View$1[];
1204
+ items: Map<string, unknown>;
1205
+ };
1206
+ /**
1207
+ * One track the projection started on the driver. The world keeps the component name and the
1208
+ * handle, so it knows which components are driven without knowing how the driver runs them.
1209
+ *
1210
+ * @example
1211
+ * ```ts
1212
+ * const track: Track = { entity: 1_048_576, component: "Transform", handle: flight };
1213
+ * track.handle.active(); // true while the flight runs
1214
+ * ```
1215
+ */
1216
+ type Track = {
1217
+ entity: Entity;
1218
+ component: string;
1219
+ handle: MotionHandle;
1220
+ };
1221
+ /**
1222
+ * The rest pose of one entity, by component name: the last output of `view(item)` for a view, or
1223
+ * what `setRest` recorded for an element another plugin owns.
1224
+ */
1225
+ type RestPose = Map<string, AnyComponentValue>;
1226
+ /**
1227
+ * Where a keyed element lives: a plugin above registered it with `registerKey`, so `entityOf`
1228
+ * and `keyOf` answer for it next to the views.
1229
+ *
1230
+ * @example
1231
+ * ```ts
1232
+ * const key: ProjectionKey = { projection: "hud", key: "coins" };
1233
+ * ```
1234
+ */
1235
+ type ProjectionKey = {
1236
+ projection: string;
1237
+ key: string;
1238
+ };
1239
+ /**
1240
+ * What the next reconcile has to do: why it runs, which roots changed and whether `view` is
1241
+ * forced for every item.
1242
+ *
1243
+ * @example
1244
+ * ```ts
1245
+ * const dirty: Dirty = { causes: ["edge"], roots: new Set(["player"]), force: false };
1246
+ * ```
1247
+ */
1248
+ type Dirty = {
1249
+ causes: Cause[];
1250
+ roots: Set<Root>;
1251
+ force: boolean;
1252
+ };
1253
+ /**
1254
+ * projection module state.
1255
+ */
1256
+ type ProjectionState = {
1257
+ specs: Map<string, AnyProjectionSpec>;
1258
+ layers: readonly LayerSpec[];
1259
+ mounted: Map<string, Mounted>;
1260
+ byEntity: Map<Entity, View$1>;
1261
+ dirty: Dirty | undefined;
1262
+ hints: Hint[]; /** The tracks the projection started, with the handle that says whether they still run. */
1263
+ tracks: Track[]; /** The tween engine `anim` installed, or `undefined`: then every target is written at once. */
1264
+ driver: TweenDriver | undefined; /** The rest pose of an entity that is not a view, recorded by its owner through `setRest`. */
1265
+ rests: Map<Entity, RestPose>; /** Projection name to key to the entity a plugin above registered under it. */
1266
+ keys: Map<string, Map<string, Entity>>; /** The reverse of `keys`, so `keyOf` answers for a registered element too. */
1267
+ keysByEntity: Map<Entity, ProjectionKey>; /** Entity to component name to the fields another writer owns. */
1268
+ mutes: Map<Entity, Map<string, Set<string>>>;
1269
+ offHints: Array<() => void>;
1270
+ };
1271
+ /**
1272
+ * projection module API, `app.world.projection`: the bridge from the model to entities.
1273
+ *
1274
+ * @example
1275
+ * ```ts
1276
+ * // A test mounts the board and asks which entity carries one model key.
1277
+ * app.world.projection.mount(["board.items"], { kind: "plugin", name: "test" });
1278
+ * app.world.projection.entityOf("board.items", "i7"); // 1048576
1279
+ * ```
1280
+ */
1281
+ type ProjectionApi = {
1282
+ /**
1283
+ * Stores a projection spec. Nothing is drawn until `mount`.
1284
+ *
1285
+ * @param spec - The projection, built with `projection()`.
1286
+ * @throws {Error} When a projection of that name is already registered.
1287
+ * @example
1288
+ * ```ts
1289
+ * // A test registers one projection by hand instead of composing a feature.
1290
+ * app.world.projection.register(boardItems);
1291
+ * app.world.projection.entityOf("board.items", "i7"); // undefined: nothing is mounted yet
1292
+ * ```
1293
+ */
1294
+ register(spec: AnyProjectionSpec): void;
1295
+ /**
1296
+ * Stores the layer list of the scene. The order of the list is draw order; every call stores a
1297
+ * new frozen array.
1298
+ *
1299
+ * @param list - The layers, in draw order.
1300
+ * @example
1301
+ * ```ts
1302
+ * // `scenes` switches to the board scene.
1303
+ * const world = ctx.require(worldPlugin);
1304
+ * world.projection.setLayers([
1305
+ * { name: "board", sort: "none" },
1306
+ * { name: "items", sort: "y" },
1307
+ * { name: "lifted", sort: "none" }
1308
+ * ]);
1309
+ * ```
1310
+ */
1311
+ setLayers(list: ReadonlyArray<LayerSpec>): void;
1312
+ /**
1313
+ * The current layer list, empty before the first `setLayers`. A new list is a new reference, so
1314
+ * a reader compares by identity.
1315
+ *
1316
+ * @returns The layers, in draw order.
1317
+ * @example
1318
+ * ```ts
1319
+ * // `renderer.sync` rebuilds its containers only when the scene really changed.
1320
+ * const current = app.world.projection.layers();
1321
+ * if (current !== lastLayers) rebuildContainers(current); // [{ name: "board", sort: "none" }, ...]
1322
+ * ```
1323
+ */
1324
+ layers(): ReadonlyArray<LayerSpec>;
1325
+ /**
1326
+ * Marks projections mounted under an owner and reconciles them at once, direct: a scene brings
1327
+ * its own transition. Nothing is mounted when the call throws.
1328
+ *
1329
+ * @param names - Names of the projections to mount.
1330
+ * @param owner - Who owns the entities of these projections.
1331
+ * @throws {Error} For an unknown name, or a `layer` or `lift` the scene does not declare.
1332
+ * @example
1333
+ * ```ts
1334
+ * // `scenes` mounts the projections of the board scene it just switched to.
1335
+ * const world = ctx.require(worldPlugin);
1336
+ * world.projection.mount(["board.cells", "board.items"], { kind: "plugin", name: "scenes" });
1337
+ * world.projection.entityOf("board.items", "i7"); // 1048576, the picture is already there
1338
+ * ```
1339
+ */
1340
+ mount(names: readonly string[], owner: Owner): void;
1341
+ /**
1342
+ * Unmounts projections: every motion of their views is finished, and the live views and the
1343
+ * despawn queue are despawned in the same call.
1344
+ *
1345
+ * @param names - Names of the projections to unmount.
1346
+ * @example
1347
+ * ```ts
1348
+ * // `scenes` leaves the board scene.
1349
+ * const world = ctx.require(worldPlugin);
1350
+ * world.projection.unmount(["board.cells", "board.items"]);
1351
+ * world.projection.entityOf("board.items", "i7"); // undefined
1352
+ * ```
1353
+ */
1354
+ unmount(names: readonly string[]): void;
1355
+ /**
1356
+ * A drop with no commit: cancels the view's motions and plays settle for every loose component.
1357
+ * In mode `paused` or `fast` the rest pose is written at once. No-op for an entity that is not
1358
+ * a live view.
1359
+ *
1360
+ * @param entity - The entity of the view that was dropped.
1361
+ * @example
1362
+ * ```ts
1363
+ * // `input` released the finger over nothing, so the gate refused the answer.
1364
+ * const world = ctx.require(worldPlugin);
1365
+ * world.projection.settle(held); // the item glides back to its cell over settleMs
1366
+ * ```
1367
+ */
1368
+ settle(entity: Entity): void;
1369
+ /**
1370
+ * Hands a set of fields to another writer. Tracks never write them, also not on `finish()`.
1371
+ *
1372
+ * @param entity - The entity of the view.
1373
+ * @param component - The component whose fields are taken over.
1374
+ * @param fields - The field names the caller owns.
1375
+ * @returns The remover; safe after the entity left, and a no-op when called twice.
1376
+ * @example
1377
+ * ```ts
1378
+ * // `input` owns the position of the held view while the finger drags it.
1379
+ * const world = ctx.require(worldPlugin);
1380
+ * const release = world.projection.mute(held, Transform, ["x", "y"]);
1381
+ *
1382
+ * release(); // the finger let go
1383
+ * ```
1384
+ */
1385
+ mute(entity: Entity, component: ComponentType<object>, fields: readonly string[]): () => void;
1386
+ /**
1387
+ * Moves a view into or out of its projection's `lift` layer. `lift(entity, false)` on a view
1388
+ * that still moves takes effect when its last motion ends, so a settling view stays above the
1389
+ * board until it is home.
1390
+ *
1391
+ * @param entity - The entity of the view.
1392
+ * @param on - True to lift, false to drop back.
1393
+ * @example
1394
+ * ```ts
1395
+ * // `input` lifts the item the finger picked up so it draws above the board.
1396
+ * const world = ctx.require(worldPlugin);
1397
+ * world.projection.lift(held, true);
1398
+ *
1399
+ * world.projection.lift(held, false); // on release: takes effect when the settle motion ends
1400
+ * ```
1401
+ */
1402
+ lift(entity: Entity, on: boolean): void;
1403
+ /**
1404
+ * The projection and key of an entity. Also answers for a view in the despawn queue and for an
1405
+ * element registered with `registerKey`.
1406
+ *
1407
+ * @param entity - The entity to ask about.
1408
+ * @returns The projection name and the key, or `undefined`.
1409
+ * @example
1410
+ * ```ts
1411
+ * // `renderer.sync` labels its display objects for the inspector.
1412
+ * app.world.projection.keyOf(1_048_576); // { projection: "board.items", key: "i7" }
1413
+ * app.world.projection.keyOf(42); // undefined: not a view
1414
+ * ```
1415
+ */
1416
+ keyOf(entity: Entity): ProjectionKey | undefined;
1417
+ /**
1418
+ * The rest value of one component of an entity: what a view holds when nothing animates it, or
1419
+ * what `setRest` recorded for an element. Read-only and owner-free, so a plugin that owns no
1420
+ * entity can aim at one.
1421
+ *
1422
+ * @param entity - A projection view or a registered element.
1423
+ * @param component - The component whose rest value is asked.
1424
+ * @returns The rest value, or `undefined` when the entity has none recorded.
1425
+ * @example
1426
+ * ```ts
1427
+ * // `anim` builds a coin flight toward the counter: `at(target)` is the counter's rest pose.
1428
+ * const world = ctx.require(worldPlugin);
1429
+ * const counter = world.projection.entityOf("hud", "coins") ?? 0;
1430
+ *
1431
+ * world.projection.restOf(counter, Transform); // { x: 40, y: 120, rotation: 0, scale: 1, pivot: { x: 0, y: 0 } }
1432
+ * ```
1433
+ */
1434
+ restOf<Value extends object>(entity: Entity, component: ComponentType<Value>): Value | undefined;
1435
+ /**
1436
+ * The entity of one key of one projection: a live view, never the despawn queue, or an element
1437
+ * a plugin above registered under that key.
1438
+ *
1439
+ * @param projection - Name of the projection.
1440
+ * @param key - The model key.
1441
+ * @returns The entity, or `undefined`.
1442
+ * @example
1443
+ * ```ts
1444
+ * // `input` resolves the target of a scripted drag.
1445
+ * const world = ctx.require(worldPlugin);
1446
+ * world.projection.entityOf("board.items", "i7"); // 1048576
1447
+ * world.projection.entityOf("board.items", "gone"); // undefined
1448
+ * ```
1449
+ */
1450
+ entityOf(projection: string, key: string): Entity | undefined;
1451
+ /**
1452
+ * The live view entities of a mounted projection, in the order of its model keys. A view that
1453
+ * plays its exit is left out, and so is an element registered with `registerKey`. Every call
1454
+ * hands out a new array.
1455
+ *
1456
+ * @param name - Name of the projection.
1457
+ * @returns The entities, or `[]` when the projection is not mounted.
1458
+ * @example
1459
+ * ```ts
1460
+ * // `ui` hosts the board inside a slot and parents every board item to it on reconcile.
1461
+ * const world = ctx.require(worldPlugin);
1462
+ * world.projection.entitiesOf("board.items"); // [1048580, 1048581]
1463
+ * world.projection.entitiesOf("shop.items"); // []: not mounted
1464
+ * ```
1465
+ */
1466
+ entitiesOf(name: string): readonly Entity[];
1467
+ /**
1468
+ * Installs the tween engine every `ViewHandle.tween`, `toRest` and `all` then runs on. Without
1469
+ * one the world writes every target at once and hands back an inactive handle.
1470
+ *
1471
+ * @param driver - The engine that runs the tracks.
1472
+ * @returns The remover; it puts the instant writes back.
1473
+ * @example
1474
+ * ```ts
1475
+ * // `anim` installs its tween core when it starts and takes it back when it stops.
1476
+ * const world = ctx.require(worldPlugin);
1477
+ * ctx.state.removeDriver = world.projection.setDriver(createDriver(ctx, ctx.state));
1478
+ *
1479
+ * ctx.state.removeDriver(); // onStop: motions are instant again
1480
+ * ```
1481
+ */
1482
+ setDriver(driver: TweenDriver): () => void;
1483
+ /**
1484
+ * A view handle for an entity that is not a projection view, so an element another plugin owns
1485
+ * can be animated the same way. `set`, `get`, `tween`, `toRest` and `all` work; `rest` answers
1486
+ * what `setRest` recorded; `peer` answers `undefined`.
1487
+ *
1488
+ * @param entity - The entity of the element.
1489
+ * @param owner - Who is asking; without it only the kind of the owner is checked.
1490
+ * @returns The handle, or `undefined` for a projection view and for a foreign entity.
1491
+ * @example
1492
+ * ```ts
1493
+ * // `ui` plays the `motion` prop of an element it owns.
1494
+ * const world = ctx.require(worldPlugin);
1495
+ * const handle = world.projection.viewOf(button, { kind: "plugin", name: "ui" });
1496
+ *
1497
+ * handle?.tween(Transform, { scale: 1.1 }, { ms: 120, ease: "outBack" });
1498
+ * ```
1499
+ */
1500
+ viewOf(entity: Entity, owner: Owner): ViewHandle<unknown> | undefined;
1501
+ /**
1502
+ * Records what one component of an element looks like at rest, so `toRest` knows where home is.
1503
+ *
1504
+ * @param entity - The entity of the element.
1505
+ * @param component - The component whose rest value is recorded.
1506
+ * @param value - The value the element holds when nothing animates.
1507
+ * @example
1508
+ * ```ts
1509
+ * // `ui` laid the coin counter out and tells the world where it belongs.
1510
+ * const world = ctx.require(worldPlugin);
1511
+ * world.projection.setRest(counter, Transform, { x: 40, y: 120, rotation: 0, scale: 1, pivot: { x: 0, y: 0 } });
1512
+ *
1513
+ * world.projection.viewOf(counter, { kind: "plugin", name: "ui" })?.rest(Transform); // { x: 40, y: 120, rotation: 0, scale: 1, pivot: { x: 0, y: 0 } }
1514
+ * ```
1515
+ */
1516
+ setRest<Value extends object>(entity: Entity, component: ComponentType<Value>, value: Value): void;
1517
+ /**
1518
+ * Gives an entity a projection and key of its own, so `entityOf` and `keyOf` answer for it next
1519
+ * to the live views. That is how an element becomes a target for `anim`, `input` and `guide`.
1520
+ *
1521
+ * @param projection - Name the element is addressed under.
1522
+ * @param key - Key the element is addressed under.
1523
+ * @param entity - The entity of the element.
1524
+ * @returns The remover; it drops the key again.
1525
+ * @throws {Error} When a live view of that projection already holds the key.
1526
+ * @example
1527
+ * ```ts
1528
+ * // `ui` publishes the coin counter of the HUD.
1529
+ * const world = ctx.require(worldPlugin);
1530
+ * const drop = world.projection.registerKey("hud", "coins", counter);
1531
+ *
1532
+ * world.projection.entityOf("hud", "coins"); // the entity of the counter
1533
+ * drop(); // the element left the screen
1534
+ * ```
1535
+ */
1536
+ registerKey(projection: string, key: string, entity: Entity): () => void;
1537
+ /**
1538
+ * Marks every mounted projection dirty and forces `view` for every item, so the next frame
1539
+ * rebuilds the picture directly.
1540
+ *
1541
+ * @example
1542
+ * ```ts
1543
+ * // `i18n` switched the locale: every label has to be projected again.
1544
+ * const world = ctx.require(worldPlugin);
1545
+ * world.projection.rerunAll(); // the next frame writes the new strings with no motion
1546
+ * ```
1547
+ */
1548
+ rerunAll(): void;
1549
+ };
1550
+ /**
1551
+ * projection methods the plugin root drives from the frame and the hooks. Not public.
1552
+ */
1553
+ type ProjectionInternal = {
1554
+ /**
1555
+ * Records a commit. In fast mode it reconciles at once, direct.
1556
+ *
1557
+ * @param roots - The state roots the commit touched.
1558
+ * @param cause - Why the commit happened.
1559
+ */
1560
+ markDirty(roots: readonly Root[], cause: Cause): void;
1561
+ /**
1562
+ * Reconciles when the world is dirty. Runs at the start of `time` phase `input`.
1563
+ */
1564
+ reconcileIfDirty(): void;
1565
+ /**
1566
+ * Empties the hint buffer. Runs once per frame, reconcile or not.
1567
+ */
1568
+ dropHints(): void;
1569
+ /**
1570
+ * Buffers one released hint.
1571
+ *
1572
+ * @param hint - The hint `flow.fx` released after a commit.
1573
+ */
1574
+ pushHint(hint: Hint): void;
1575
+ /**
1576
+ * Sweeps the views whose motions ended: the despawn queue, the convergence check and the
1577
+ * pending drops. The tracks themselves are advanced by the driver.
1578
+ */
1579
+ sweep(): void;
1580
+ /**
1581
+ * Finishes every motion and flushes every despawn queue. Called when the mode turns `fast`.
1582
+ */
1583
+ flushAll(): void;
1584
+ /**
1585
+ * Drops the views of a projection whose owner despawned its entities.
1586
+ *
1587
+ * @param owner - The owner that left.
1588
+ */
1589
+ ownerLeft(owner: Owner): void;
1590
+ /**
1591
+ * Drops every view, track, mute and spec.
1592
+ */
1593
+ clear(): void;
1594
+ };
1595
+ declare namespace types_d_exports$1 {
1596
+ export { AnyComponentType, AnyComponentValue, AnyProjectionSpec, AnySystem, Api$1 as Api, Cause, ChangeHook, ComponentHandle, ComponentType, ComponentValue, Config$1 as Config, Deps$1 as Deps, DescriptionNode, Ease, EcsApi, EcsModule, Entity, EntitySnapshot, Events$1 as Events, KernelSlice$1 as KernelSlice, LayerSort, LayerSpec, ModelCommitted, Motion, MotionHandle, Mut, Narrowed, NumericFields, Owner, ProjectionApi, ProjectionKey, ProjectionModule, ProjectionMotion, ProjectionSpec, QueryTerm, QueryTuple, ResourceType, RestOptions, State$1 as State, SystemContext, SystemDefinition, TagType, TrackOptions, TrackSegment, TweenDriver, TweenOptions, ViewHandle, ViewOutput, WorldCtx, WorldMode, WorldPhase, WorldSnapshot };
1597
+ }
1598
+ /**
1599
+ * world plugin events.
1600
+ *
1601
+ * @example
1602
+ * ```ts
1603
+ * // A dev build counts what one reconcile did.
1604
+ * createPlugin("reconcileLog", {
1605
+ * depends: [worldPlugin],
1606
+ * hooks: ctx => ({ "world:reconciled": counts => ctx.log.debug("reconciled", counts) })
1607
+ * }); // one merge logs { mode: "play", entered: 0, changed: 1, exited: 1, ... }
1608
+ * ```
1609
+ */
1610
+ type Events$1 = {
1611
+ /** Dev only (config `reconciledEvent`). Counts of one reconcile. */"world:reconciled": {
1612
+ mode: "play" | "direct";
1613
+ projections: number;
1614
+ entered: number;
1615
+ changed: number;
1616
+ exited: number;
1617
+ revived: number;
1618
+ queued: number;
1619
+ hintsRouted: number;
1620
+ hintsDropped: number;
1621
+ };
1622
+ };
1623
+ /**
1624
+ * world plugin config.
1625
+ *
1626
+ * @example
1627
+ * ```ts
1628
+ * createApp({ pluginConfigs: { world: { settleMs: 250, reconciledEvent: true } } });
1629
+ * ```
1630
+ */
1631
+ type Config$1 = {
1632
+ /** Duration of the built-in settle motion, in game milliseconds. */settleMs: number; /** Emit `world:reconciled` after every reconcile. Off in production; tests and the editor turn it on. */
1633
+ reconciledEvent: boolean;
1634
+ };
1635
+ /**
1636
+ * world plugin state: one branch per module.
1637
+ */
1638
+ type State$1 = {
1639
+ ecs: EcsState;
1640
+ projection: ProjectionState;
1641
+ };
1642
+ /**
1643
+ * world plugin API, `app.world`, grouped by module.
1644
+ *
1645
+ * @example
1646
+ * ```ts
1647
+ * app.world.ecs.mode(); // "live"
1648
+ * app.world.projection.entityOf("board.items", "i7"); // 1048576
1649
+ * ```
1650
+ */
1651
+ type Api$1 = {
1652
+ ecs: EcsApi;
1653
+ projection: ProjectionApi;
1654
+ };
1655
+ /**
1656
+ * Resolved dependency APIs.
1657
+ */
1658
+ type Deps$1 = {
1659
+ time: Api$2;
1660
+ model: Api$3;
1661
+ flow: Api$4;
1662
+ };
1663
+ /**
1664
+ * What the kernel context offers before the deps are attached.
1665
+ *
1666
+ * `index.ts` writes `events` with an annotated `register` (core spec `14-EVENT-REGISTRATION.md`
1667
+ * row 8), so the own event reaches the context the kernel hands the factories and `emit` is the
1668
+ * kernel's own, world-typed one. No member of the context is cast.
1669
+ */
1670
+ type KernelSlice$1 = PluginCtx<Config$1, State$1, Events$1> & {
1671
+ readonly global: object;
1672
+ readonly log: Log.LogApi;
1673
+ readonly require: Require;
1674
+ };
1675
+ /**
1676
+ * Domain context shared by the two modules.
1677
+ */
1678
+ type WorldCtx = KernelSlice$1 & {
1679
+ readonly deps: Deps$1;
1680
+ };
1681
+ /**
1682
+ * Payload of the one event world listens to.
1683
+ */
1684
+ type ModelCommitted = Events$2["model:committed"];
1685
+ /**
1686
+ * Both halves of the `ecs` module: what a game calls and what `projection` gets injected.
1687
+ */
1688
+ type EcsModule = EcsApi & EcsInternal;
1689
+ /**
1690
+ * Both halves of the `projection` module: what a game calls and what the frame drives.
1691
+ */
1692
+ type ProjectionModule = ProjectionApi & ProjectionInternal;
1693
+ //#endregion
1694
+ //#region src/plugins/renderer/host/types.d.ts
1695
+ /**
1696
+ * host module state.
1697
+ */
1698
+ type HostState = {
1699
+ /** The Pixi module object, once `config.loadPixi()` resolved. */pixi: PixiModule | undefined;
1700
+ app: PixiApplication | undefined;
1701
+ canvas: HTMLCanvasElement | undefined; /** Where the canvas was appended. Read by `viewport` for its observer and its measurements. */
1702
+ mount: HTMLElement | undefined;
1703
+ kind: RendererKind;
1704
+ ready: boolean; /** True from the moment a device is lost until the restore attempt ends. */
1705
+ restoring: boolean; /** `viewport` and `sync` subscribe here; run once, after the first successful init. */
1706
+ onReady: Array<() => void>; /** Run after a restore created a new application, where `onReady` must not run twice. */
1707
+ onRestore: Array<() => void>; /** Run before the lost application is destroyed, while its display objects are still alive. */
1708
+ onLoss: Array<() => void>;
1709
+ cleanups: Array<() => void>;
1710
+ unsupported: HTMLElement | undefined;
1711
+ };
1712
+ /**
1713
+ * host module API, `app.renderer.host`. Everything a caller needs to know about the one Pixi
1714
+ * application: whether it draws, what it draws with, and which canvas it draws on.
1715
+ *
1716
+ * @example
1717
+ * ```ts
1718
+ * // A game shows a DOM fallback while the renderer is inert or on the unsupported screen.
1719
+ * if (!app.renderer.host.ready()) showFallback();
1720
+ * app.renderer.host.kind(); // "webgpu"
1721
+ * ```
1722
+ */
1723
+ type HostApi = {
1724
+ /**
1725
+ * Tells whether the renderer draws. False while inert, while the device is lost and on the
1726
+ * unsupported-device screen.
1727
+ *
1728
+ * @returns True after a successful init.
1729
+ * @example
1730
+ * ```ts
1731
+ * // `assets` skips the texture upload of a bundle while nothing can draw it.
1732
+ * const renderer = ctx.require(rendererPlugin);
1733
+ * if (!renderer.host.ready()) return; // headless: the bundle stays on disk
1734
+ * ```
1735
+ */
1736
+ ready(): boolean;
1737
+ /**
1738
+ * The backend Pixi chose, read once from the application after init.
1739
+ *
1740
+ * @returns `"webgpu"`, `"webgl"`, or `"none"` while inert or unsupported.
1741
+ * @example
1742
+ * ```ts
1743
+ * // A game picks the WGSL or the GLSL source of its own filter.
1744
+ * const source = app.renderer.host.kind() === "webgpu" ? wgsl : glsl; // "webgpu" in Chrome
1745
+ * ```
1746
+ */
1747
+ kind(): RendererKind;
1748
+ /**
1749
+ * The canvas of the Pixi application. A WebGPU restore makes a NEW canvas, so a caller that
1750
+ * holds listeners compares this with its stored canvas every frame.
1751
+ *
1752
+ * @returns The canvas, or `undefined` while inert and on the unsupported screen.
1753
+ * @example
1754
+ * ```ts
1755
+ * // `input` re-attaches its pointer listeners when a restore replaced the canvas.
1756
+ * const canvas = ctx.require(rendererPlugin).host.canvas();
1757
+ * if (canvas !== undefined && canvas !== attached) attachPointerListeners(canvas);
1758
+ * ```
1759
+ */
1760
+ canvas(): HTMLCanvasElement | undefined;
1761
+ /**
1762
+ * The Pixi module the renderer loaded lazily, so a plugin above draws with the same Pixi and
1763
+ * still imports none of it.
1764
+ *
1765
+ * @returns The module once `ready()`, `undefined` while inert, lost or unsupported.
1766
+ * @example
1767
+ * ```ts
1768
+ * // `text` builds the display object of a label out of the module the renderer loaded.
1769
+ * const pixi = ctx.require(rendererPlugin).host.pixi();
1770
+ * if (pixi === undefined) return; // headless: nothing is drawn
1771
+ *
1772
+ * const line = new pixi.BitmapText({ text: "+5", style: { fontFamily: "hud.body" } });
1773
+ * ```
1774
+ */
1775
+ pixi(): PixiModule | undefined;
1776
+ };
1777
+ /**
1778
+ * The textures the GPU holds: how many sources, and their estimated bytes.
1779
+ *
1780
+ * @example
1781
+ * ```ts
1782
+ * const usage: TextureUsage = { count: 2, bytes: 5_242_880 };
1783
+ * ```
1784
+ */
1785
+ type TextureUsage = {
1786
+ count: number;
1787
+ bytes: number;
1788
+ };
1789
+ /**
1790
+ * host methods injected into `viewport`, `sync` and `monitor`, and driven by the plugin root. Not
1791
+ * public.
1792
+ */
1793
+ type HostInternal = {
1794
+ /**
1795
+ * Subscribes to the first successful init. Callbacks run in subscription order: `viewport`
1796
+ * first, `sync` second.
1797
+ *
1798
+ * @param fn - Called once, after `ready` turned true.
1799
+ */
1800
+ onReady(fn: () => void): void;
1801
+ /**
1802
+ * Subscribes to a successful restore, where a new application and a new canvas exist but the
1803
+ * one-time registrations of `onReady` must not run again.
1804
+ *
1805
+ * @param fn - Called after every successful restore.
1806
+ */
1807
+ onRestore(fn: () => void): void;
1808
+ /**
1809
+ * Subscribes to the moment just before a lost application is destroyed. This is the last point
1810
+ * at which a display object the game owns can be saved out of the tree.
1811
+ *
1812
+ * @param fn - Called before every `destroy` of a lost application.
1813
+ */
1814
+ onLoss(fn: () => void): void;
1815
+ /**
1816
+ * The stage of the application. `sync` hangs its root container here.
1817
+ *
1818
+ * @returns The stage, or `undefined` while inert.
1819
+ */
1820
+ stage(): PixiContainer | undefined;
1821
+ /**
1822
+ * Where the canvas was appended. `viewport` observes and measures it.
1823
+ *
1824
+ * @returns The mount element, or `undefined` while inert.
1825
+ */
1826
+ mount(): HTMLElement | undefined;
1827
+ /**
1828
+ * Resizes the renderer to a CSS-pixel size. A no-op while nothing draws.
1829
+ *
1830
+ * @param width - New CSS width of the canvas.
1831
+ * @param height - New CSS height of the canvas.
1832
+ */
1833
+ resize(width: number, height: number): void;
1834
+ /**
1835
+ * Draws one frame. A no-op while nothing draws, so a lost device costs nothing.
1836
+ */
1837
+ render(): void;
1838
+ /**
1839
+ * Draws the stage into a PNG over the whole canvas. A failed read is logged.
1840
+ *
1841
+ * @returns A PNG data URL, or `undefined` while nothing draws or when Pixi could not read it.
1842
+ */
1843
+ extract(): Promise<string | undefined>;
1844
+ /**
1845
+ * The texture sources the GPU holds and their estimated memory, read at call time.
1846
+ *
1847
+ * @returns The usage; zero while nothing draws.
1848
+ */
1849
+ textures(): TextureUsage;
1850
+ /**
1851
+ * Runs the init sequence: resolve the mount, load Pixi, create the application, append the
1852
+ * canvas, then run the `onReady` callbacks. A failure shows the unsupported-device screen.
1853
+ *
1854
+ * @returns Resolves when the application is up, or when the plugin decided to stay inert.
1855
+ */
1856
+ init(): Promise<void>;
1857
+ };
1858
+ //#endregion
1859
+ //#region src/plugins/renderer/viewport/types.d.ts
1860
+ /**
1861
+ * The orientation a game is designed for, read from the framework config.
1862
+ *
1863
+ * @example
1864
+ * ```ts
1865
+ * const orientation: Orientation = "portrait";
1866
+ * ```
1867
+ */
1868
+ type Orientation = Config$2["orientation"];
1869
+ /**
1870
+ * A point in reference units.
1871
+ *
1872
+ * @example
1873
+ * ```ts
1874
+ * const point: Point = { x: 540, y: 960 };
1875
+ * ```
1876
+ */
1877
+ type Point = {
1878
+ x: number;
1879
+ y: number;
1880
+ };
1881
+ /**
1882
+ * A rectangle in CSS pixels inside the canvas.
1883
+ *
1884
+ * @example
1885
+ * ```ts
1886
+ * const frame: Rect = { x: 555, y: 0, width: 810, height: 1080 };
1887
+ * ```
1888
+ */
1889
+ type Rect = {
1890
+ x: number;
1891
+ y: number;
1892
+ width: number;
1893
+ height: number;
1894
+ };
1895
+ /**
1896
+ * The part of the frame a notch, a home bar or a rounded corner covers, in reference units.
1897
+ *
1898
+ * @example
1899
+ * ```ts
1900
+ * const safeArea: SafeArea = { top: 47, right: 0, bottom: 34, left: 0 };
1901
+ * ```
1902
+ */
1903
+ type SafeArea = {
1904
+ top: number;
1905
+ right: number;
1906
+ bottom: number;
1907
+ left: number;
1908
+ };
1909
+ /**
1910
+ * What a game lays its board out in: the drawn frame in reference units.
1911
+ *
1912
+ * @example
1913
+ * ```ts
1914
+ * const size: ViewportSize = {
1915
+ * width: 1080, height: 1920, scale: 0.5625, orientation: "portrait",
1916
+ * safeArea: { top: 47, right: 0, bottom: 34, left: 0 }
1917
+ * };
1918
+ * ```
1919
+ */
1920
+ type ViewportSize = {
1921
+ width: number;
1922
+ height: number;
1923
+ scale: number;
1924
+ orientation: Orientation;
1925
+ safeArea: SafeArea;
1926
+ };
1927
+ /**
1928
+ * viewport module state.
1929
+ */
1930
+ type ViewportState = {
1931
+ /** The drawn rectangle inside the canvas, in CSS pixels. Bars fill the rest. */frame: Rect; /** CSS pixels per reference unit. */
1932
+ scale: number;
1933
+ /**
1934
+ * The frame in reference units: the short side is at least `referenceSide`, the long side inside
1935
+ * the safe area at least `referenceLong`.
1936
+ */
1937
+ reference: {
1938
+ width: number;
1939
+ height: number;
1940
+ };
1941
+ safeArea: SafeArea; /** The observer saw a new size; the `render` callback applies it before drawing. */
1942
+ resizePending: boolean; /** The hidden element whose padding carries `env(safe-area-inset-*)`. */
1943
+ probe: HTMLElement | undefined;
1944
+ cleanups: Array<() => void>;
1945
+ };
1946
+ /**
1947
+ * viewport module API, `app.renderer.viewport`. The map between the window and the reference
1948
+ * space. Both sides fit: the short side holds `referenceSide`, the long side inside the safe area
1949
+ * holds `referenceLong`, and the smaller scale wins, so a wide screen gets a wider reference space.
1950
+ *
1951
+ * @example
1952
+ * ```ts
1953
+ * // A portrait game in a 1920x1080 window: an 810x1080 frame with bars left and right, and the
1954
+ * // 1920 reference long side decides the scale.
1955
+ * app.renderer.viewport.size(); // { width: 1440, height: 1920, scale: 0.5625, ... }
1956
+ * app.renderer.viewport.toReference(960, 540); // { x: 720, y: 960 }
1957
+ * ```
1958
+ */
1959
+ type ViewportApi = {
1960
+ /**
1961
+ * Maps a pointer event to reference units: `(client − canvas rect − frame offset) / scale`.
1962
+ * The canvas rectangle is read at call time, so a scrolled or animated page still answers right.
1963
+ *
1964
+ * @param clientX - `event.clientX` of a pointer event.
1965
+ * @param clientY - `event.clientY` of a pointer event.
1966
+ * @returns The point in reference units. Inert: the input unchanged.
1967
+ * @example
1968
+ * ```ts
1969
+ * // `input` turns every pointer event into reference units before it hit-tests.
1970
+ * const renderer = ctx.require(rendererPlugin);
1971
+ * const point = renderer.viewport.toReference(event.clientX, event.clientY);
1972
+ * renderer.sync.hitTest(point.x, point.y, isLiveDraggable);
1973
+ * ```
1974
+ */
1975
+ toReference(clientX: number, clientY: number): Point;
1976
+ /**
1977
+ * Maps a point in reference units to client CSS pixels, the coordinates of `event.clientX`:
1978
+ * `canvas rect + frame offset + point × scale`. The inverse of `toReference`; the canvas
1979
+ * rectangle is read at call time.
1980
+ *
1981
+ * @param point - A point in reference units.
1982
+ * @returns The point in client CSS pixels, a fresh object. Inert: the same numbers, reference
1983
+ * units, since there is no canvas to place them on.
1984
+ * @example
1985
+ * ```ts
1986
+ * // The `game.rect` source of the editor places Home's Play plank on the page. A 390x844
1987
+ * // phone, the canvas at the top left: the scale is 390 / 1080.
1988
+ * const renderer = ctx.require(rendererPlugin);
1989
+ * renderer.viewport.toScreen({ x: 540, y: 960 }); // { x: 195, y: 346.666… }
1990
+ * ```
1991
+ */
1992
+ toScreen(point: Point): Point;
1993
+ /**
1994
+ * The drawn frame in reference units, as a fresh object.
1995
+ *
1996
+ * @returns Width, height, scale, designed orientation and the safe area.
1997
+ * @example
1998
+ * ```ts
1999
+ * // A board keeps its bottom row above the home bar of the phone.
2000
+ * const { height, safeArea } = app.renderer.viewport.size();
2001
+ * const bottom = height - safeArea.bottom; // 1920 - 34
2002
+ * ```
2003
+ */
2004
+ size(): ViewportSize;
2005
+ };
2006
+ /**
2007
+ * viewport methods injected into `sync` and driven by the plugin root. Not public.
2008
+ */
2009
+ type ViewportInternal = {
2010
+ /**
2011
+ * Writes the frame offset and the scale onto the root container, so root-local coordinates ARE
2012
+ * reference coordinates.
2013
+ *
2014
+ * @param root - The root container of `sync`.
2015
+ */
2016
+ apply(root: PixiContainer): void;
2017
+ /**
2018
+ * Creates the safe-area probe and the resize observer, and measures once.
2019
+ */
2020
+ start(): void;
2021
+ /**
2022
+ * Applies a pending resize: one per frame, before drawing.
2023
+ *
2024
+ * @returns True when the frame changed, so `sync` writes the root transform again.
2025
+ */
2026
+ applyPending(): boolean;
2027
+ };
2028
+ //#endregion
2029
+ //#region src/plugins/renderer/sync/types.d.ts
2030
+ /**
2031
+ * Which component gave an entity its display object. `"adapter"` is a component a plugin above
2032
+ * registered with `displays.provide`.
2033
+ *
2034
+ * @example
2035
+ * ```ts
2036
+ * const kind: ViewKind = "NineSlice";
2037
+ * ```
2038
+ */
2039
+ type ViewKind = "Sprite" | "NineSlice" | "Shape" | "Display" | "adapter";
2040
+ /**
2041
+ * How a component of a plugin above becomes a display object. The renderer parents, orders and
2042
+ * frees the object like a sprite; the plugin decides what it is.
2043
+ *
2044
+ * @example
2045
+ * ```ts
2046
+ * const adapter: DisplayAdapter<{ resolved: string }> = {
2047
+ * create: value => buildRuns(value.resolved),
2048
+ * update: (object, previous, next) => {
2049
+ * if (previous.resolved !== next.resolved) rebuild(object, next.resolved);
2050
+ * },
2051
+ * destroy: object => (object as Container).destroy({ children: true, texture: false })
2052
+ * };
2053
+ * ```
2054
+ */
2055
+ type DisplayAdapter<Value extends object = object> = {
2056
+ /**
2057
+ * Builds the display object of one entity. Called on the first pass after the component
2058
+ * appeared, and never while the renderer is inert.
2059
+ *
2060
+ * @param value - The component value.
2061
+ * @param entity - The entity it sits on.
2062
+ * @returns A Pixi container.
2063
+ */
2064
+ create(value: Readonly<Value>, entity: Entity): unknown;
2065
+ /**
2066
+ * Writes a changed component value onto the object the adapter built.
2067
+ *
2068
+ * @param object - What `create` returned.
2069
+ * @param previous - The value of the last pass.
2070
+ * @param next - The value now.
2071
+ */
2072
+ update(object: unknown, previous: Readonly<Value>, next: Readonly<Value>): void;
2073
+ /**
2074
+ * Frees the object. Called when the component or the entity leaves, and when the renderer stops.
2075
+ *
2076
+ * @param object - What `create` returned.
2077
+ */
2078
+ destroy(object: unknown): void;
2079
+ };
2080
+ /**
2081
+ * One registration of `displays.provide`: the component, its adapter and the world hooks that
2082
+ * watch the component.
2083
+ */
2084
+ type DisplayEntry = {
2085
+ component: ComponentHandle<object>;
2086
+ adapter: DisplayAdapter; /** The `onAdded` and `onRemoved` removers of this registration. */
2087
+ removers: Array<() => void>;
2088
+ };
2089
+ /**
2090
+ * The rectangle a hit test checks, in the local space of the view: anchor already applied.
2091
+ *
2092
+ * @example
2093
+ * ```ts
2094
+ * const box: HitBox = { x: -32, y: -32, width: 64, height: 64 };
2095
+ * ```
2096
+ */
2097
+ type HitBox = {
2098
+ x: number;
2099
+ y: number;
2100
+ width: number;
2101
+ height: number;
2102
+ };
2103
+ /**
2104
+ * What `sync` knows about one drawn entity.
2105
+ *
2106
+ * @example
2107
+ * ```ts
2108
+ * const view: View = {
2109
+ * object: sprite, kind: "Sprite", poolKey: "Sprite:board.cell", layer: "items",
2110
+ * textureKey: "board.cell", wrapper: undefined, placeholder: false, mask: undefined,
2111
+ * display: undefined, value: undefined, drawScale: { x: 1, y: 1 }, frameKey: "",
2112
+ * outline: undefined, hitBox: { x: -32, y: -32, width: 64, height: 64 }
2113
+ * };
2114
+ * ```
2115
+ */
2116
+ type View = {
2117
+ object: PixiContainer;
2118
+ kind: ViewKind; /** `kind + ":" + texture key`, the pool this object goes back to. */
2119
+ poolKey: string; /** Name of the layer container the object hangs in, or `""` while it is parented. */
2120
+ layer: string;
2121
+ textureKey: string; /** The container that holds this entity's children, created when it is first named a parent. */
2122
+ wrapper: PixiContainer | undefined; /** No provider answered: the view draws the 64x64 magenta square until a bundle arrives. */
2123
+ placeholder: boolean; /** The rectangle a `clip: true` shape masks its children with. */
2124
+ mask: PixiGraphics | undefined; /** The registration that built the object, for a component a plugin above draws its own way. */
2125
+ display: DisplayEntry | undefined; /** A copy of the component value the adapter last saw, so `update` gets an honest `previous`. */
2126
+ value: Readonly<object> | undefined;
2127
+ /**
2128
+ * How much the object is stretched from its texture to the box it draws, per axis: box over
2129
+ * texture for a sized sprite, 64 for the 1x1 placeholder, 1 for everything else.
2130
+ */
2131
+ drawScale: Point; /** The crop a `"cover"` sprite shows, as its `frames` key; `""` when it shows none. */
2132
+ frameKey: string; /** The debug outline of a nine-slice, in its wrapper; `undefined` while debug is off. */
2133
+ outline: PixiGraphics | undefined;
2134
+ hitBox: HitBox;
2135
+ };
2136
+ /**
2137
+ * The crop of a `"cover"` sprite: a texture that shows the part of `base` covering one box.
2138
+ * Cached by texture key and box size, shared by the entities in `users`, and freed when the last
2139
+ * of them lets go or when its base is destroyed.
2140
+ *
2141
+ * @example
2142
+ * ```ts
2143
+ * const frame: CoverFrame = { base: meadow, texture: meadowCut, users: new Set([1_048_576]) };
2144
+ * ```
2145
+ */
2146
+ type CoverFrame = {
2147
+ base: PixiTexture;
2148
+ texture: PixiTexture;
2149
+ users: Set<Entity>;
2150
+ };
2151
+ /**
2152
+ * One layer of the scene: its container and the sort rule its children follow.
2153
+ *
2154
+ * @example
2155
+ * ```ts
2156
+ * const entry: LayerEntry = { container: itemsContainer, sort: "y" };
2157
+ * ```
2158
+ */
2159
+ type LayerEntry = {
2160
+ container: PixiContainer | undefined;
2161
+ sort: LayerSort;
2162
+ };
2163
+ /**
2164
+ * A texture source `assets` registered.
2165
+ *
2166
+ * @example
2167
+ * ```ts
2168
+ * const provider: TextureProvider = key => loaded.get(key);
2169
+ * ```
2170
+ */
2171
+ type TextureProvider = (key: string) => PixiTexture | undefined;
2172
+ /**
2173
+ * Nine-slice borders in pixels: left, top, right, bottom.
2174
+ *
2175
+ * @example
2176
+ * ```ts
2177
+ * const borders: NineBorders = [12, 12, 12, 12];
2178
+ * ```
2179
+ */
2180
+ type NineBorders = readonly [number, number, number, number];
2181
+ /**
2182
+ * Options of `textures.create`.
2183
+ *
2184
+ * @example
2185
+ * ```ts
2186
+ * const options: CreateTextureOptions = { nine: [12, 12, 12, 12] };
2187
+ * ```
2188
+ */
2189
+ type CreateTextureOptions = {
2190
+ nine?: NineBorders;
2191
+ };
2192
+ /**
2193
+ * The texture registry `assets` drives, `app.renderer.sync.textures`. The renderer makes and
2194
+ * destroys Pixi textures on request; `assets` owns when that happens.
2195
+ *
2196
+ * @example
2197
+ * ```ts
2198
+ * // `assets` decoded a file, hands the bitmap over and answers for the key from then on.
2199
+ * const texture = app.renderer.sync.textures.create(bitmap);
2200
+ * app.renderer.sync.textures.invalidate(["board.cell"]);
2201
+ * ```
2202
+ */
2203
+ type TexturesApi = {
2204
+ /**
2205
+ * Adds a provider to the chain. The newest provider is asked first.
2206
+ *
2207
+ * @param fn - Answers a texture for an asset key, or `undefined`.
2208
+ * @returns The remover; calling it twice is a no-op.
2209
+ * @example
2210
+ * ```ts
2211
+ * // `assets` publishes its loaded bundles in onStart and takes them back in onStop.
2212
+ * const renderer = ctx.require(rendererPlugin);
2213
+ * const off = renderer.sync.textures.provide(key => ctx.state.loaded.get(key));
2214
+ *
2215
+ * off(); // `assets` stops
2216
+ * ```
2217
+ */
2218
+ provide(fn: TextureProvider): () => void;
2219
+ /**
2220
+ * Makes a Pixi texture from a decoded image, so `assets` never imports Pixi.
2221
+ *
2222
+ * @param image - The decoded image.
2223
+ * @param options - `nine` is left, top, right and bottom in pixels; it becomes the texture's
2224
+ * default nine-slice borders.
2225
+ * @returns The new texture.
2226
+ * @throws {Error} When the renderer does not draw.
2227
+ * @example
2228
+ * ```ts
2229
+ * // `assets` uploads one file of a bundle it just decoded.
2230
+ * const renderer = ctx.require(rendererPlugin);
2231
+ * const bitmap = await createImageBitmap(await response.blob());
2232
+ * const texture = renderer.sync.textures.create(bitmap, { nine: [12, 12, 12, 12] });
2233
+ * ```
2234
+ */
2235
+ create(image: ImageBitmap | HTMLImageElement, options?: CreateTextureOptions): PixiTexture;
2236
+ /**
2237
+ * Destroys a texture and its source. Destroying the same texture twice is a no-op.
2238
+ *
2239
+ * @param texture - The texture to free.
2240
+ * @example
2241
+ * ```ts
2242
+ * // `assets` unloads a bundle: first the views let go, then the GPU memory is freed.
2243
+ * const renderer = ctx.require(rendererPlugin);
2244
+ * renderer.sync.textures.invalidate(["board.cell"]);
2245
+ * renderer.sync.textures.destroy(texture);
2246
+ * ```
2247
+ */
2248
+ destroy(texture: PixiTexture): void;
2249
+ /**
2250
+ * Marks asset keys stale. Every view that uses one resolves again in the next pass, and the
2251
+ * pooled objects of these keys are destroyed at once.
2252
+ *
2253
+ * @param keys - The asset keys a bundle brought or took away.
2254
+ * @example
2255
+ * ```ts
2256
+ * // A lazy bundle arrived: the magenta placeholders become the art on the next frame.
2257
+ * const renderer = ctx.require(rendererPlugin);
2258
+ * renderer.sync.textures.invalidate(["board.chain-1", "board.chain-2"]);
2259
+ * ```
2260
+ */
2261
+ invalidate(keys: readonly string[]): void;
2262
+ };
2263
+ /**
2264
+ * The display registry a plugin above extends, `app.renderer.sync.displays`. `Sprite`,
2265
+ * `NineSlice` and `Shape` are built in; everything else arrives as an adapter.
2266
+ *
2267
+ * @example
2268
+ * ```ts
2269
+ * // `text` teaches the renderer to draw its own component, and takes it back in onStop.
2270
+ * const off = ctx.require(rendererPlugin).sync.displays.provide(Text, textAdapter);
2271
+ * ```
2272
+ */
2273
+ type DisplaysApi = {
2274
+ /**
2275
+ * Registers how a component becomes a display object. The renderer calls `create` on the first
2276
+ * pass after the component appeared, `update` on every change, and `destroy` when it leaves.
2277
+ *
2278
+ * @param component - The component the plugin owns.
2279
+ * @param adapter - How that component is built, written and freed.
2280
+ * @returns The remover; calling it twice is a no-op. The views built with it stay until their
2281
+ * entities leave or the renderer stops.
2282
+ * @example
2283
+ * ```ts
2284
+ * // `text` draws its labels with BitmapText, and stops drawing them when it stops.
2285
+ * const renderer = ctx.require(rendererPlugin);
2286
+ * const off = renderer.sync.displays.provide(Text, createTextAdapter(ctx));
2287
+ *
2288
+ * off(); // `text` stops: the renderer builds no new label
2289
+ * ```
2290
+ */
2291
+ provide<Value extends object>(component: ComponentHandle<Value>, adapter: DisplayAdapter<Value>): () => void;
2292
+ };
2293
+ /**
2294
+ * The bitmap fonts of the one Pixi application, `app.renderer.sync.fonts`. `assets` loads the
2295
+ * files, `text` hands them over, the renderer owns the installed font.
2296
+ *
2297
+ * @example
2298
+ * ```ts
2299
+ * // `text` installs the font of a bundle that just landed, once.
2300
+ * const renderer = ctx.require(rendererPlugin);
2301
+ * const font = ctx.require(assetsPlugin).font("hud.body");
2302
+ *
2303
+ * if (font !== undefined && !renderer.sync.fonts.installed("hud.body")) {
2304
+ * renderer.sync.fonts.install("hud.body", font.fnt, font.texture);
2305
+ * }
2306
+ * ```
2307
+ */
2308
+ type FontsApi = {
2309
+ /**
2310
+ * Installs a BMFont file and its page texture under an asset key, so a `BitmapText` with that
2311
+ * key as its `fontFamily` draws with it.
2312
+ *
2313
+ * @param key - The asset key of the font.
2314
+ * @param fnt - The `.fnt` file: BMFont text, BMFont XML or BMFont JSON.
2315
+ * @param texture - The page texture `assets` uploaded.
2316
+ * @throws {Error} When the renderer does not draw, which is every headless run.
2317
+ * @example
2318
+ * ```ts
2319
+ * // `text` installs the boot font after the renderer came up.
2320
+ * const renderer = ctx.require(rendererPlugin);
2321
+ * const font = ctx.require(assetsPlugin).font("hud.body");
2322
+ *
2323
+ * if (renderer.host.ready() && font !== undefined) {
2324
+ * renderer.sync.fonts.install("hud.body", font.fnt, font.texture);
2325
+ * }
2326
+ * ```
2327
+ */
2328
+ install(key: string, fnt: string, texture: PixiTexture): void;
2329
+ /**
2330
+ * Tells whether a font key is installed in this application.
2331
+ *
2332
+ * @param key - The asset key of the font.
2333
+ * @returns True when the font is there.
2334
+ * @example
2335
+ * ```ts
2336
+ * // `text` measures with the real advance table only once the font is in the renderer.
2337
+ * const renderer = ctx.require(rendererPlugin);
2338
+ * renderer.sync.fonts.installed("hud.body"); // false before the boot bundle landed
2339
+ * ```
2340
+ */
2341
+ installed(key: string): boolean;
2342
+ };
2343
+ /**
2344
+ * The debug switches of the renderer.
2345
+ *
2346
+ * @example
2347
+ * ```ts
2348
+ * const switches: DebugSwitches = { nineSlice: true };
2349
+ * ```
2350
+ */
2351
+ type DebugSwitches = {
2352
+ nineSlice: boolean;
2353
+ };
2354
+ /**
2355
+ * The debug drawing of the renderer, `app.renderer.sync.debug`. The switch outlines every
2356
+ * nine-slice at once; a single panel asks for its own outline with `NineSlice.debug` (`ui`:
2357
+ * `style.debug`).
2358
+ *
2359
+ * @example
2360
+ * ```ts
2361
+ * // Check where every panel of the open popup is cut: cyan lines, red where corners overlap.
2362
+ * app.renderer.sync.debug.nineSlice(true);
2363
+ * app.renderer.sync.debug.state(); // { nineSlice: true }
2364
+ * ```
2365
+ */
2366
+ type DebugApi = {
2367
+ /**
2368
+ * Turns the outline of every nine-slice on or off. The outline strokes the bounds of the panel
2369
+ * and the four lines Pixi cuts its texture at: cyan, red when the corners overlap or the texture
2370
+ * is missing. A nine-slice whose own `debug` is true keeps its outline while the switch is off.
2371
+ * The views are drawn again on the next pass.
2372
+ *
2373
+ * @param on - True to outline every nine-slice.
2374
+ * @example
2375
+ * ```ts
2376
+ * // A dev build binds a key: "d" shows where the settings popup cuts its parchment and tabs.
2377
+ * globalThis.addEventListener("keydown", event => {
2378
+ * if (event.key === "d") app.renderer.sync.debug.nineSlice(true);
2379
+ * });
2380
+ * // After the next frame the parchment 1000x1040 with insets 72/76 shows lines at x 72 and
2381
+ * // 928, y 76 and 964.
2382
+ * ```
2383
+ */
2384
+ nineSlice(on: boolean): void;
2385
+ /**
2386
+ * What the debug switches are set to, as a fresh object.
2387
+ *
2388
+ * @returns The switches.
2389
+ * @example
2390
+ * ```ts
2391
+ * // An e2e test makes sure no outline is drawn before it compares a screenshot.
2392
+ * app.renderer.sync.debug.state(); // { nineSlice: false }
2393
+ * ```
2394
+ */
2395
+ state(): DebugSwitches;
2396
+ };
2397
+ /**
2398
+ * sync module API, `app.renderer.sync`. The one owner of every display object: it builds them
2399
+ * from components, sorts them inside the named layers of the scene and answers hit tests.
2400
+ *
2401
+ * @example
2402
+ * ```ts
2403
+ * // `input` turns a pointer position into the entity under the finger.
2404
+ * app.renderer.sync.hitTest(540, 300, entity => !app.world.ecs.has(entity, Exiting)); // 1048576
2405
+ * ```
2406
+ */
2407
+ type SyncApi = {
2408
+ /**
2409
+ * The topmost entity whose hit box holds the point and which `accept` allows. Pure component
2410
+ * math: Pixi world matrices are one frame behind at input time.
2411
+ *
2412
+ * @param x - Reference x.
2413
+ * @param y - Reference y.
2414
+ * @param accept - Filter of the caller: the first accepted entity wins.
2415
+ * @returns The entity, or `undefined` when nothing was hit. Inert: always `undefined`.
2416
+ * @example
2417
+ * ```ts
2418
+ * // `input` never picks a view that is on its way out.
2419
+ * const renderer = ctx.require(rendererPlugin);
2420
+ * const world = ctx.require(worldPlugin);
2421
+ * renderer.sync.hitTest(540, 300, entity => !world.ecs.has(entity, Exiting)); // 1048576
2422
+ * ```
2423
+ */
2424
+ hitTest(x: number, y: number, accept: (entity: Entity) => boolean): Entity | undefined;
2425
+ /**
2426
+ * The texture registry `assets` drives.
2427
+ */
2428
+ textures: TexturesApi;
2429
+ /**
2430
+ * The display registry a plugin above extends with its own component.
2431
+ */
2432
+ displays: DisplaysApi;
2433
+ /**
2434
+ * The bitmap fonts of the one application.
2435
+ */
2436
+ fonts: FontsApi;
2437
+ /**
2438
+ * The debug drawing: the nine-slice outline.
2439
+ */
2440
+ debug: DebugApi;
2441
+ /**
2442
+ * The Pixi object of an entity, for debugging and for the plugins that draw their own thing.
2443
+ *
2444
+ * @param entity - The entity to ask about.
2445
+ * @returns The display object, or `undefined` when the entity draws nothing.
2446
+ * @example
2447
+ * ```ts
2448
+ * // An e2e test reads the label the renderer wrote, to find the view in the Pixi tree.
2449
+ * const object = app.renderer.sync.displayOf(entity);
2450
+ * (object as { label: string }).label; // "Sprite#1048576 board.items:i5"
2451
+ * ```
2452
+ */
2453
+ displayOf(entity: Entity): unknown | undefined;
2454
+ };
2455
+ /**
2456
+ * sync methods driven by the plugin root and by a device restore. Not public.
2457
+ */
2458
+ type SyncInternal = {
2459
+ /**
2460
+ * Creates the root container, registers the world hooks and the `renderer.sync` system, and
2461
+ * builds the views of the entities that already exist.
2462
+ */
2463
+ start(): void;
2464
+ /**
2465
+ * Drops every view, rebuilds the root, the layers and every view. Used after a restore, where
2466
+ * the old application and all its objects are gone.
2467
+ */
2468
+ rebuildAll(): void;
2469
+ /**
2470
+ * One pass over the change sets: removed, layers, added, changed, invalidated keys.
2471
+ */
2472
+ pass(): void;
2473
+ /**
2474
+ * Lets go of the whole tree while it is still alive: the objects the game owns are detached,
2475
+ * the pooled ones are destroyed. Called just before a lost application is destroyed.
2476
+ */
2477
+ forget(): void;
2478
+ /**
2479
+ * The root container, so the plugin root can re-apply the viewport transform after a resize.
2480
+ *
2481
+ * @returns The root, or `undefined` while inert.
2482
+ */
2483
+ root(): PixiContainer | undefined;
2484
+ /**
2485
+ * How many display objects `sync` holds, for the counters of `monitor`.
2486
+ *
2487
+ * @returns The views of entities and the objects waiting in the pools.
2488
+ */
2489
+ counts(): SyncCounts;
2490
+ };
2491
+ /**
2492
+ * The display objects `sync` holds: views of entities and pooled objects.
2493
+ *
2494
+ * @example
2495
+ * ```ts
2496
+ * const counts: SyncCounts = { views: 180, pooled: 24 };
2497
+ * ```
2498
+ */
2499
+ type SyncCounts = {
2500
+ views: number;
2501
+ pooled: number;
2502
+ };
2503
+ /**
2504
+ * sync module state.
2505
+ */
2506
+ type SyncState = {
2507
+ /** The container the viewport transform is written on. Its children are the layers. */root: PixiContainer | undefined; /** The list last read from `world.projection.layers()`, compared by reference. */
2508
+ layerList: ReadonlyArray<LayerSpec> | undefined;
2509
+ layers: Map<string, LayerEntry>;
2510
+ views: Map<Entity, View>; /** The back reference from a display object to its entity. */
2511
+ entityOf: Map<object, Entity>;
2512
+ pools: Map<string, PixiContainer[]>;
2513
+ pooled: number;
2514
+ providers: TextureProvider[]; /** The components a plugin above draws its own way, in registration order. */
2515
+ adapters: DisplayEntry[]; /** The bitmap fonts installed in this application, by asset key. */
2516
+ fonts: Map<string, PixiBitmapFont>; /** The global Pixi cache the fonts were registered in, so `onStop` can take them out again. */
2517
+ fontCache: PixiModule["Cache"] | undefined; /** Texture key to the entities that use it, for `invalidate`. */
2518
+ byKey: Map<string, Set<Entity>>; /** The crops of `"cover"` sprites, by `key@widthxheight`. */
2519
+ frames: Map<string, CoverFrame>;
2520
+ invalidated: Set<string>; /** Keys already reported missing, so one key warns once. */
2521
+ warned: Set<string>;
2522
+ added: Set<Entity>;
2523
+ removed: Set<Entity>; /** Entities whose `Parent` was removed: `world` marks no change then, only `onRemoved` fires. */
2524
+ reparented: Set<Entity>; /** The debug switches, from `config.debug` and `debug.nineSlice`. */
2525
+ debug: DebugSwitches; /** The nine-slice switch moved: the next pass writes every nine-slice again. */
2526
+ outlinesStale: boolean;
2527
+ cleanups: Array<() => void>;
2528
+ };
2529
+ //#endregion
2530
+ //#region src/plugins/renderer/monitor/types.d.ts
2531
+ /**
2532
+ * The counters of the renderer, for a stats panel or a frame-budget check. Plain numbers, so the
2533
+ * object goes through `JSON.stringify` as it is.
2534
+ *
2535
+ * @example
2536
+ * ```ts
2537
+ * const stats: RenderStats = { fps: 60, frameMs: 3.4, textures: 12, textureMb: 41.25, views: 180, pooled: 24 };
2538
+ * ```
2539
+ */
2540
+ type RenderStats = {
2541
+ /**
2542
+ * Frames drawn per second, measured over the last full second of `clock` time. 0 before the
2543
+ * first second, while inert, and when no frame was drawn in the last second (a paused clock).
2544
+ */
2545
+ fps: number;
2546
+ /**
2547
+ * Mean milliseconds of `clock` time one frame took over that second, CPU side: from the
2548
+ * renderer's callback in phase `input` to the end of `render`.
2549
+ */
2550
+ frameMs: number; /** Live texture sources on the GPU: Pixi's `renderer.texture.managedTextures`, empty slots skipped. */
2551
+ textures: number; /** Estimated GPU memory of those sources in MiB: 4 bytes per pixel, every mip level. */
2552
+ textureMb: number; /** Display objects `sync` holds for entities. */
2553
+ views: number; /** Display objects waiting in the pools. */
2554
+ pooled: number;
2555
+ /**
2556
+ * Not counted, so always absent. Under WebGPU, Pixi 8.21 batches sprites and graphics straight
2557
+ * onto the native render pass encoder (`encoder.renderPassEncoder.drawIndexed`), past
2558
+ * `renderer.encoder.draw`, so no one place in Pixi sees every draw.
2559
+ */
2560
+ drawCalls?: number;
2561
+ };
2562
+ /**
2563
+ * monitor module state: the open measuring window, the last closed one, and the captures that
2564
+ * wait for a frame.
2565
+ */
2566
+ type MonitorState = {
2567
+ /** Clock time at the start of the frame being drawn; `undefined` between frames. */frameStart: number | undefined; /** Clock time at the start of the last frame, the far end of the next interval. */
2568
+ lastStart: number | undefined; /** Clock milliseconds between frame starts, summed over the open window. */
2569
+ windowMs: number; /** Intervals between frame starts in the open window. */
2570
+ intervals: number; /** Clock milliseconds spent inside frames in the open window. */
2571
+ workMs: number; /** Frames finished in the open window. */
2572
+ frames: number; /** Frames per second of the last closed window. */
2573
+ fps: number; /** Mean frame work of the last closed window, in milliseconds. */
2574
+ frameMs: number; /** Callers of `capture()` waiting for the next drawn frame. */
2575
+ captures: Array<(url: string | undefined) => void>;
2576
+ };
2577
+ /**
2578
+ * monitor module API, on `app.renderer` itself: the counters of the renderer and, in a dev build,
2579
+ * a picture of the frame. What the editor's `game.render` source and `game.capture` command read.
2580
+ *
2581
+ * @example
2582
+ * ```ts
2583
+ * // A headless test: nothing is drawn, so every counter is 0 and there is no picture.
2584
+ * app.renderer.stats(); // { fps: 0, frameMs: 0, textures: 0, textureMb: 0, views: 0, pooled: 0 }
2585
+ * await app.renderer.capture(); // undefined
2586
+ * ```
2587
+ */
2588
+ type MonitorApi = {
2589
+ /**
2590
+ * The counters of the renderer, as a fresh object. The frame timing is kept each frame without
2591
+ * an allocation; the rest is read at call time from `sync` and Pixi. There is no `drawCalls`:
2592
+ * Pixi 8.21 has no one draw point under WebGPU.
2593
+ *
2594
+ * @returns Frames per second, frame work, GPU textures and their memory, views, pooled objects.
2595
+ * Inert: all 0.
2596
+ * @example
2597
+ * ```ts
2598
+ * // The editor's stats panel reads it every frame. Here the game drew one sprite for 51 frames,
2599
+ * // 20 ms apart with 4 ms of work each, and its texture is not uploaded yet.
2600
+ * app.renderer.stats(); // { fps: 50, frameMs: 4, textures: 0, textureMb: 0, views: 1, pooled: 0 }
2601
+ * ```
2602
+ */
2603
+ stats(): RenderStats;
2604
+ /**
2605
+ * A PNG of the whole canvas, bars included, taken right after the next frame is drawn, so the
2606
+ * picture shows the state the game just reached; at once while the clock is paused, since no
2607
+ * frame comes then. Dev builds only: `__MOKU_GAME_DEV__` must be `true`.
2608
+ *
2609
+ * @returns A `data:image/png;base64,…` URL. `undefined` in a production build, while inert, lost
2610
+ * or unsupported, and when Pixi could not read the frame (logged with `ctx.log.error`).
2611
+ * @example
2612
+ * ```ts
2613
+ * // The editor's capture button, on the dev page that set globalThis.__MOKU_GAME_DEV__ = true.
2614
+ * const url = await app.renderer.capture(); // "data:image/png;base64,iVBORw0KGgo…"
2615
+ * // The same call in a production build.
2616
+ * await app.renderer.capture(); // undefined
2617
+ * ```
2618
+ */
2619
+ capture(): Promise<string | undefined>;
2620
+ };
2621
+ /**
2622
+ * monitor methods the frame drives. Not public.
2623
+ */
2624
+ type MonitorInternal = {
2625
+ /**
2626
+ * Marks the start of a frame: closes an interval of the measuring window.
2627
+ */
2628
+ begin(): void;
2629
+ /**
2630
+ * Marks the end of a drawn frame: adds its work to the window, and hands the frame to the
2631
+ * captures that wait for it.
2632
+ */
2633
+ end(): void;
2634
+ };
2635
+ /**
2636
+ * What `monitor` gets injected: the modules it reads its counters from.
2637
+ */
2638
+ type MonitorDeps = {
2639
+ host: HostApi & HostInternal;
2640
+ sync: SyncApi & SyncInternal;
2641
+ };
2642
+ declare namespace types_d_exports {
2643
+ export { Api, AspectRange, BitmapFontData, Config, CoverFrame, CreateTextureOptions, DebugApi, DebugSwitches, Deps, DisplayAdapter, DisplayEntry, DisplaysApi, Events, FontsApi, HitBox, HostApi, HostInternal, HostModule, HostState, KernelSlice, LayerEntry, Modules, MonitorApi, MonitorDeps, MonitorInternal, MonitorModule, MonitorState, NineBorders, Orientation, PixiApplication, PixiBitmapFont, PixiContainer, PixiGraphics, PixiModule, PixiNineSliceSprite, PixiSprite, PixiTexture, Point, Rect, RenderStats, RendererCtx, RendererKind, SafeArea, State, SyncApi, SyncCounts, SyncInternal, SyncModule, SyncState, TeardownScope, TextureProvider, TextureUsage, TexturesApi, View, ViewKind, ViewportApi, ViewportInternal, ViewportModule, ViewportSize, ViewportState };
2644
+ }
2645
+ /**
2646
+ * The part of Pixi the engine uses: the classes `sync` builds views from (a `Rectangle` frames the
2647
+ * crop of a `"cover"` sprite), the bitmap-font pieces `fonts.install` needs, and the `BitmapText`
2648
+ * a plugin above builds through `host.pixi()`. The module object never arrives through a static
2649
+ * import: `config.loadPixi()` returns it, so a game without a screen carries no Pixi in its bundle.
2650
+ *
2651
+ * @example
2652
+ * ```ts
2653
+ * const pixi: PixiModule = await import("pixi.js");
2654
+ * pixi.Texture.WHITE.width; // 1
2655
+ * ```
2656
+ */
2657
+ type PixiModule = Pick<typeof import("pixi.js"), "Application" | "BitmapFont" | "BitmapText" | "Cache" | "Container" | "Graphics" | "NineSliceSprite" | "Rectangle" | "Sprite" | "Texture" | "bitmapFontTextParser" | "bitmapFontXMLStringParser">;
2658
+ /**
2659
+ * One Pixi application: renderer, canvas and stage.
2660
+ */
2661
+ type PixiApplication = InstanceType<PixiModule["Application"]>;
2662
+ /**
2663
+ * A Pixi display object. `Sprite` and `NineSliceSprite` are containers too, so every view is one.
2664
+ */
2665
+ type PixiContainer = InstanceType<PixiModule["Container"]>;
2666
+ /**
2667
+ * A Pixi sprite.
2668
+ */
2669
+ type PixiSprite = InstanceType<PixiModule["Sprite"]>;
2670
+ /**
2671
+ * A Pixi nine-slice sprite.
2672
+ */
2673
+ type PixiNineSliceSprite = InstanceType<PixiModule["NineSliceSprite"]>;
2674
+ /**
2675
+ * A Pixi graphics object: what a `Shape` component is drawn with, and what clips its children.
2676
+ */
2677
+ type PixiGraphics = InstanceType<PixiModule["Graphics"]>;
2678
+ /**
2679
+ * A Pixi texture. `assets` owns its lifetime; the renderer only makes and destroys it on request.
2680
+ */
2681
+ type PixiTexture = InstanceType<PixiModule["Texture"]>;
2682
+ /**
2683
+ * A bitmap font the renderer installed for a font asset key. One per key.
2684
+ */
2685
+ type PixiBitmapFont = InstanceType<PixiModule["BitmapFont"]>;
2686
+ /**
2687
+ * The parsed shape of a `.fnt` file: what `BitmapFont` is built from.
2688
+ */
2689
+ type BitmapFontData = ConstructorParameters<PixiModule["BitmapFont"]>[0]["data"];
2690
+ /**
2691
+ * Which backend the one application drew with, or `"none"` while it draws nothing.
2692
+ *
2693
+ * @example
2694
+ * ```ts
2695
+ * const kind: RendererKind = "webgpu";
2696
+ * ```
2697
+ */
2698
+ type RendererKind = "webgpu" | "webgl" | "none";
2699
+ /**
2700
+ * renderer plugin events.
2701
+ *
2702
+ * @example
2703
+ * ```ts
2704
+ * // A game reports a lost GPU to its analytics; the pause itself is handled by `lifecycle`.
2705
+ * createPlugin("gpuReport", {
2706
+ * depends: [rendererPlugin],
2707
+ * hooks: ctx => ({ "renderer:device-lost": ({ kind }) => ctx.log.warn("gpu-lost", { kind }) })
2708
+ * }); // a lost WebGPU device logs { kind: "webgpu" }
2709
+ * ```
2710
+ */
2711
+ type Events = {
2712
+ /** The GPU device or the WebGL context was lost; the game is paused until the restore lands. */"renderer:device-lost": {
2713
+ kind: "webgpu" | "webgl";
2714
+ reason: string;
2715
+ };
2716
+ };
2717
+ /**
2718
+ * The two numbers that bound the shape of the drawn frame: allowed long side / short side.
2719
+ *
2720
+ * @example
2721
+ * ```ts
2722
+ * const aspect: AspectRange = { min: 4 / 3, max: 21 / 9 };
2723
+ * ```
2724
+ */
2725
+ type AspectRange = {
2726
+ min: number;
2727
+ max: number;
2728
+ };
2729
+ /**
2730
+ * renderer plugin config.
2731
+ *
2732
+ * @example
2733
+ * ```ts
2734
+ * createApp({
2735
+ * plugins: [...screen],
2736
+ * pluginConfigs: { renderer: { mount: "#game", background: 0x101018, maxResolution: 2 } }
2737
+ * });
2738
+ * // A debug build of the fixture outlines every nine-slice from the first frame.
2739
+ * createApp({ pluginConfigs: { renderer: { mount: "#game", debug: { nineSlice: true } } } });
2740
+ * ```
2741
+ */
2742
+ type Config = {
2743
+ /** Where the canvas goes: a selector or an element. `undefined` keeps the plugin inert. */mount: string | HTMLElement | undefined; /** Clear colour. Also the colour of the bars around the frame. */
2744
+ background: number; /** Antialias the whole canvas. Off by default: pixel art and sprites do not need it. */
2745
+ antialias: boolean; /** Cap of `devicePixelRatio`. Memory grows with its square. */
2746
+ maxResolution: number; /** Passed to Pixi. Pixi falls back to WebGL by itself. */
2747
+ preference: "webgpu" | "webgl"; /** Allowed long side / short side of the frame. A window outside it gets bars. */
2748
+ aspect: AspectRange; /** Display objects kept in all pools together. */
2749
+ poolLimit: number; /** Text of the unsupported-device screen. */
2750
+ unsupportedMessage: string; /** Loader seam. Tests pass a fake module. */
2751
+ loadPixi: () => Promise<PixiModule>; /** Debug drawing at start. `app.renderer.sync.debug` switches it while the game runs. */
2752
+ debug: DebugSwitches;
2753
+ };
2754
+ /**
2755
+ * renderer plugin state: one branch per module.
2756
+ */
2757
+ type State = {
2758
+ host: HostState;
2759
+ viewport: ViewportState;
2760
+ sync: SyncState;
2761
+ monitor: MonitorState;
2762
+ };
2763
+ /**
2764
+ * renderer plugin API, `app.renderer`: grouped by module, and the two members of `monitor`,
2765
+ * `stats()` and `capture()`, on the plugin itself.
2766
+ *
2767
+ * @example
2768
+ * ```ts
2769
+ * app.renderer.host.kind(); // "webgpu"
2770
+ * app.renderer.viewport.size().width; // 1080, reference units
2771
+ * app.renderer.sync.hitTest(540, 300, () => true); // 1048576
2772
+ * app.renderer.stats().views; // 180
2773
+ * ```
2774
+ */
2775
+ type Api = {
2776
+ host: HostApi;
2777
+ viewport: ViewportApi;
2778
+ sync: SyncApi;
2779
+ } & MonitorApi;
2780
+ /**
2781
+ * Resolved dependency APIs. `clock` is the time source of the frame counters.
2782
+ */
2783
+ type Deps = {
2784
+ time: Api$2;
2785
+ lifecycle: Api$5;
2786
+ world: Api$1;
2787
+ clock: Api$6;
2788
+ };
2789
+ /**
2790
+ * What the kernel context offers before the deps are attached.
2791
+ *
2792
+ * `emit` is the kernel's own: `index.ts` writes `events` with an annotated `register`
2793
+ * (core spec `14-EVENT-REGISTRATION.md` row 8), so the plugin's own event reaches the pre-typed
2794
+ * `api` factory and no member of this context is cast.
2795
+ */
2796
+ type KernelSlice = PluginCtx<Config, State, Events> & {
2797
+ readonly global: Readonly<Config$2>;
2798
+ readonly log: Log.LogApi;
2799
+ readonly require: Require;
2800
+ };
2801
+ /**
2802
+ * Domain context shared by the three modules.
2803
+ */
2804
+ type RendererCtx = KernelSlice & {
2805
+ readonly deps: Deps;
2806
+ };
2807
+ /**
2808
+ * Both halves of the `host` module: what a game calls and what the two modules above get injected.
2809
+ */
2810
+ type HostModule = HostApi & HostInternal;
2811
+ /**
2812
+ * Both halves of the `viewport` module: what a game calls and what `sync` and the frame drive.
2813
+ */
2814
+ type ViewportModule = ViewportApi & ViewportInternal;
2815
+ /**
2816
+ * Both halves of the `sync` module: what `input` and `assets` call and what the frame drives.
2817
+ */
2818
+ type SyncModule = SyncApi & SyncInternal;
2819
+ /**
2820
+ * Both halves of the `monitor` module: what a tool calls and what the frame drives.
2821
+ */
2822
+ type MonitorModule = MonitorApi & MonitorInternal;
2823
+ /**
2824
+ * The four modules in injection order.
2825
+ */
2826
+ type Modules = {
2827
+ host: HostModule;
2828
+ viewport: ViewportModule;
2829
+ sync: SyncModule;
2830
+ monitor: MonitorModule;
2831
+ };
2832
+ /**
2833
+ * What `onStop` receives: the frozen config and the plugin state, nothing else.
2834
+ */
2835
+ type TeardownScope = {
2836
+ readonly config: Readonly<Config>;
2837
+ readonly state: State;
2838
+ };
2839
+ //#endregion
2840
+ export { ComponentHandle as A, SystemDefinition as B, ProjectionSpec as C, AnyComponent as D, ViewHandle as E, Mut as F, Narrowed as I, Owner as L, ComponentValue as M, Entity as N, AnyComponentType as O, EntitySnapshot as P, QueryTerm as R, ProjectionMotion as S, TrackSegment as T, TagType as V, LayerSort as _, types_d_exports as a, MotionHandle as b, Point as c, Config$1 as d, State$1 as f, Ease as g, DescriptionNode as h, State as i, ComponentType as j, AnyComponentValue as k, ViewportSize as l, ChangeHook as m, Config as n, RenderStats as o, types_d_exports$1 as p, PixiTexture as r, DebugSwitches as s, Api as t, Api$1 as u, LayerSpec as v, TrackOptions as w, NumericFields as x, Motion as y, ResourceType as z };