@vibecook/ice 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/{chunk-ENMRSITH.js → chunk-BFT7YWS2.js} +16 -3
- package/dist/chunk-BFT7YWS2.js.map +1 -0
- package/dist/{chunk-3PHPWRB2.js → chunk-ELCBCTKY.js} +7 -4
- package/dist/chunk-ELCBCTKY.js.map +1 -0
- package/dist/{chunk-7LZAXQAO.js → chunk-JLXETATP.js} +5699 -3099
- package/dist/chunk-JLXETATP.js.map +1 -0
- package/dist/devtools.js +5 -2
- package/dist/devtools.js.map +1 -1
- package/dist/dom.js +2 -2
- package/dist/ground.js +1 -1
- package/dist/index.js +33 -1
- package/dist/r3f.js +3 -3
- package/dist/react.js +5 -3
- package/dist/types/packages/core/src/behavior/compile.d.ts +74 -0
- package/dist/types/packages/core/src/behavior/define-behavior.d.ts +55 -0
- package/dist/types/packages/core/src/behavior/differ.d.ts +91 -0
- package/dist/types/packages/core/src/behavior/harness.d.ts +65 -0
- package/dist/types/packages/core/src/behavior/migrate.d.ts +87 -0
- package/dist/types/packages/core/src/behavior/public-reads.d.ts +31 -0
- package/dist/types/packages/core/src/behavior/runtime.d.ts +88 -0
- package/dist/types/packages/core/src/behavior/types.d.ts +312 -0
- package/dist/types/packages/core/src/behavior/world-view.d.ts +36 -0
- package/dist/types/packages/core/src/engine/engine.d.ts +38 -1
- package/dist/types/packages/core/src/engine/guests.d.ts +196 -0
- package/dist/types/packages/core/src/engine/reflectors.d.ts +8 -0
- package/dist/types/packages/core/src/facade/create-canvas-engine.d.ts +16 -0
- package/dist/types/packages/core/src/guards/guarded-tx.d.ts +57 -0
- package/dist/types/packages/core/src/index.d.ts +10 -3
- package/dist/types/packages/core/src/widget/define-widget.d.ts +28 -0
- package/dist/types/packages/core/src/widget/props.d.ts +35 -1
- package/dist/types/packages/core/src/widget/spawn.d.ts +14 -0
- package/dist/types/packages/dom/src/loop.d.ts +9 -0
- package/dist/types/packages/react/src/hooks.d.ts +12 -1
- package/dist/types/packages/react/src/index.d.ts +1 -1
- package/package.json +31 -65
- package/dist/chunk-3PHPWRB2.js.map +0 -1
- package/dist/chunk-7LZAXQAO.js.map +0 -1
- package/dist/chunk-ENMRSITH.js.map +0 -1
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Component, Entity, Query, Relation, Resource, SystemCtx, Tag, World } from "@vibecook/strata-ecs";
|
|
2
|
+
import type { AnyBehaviorDef, BehaviorQuery, BehaviorQuerySpec, BehaviorWorld } from "./types";
|
|
3
|
+
export declare function createBehaviorWorld(world: World): BehaviorWorld;
|
|
4
|
+
/**
|
|
5
|
+
* The compiled-query cache for one behavior. Keyed on the term identity list,
|
|
6
|
+
* so the common case (a behavior calling `ctx.query` with the same spec every
|
|
7
|
+
* frame) compiles ONCE — `defineQuery` identity is strata's own cache key for
|
|
8
|
+
* the matched-archetype list, so re-compiling per call would defeat two caches.
|
|
9
|
+
*/
|
|
10
|
+
export declare function createQueryScope(behavior: AnyBehaviorDef, allowed: ReadonlySet<Component | Tag>): (walker: BehaviorWalker, spec: BehaviorQuerySpec) => BehaviorQuery;
|
|
11
|
+
/**
|
|
12
|
+
* Where a behavior's walks and structural ops go, which depends on WHERE it is
|
|
13
|
+
* running — and the difference is not cosmetic.
|
|
14
|
+
*
|
|
15
|
+
* In a pipeline phase the behavior is inside a system: walks go through
|
|
16
|
+
* `SystemCtx` (attributed — strata's enforced read path), and structural ops
|
|
17
|
+
* are DEFERRED to the phase boundary because immediate `world.*` structural
|
|
18
|
+
* mutation throws at iteration depth > 0.
|
|
19
|
+
*
|
|
20
|
+
* At the publish slot there is no running system and no iteration bracket:
|
|
21
|
+
* walks are plain `world.query` (unattributed — nothing to attribute them to),
|
|
22
|
+
* and structural ops are immediate and legal. Ephemeral behaviors live here,
|
|
23
|
+
* which is exactly why eph structural writes are legal for them and illegal
|
|
24
|
+
* for everyone else.
|
|
25
|
+
*/
|
|
26
|
+
export interface BehaviorWalker {
|
|
27
|
+
each(q: Query, fn: (e: Entity) => void): void;
|
|
28
|
+
first(q: Query): Entity | undefined;
|
|
29
|
+
addComponent(e: Entity, c: Component, v: unknown): void;
|
|
30
|
+
removeComponent(e: Entity, c: Component): void;
|
|
31
|
+
}
|
|
32
|
+
export declare function systemWalker(ctx: SystemCtx): BehaviorWalker;
|
|
33
|
+
export declare function publishWalker(world: World): BehaviorWalker;
|
|
34
|
+
/** The term set a behavior may query: its own component ∪ declared components/tags. */
|
|
35
|
+
export declare function allowedQueryTerms(behavior: AnyBehaviorDef, readComponents: readonly Component[], readTags: readonly Tag[]): ReadonlySet<Component | Tag>;
|
|
36
|
+
export type { Relation, Resource };
|
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
* setFrameInfo FrameInfo resource — value write, pre-tick
|
|
6
6
|
* world.sync() durable + presence drains land; projections place
|
|
7
7
|
* world.tick(...) the phase-group pipeline (pipeline.ts)
|
|
8
|
+
* guests [2026-08-15, petition I14] the NAMED guest slot:
|
|
9
|
+
* plugin/host frame work under a circuit breaker
|
|
10
|
+
* (guests.ts). BEFORE publish so derived state has
|
|
11
|
+
* settled when presence I/O reads it, and before
|
|
12
|
+
* notify so the same frame reflects it. Guests are
|
|
13
|
+
* fault-ISOLATED — the one tier here that is.
|
|
8
14
|
* publish hooks POST-TICK, PRE-NOTIFY: presence I/O home — eph
|
|
9
15
|
* structural ops are illegal for the whole tick, this
|
|
10
16
|
* step is theirs; `world.*` is legal here (outside the
|
|
@@ -22,6 +28,7 @@
|
|
|
22
28
|
import type { World } from "@vibecook/strata-ecs";
|
|
23
29
|
import type { AnySystem } from "./pipeline";
|
|
24
30
|
import { type FrameControl } from "./frame-control";
|
|
31
|
+
import { type GuestRegistry } from "./guests";
|
|
25
32
|
import { type PhaseGroup } from "./pipeline";
|
|
26
33
|
import { type ReflectorDef } from "./reflectors";
|
|
27
34
|
export type PublishHook = (world: World) => void;
|
|
@@ -38,10 +45,29 @@ export interface FrameTelemetry {
|
|
|
38
45
|
readonly systems: readonly SystemRunRecord[];
|
|
39
46
|
readonly phaseFlushMicros: ReadonlyMap<string, number>;
|
|
40
47
|
readonly reflectorsFlushed: readonly string[];
|
|
48
|
+
/**
|
|
49
|
+
* Total reflector-flush cost, µs (post-notify — OUTSIDE the tick, which is
|
|
50
|
+
* why it can never appear in `phaseFlushMicros`: that map is keyed by strata
|
|
51
|
+
* PHASE names and reflect is not a phase. Devtools' "reflect" lane read that
|
|
52
|
+
* map until 2026-08-15 and therefore never reported at all).
|
|
53
|
+
*/
|
|
54
|
+
readonly reflectMicros: number;
|
|
55
|
+
/** Per-reflector flush cost, µs, for the same frame (registration order). */
|
|
56
|
+
readonly reflectorMicros: ReadonlyMap<string, number>;
|
|
41
57
|
}
|
|
42
58
|
export interface EngineOpts {
|
|
43
59
|
/** Reflector fault sink (default: console.error, skip this frame). */
|
|
44
60
|
readonly onReflectorFault?: (name: string, err: unknown) => void;
|
|
61
|
+
/** Guest fault sink (default: console.error). A guest throwing is contained
|
|
62
|
+
* by the breaker; hosts route this to their own diagnostics. */
|
|
63
|
+
readonly onGuestFault?: (id: string, err: unknown) => void;
|
|
64
|
+
/**
|
|
65
|
+
* Guest breaker notices — suspensions and lenient-mode overruns (default:
|
|
66
|
+
* console.warn). Hosts SHOULD route this: a suspended derived guest means
|
|
67
|
+
* derived state silently stops updating, which reads to a user as a broken
|
|
68
|
+
* document rather than a broken plugin (design-009 §7).
|
|
69
|
+
*/
|
|
70
|
+
readonly onGuestNotice?: (message: string) => void;
|
|
45
71
|
}
|
|
46
72
|
export interface Engine {
|
|
47
73
|
readonly world: World;
|
|
@@ -53,6 +79,13 @@ export interface Engine {
|
|
|
53
79
|
reflectorNames(): readonly string[];
|
|
54
80
|
/** Register a publish-step hook (post-tick, pre-notify presence I/O). Returns a remover. */
|
|
55
81
|
onPublish(hook: PublishHook): () => void;
|
|
82
|
+
/**
|
|
83
|
+
* The guest runtime (petition I14): frame-cadence work the engine did not
|
|
84
|
+
* write — plugin systems, host derived-state jobs, compiled behaviors — run
|
|
85
|
+
* in a named slot between the tick and the publish hooks, each fault-isolated
|
|
86
|
+
* and budgeted by a circuit breaker (guests.ts).
|
|
87
|
+
*/
|
|
88
|
+
readonly guests: GuestRegistry;
|
|
56
89
|
/** Run one full frame. `now` in ms (rAF timestamp or a test counter). */
|
|
57
90
|
step(now: number): void;
|
|
58
91
|
/**
|
|
@@ -65,8 +98,12 @@ export interface Engine {
|
|
|
65
98
|
* hand exactly as before.
|
|
66
99
|
*/
|
|
67
100
|
readonly frame: FrameControl;
|
|
68
|
-
/** Arm per-system run/skip + µs collection (idempotent; costs timer calls per system).
|
|
101
|
+
/** Arm per-system run/skip + µs collection (idempotent; costs timer calls per system).
|
|
102
|
+
* Also puts the guest breaker in LENIENT mode — devtools attached means a
|
|
103
|
+
* debugger breakpoint must not suspend a healthy guest. */
|
|
69
104
|
enableTelemetry(): void;
|
|
105
|
+
/** Tear down guest instances (the facade calls this from `dispose()`). */
|
|
106
|
+
disposeGuests(): void;
|
|
70
107
|
/** The most recent completed frame's readout (undefined until telemetry armed + one step). */
|
|
71
108
|
lastFrame(): FrameTelemetry | undefined;
|
|
72
109
|
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The guest runtime (petition I14 — the literal PA-28 "spine scheduler").
|
|
3
|
+
*
|
|
4
|
+
* A GUEST is frame-cadence work the engine did not write: a plugin's declared
|
|
5
|
+
* system, a host's derived-state job, and (from design-009) every compiled
|
|
6
|
+
* behavior. Guests get a NAMED slot in the frame contract — design-002 §1 as
|
|
7
|
+
* amended 2026-08-15:
|
|
8
|
+
*
|
|
9
|
+
* setFrameInfo → sync → tick → GUESTS → publish hooks → notify → reflect
|
|
10
|
+
*
|
|
11
|
+
* Guests run BEFORE the publish hooks so derived state has settled by the time
|
|
12
|
+
* presence I/O reads it, and before `notify()` so the same frame reflects their
|
|
13
|
+
* writes. Three properties the engine owes anything it hosts here:
|
|
14
|
+
*
|
|
15
|
+
* 1. **Fault isolation.** A throwing guest is caught, struck, and skipped — it
|
|
16
|
+
* never kills the loop, its neighbours, or the frame. (Engine systems and
|
|
17
|
+
* publish hooks keep propagating loudly by design; guests are the untrusted
|
|
18
|
+
* tier, and the breaker below is what makes hosting them honest.)
|
|
19
|
+
* 2. **The snapshot rule.** `runAll` iterates a snapshot with a liveness check,
|
|
20
|
+
* so a guest that adds or removes guests mid-run never skips a neighbour —
|
|
21
|
+
* the bug this same release fixed in the publish-hook and reflector loops.
|
|
22
|
+
* A guest added during a run starts NEXT frame.
|
|
23
|
+
* 3. **A circuit breaker, named honestly.** A main-thread body cannot be
|
|
24
|
+
* preempted: by the time we measure, the frame is already spent. What the
|
|
25
|
+
* breaker guarantees is that no guest hurts the canvas TWICE sustained.
|
|
26
|
+
*
|
|
27
|
+
* GENERATIONS. `make(gen)` builds a per-generation instance; the generation
|
|
28
|
+
* boundary in ICE is `world.reset()` (doc close/switch — the world is reset in
|
|
29
|
+
* place, so systems, observers and this registry all survive while every entity
|
|
30
|
+
* dies). On reset we mark the generation dirty and rebuild at the TOP of the
|
|
31
|
+
* next guest step — never inside the observer emit, where structural writes are
|
|
32
|
+
* illegal and user code has no business running. `gen.signal` aborts when the
|
|
33
|
+
* generation ends or the guest is removed.
|
|
34
|
+
*
|
|
35
|
+
* THE LEDGER IS HOST-INJECTABLE. Downstream (VibeField) an engine is per-doc, so
|
|
36
|
+
* a registry-local ledger would reset on every doc switch and hand chronic
|
|
37
|
+
* offenders a fresh probation forever. `add({ledger})` seeds prior state and
|
|
38
|
+
* `onLedgerChange` reports every transition, so the host persists across
|
|
39
|
+
* generations it owns.
|
|
40
|
+
*/
|
|
41
|
+
import type { World } from "@vibecook/strata-ecs";
|
|
42
|
+
/** Frame facts a guest may read (FrameInfo, pre-read by the engine). */
|
|
43
|
+
export interface GuestFrame {
|
|
44
|
+
readonly dtMs: number;
|
|
45
|
+
readonly tick: number;
|
|
46
|
+
/** Accumulated CLAMPED dt — under-reports across freezes; never accumulate
|
|
47
|
+
* wall time from it (frame-info.ts). */
|
|
48
|
+
readonly clock: number;
|
|
49
|
+
}
|
|
50
|
+
export type GuestRun = (frame: GuestFrame) => void;
|
|
51
|
+
export interface GuestInstance {
|
|
52
|
+
run: GuestRun;
|
|
53
|
+
dispose?: () => void;
|
|
54
|
+
/**
|
|
55
|
+
* "I still owe visible work." Feeds the frame gate's settle walk, so a
|
|
56
|
+
* freeze taken mid-derive parks only AFTER the guest has caught up — the
|
|
57
|
+
* canvas is never photographed between a structural change and its reflow.
|
|
58
|
+
* A guest with no owed-work notion simply omits it (the frame gate then
|
|
59
|
+
* treats it as quiet, which is the honest default).
|
|
60
|
+
*/
|
|
61
|
+
busy?: () => boolean;
|
|
62
|
+
}
|
|
63
|
+
export interface GuestGeneration {
|
|
64
|
+
readonly world: World;
|
|
65
|
+
/** Aborts at generation teardown (world reset), removal, or engine dispose. */
|
|
66
|
+
readonly signal: AbortSignal;
|
|
67
|
+
}
|
|
68
|
+
export type GuestFactory = (gen: GuestGeneration) => GuestInstance | GuestRun;
|
|
69
|
+
/** The persistable half of a guest's breaker state. */
|
|
70
|
+
export interface GuestLedgerRecord {
|
|
71
|
+
/** Cumulative over-budget/fault strikes (never reset by resume — honesty). */
|
|
72
|
+
readonly strikes: number;
|
|
73
|
+
readonly suspended: boolean;
|
|
74
|
+
}
|
|
75
|
+
export interface GuestSpec {
|
|
76
|
+
/** Stable id — the ledger key, the profiler lane name, the doctor's row. */
|
|
77
|
+
readonly id: string;
|
|
78
|
+
readonly make: GuestFactory;
|
|
79
|
+
/** Per-invocation budget request; the effective budget is
|
|
80
|
+
* `min(budgetMs, seamCapMs)` — no single guest may claim the whole seam. */
|
|
81
|
+
readonly budgetMs?: number;
|
|
82
|
+
/** RESERVED. v1 hosts the publish slot only; pipeline-group values arrive
|
|
83
|
+
* with the behavior framework (design-009 M13a), wrapped by this breaker. */
|
|
84
|
+
readonly phase?: "publish";
|
|
85
|
+
/** Seed prior breaker state (host-persisted across engine generations). */
|
|
86
|
+
readonly ledger?: GuestLedgerRecord;
|
|
87
|
+
}
|
|
88
|
+
export interface GuestStatus {
|
|
89
|
+
readonly id: string;
|
|
90
|
+
readonly status: "running" | "suspended" | "failed";
|
|
91
|
+
/** Last invocation, ms. */
|
|
92
|
+
readonly lastMs: number;
|
|
93
|
+
/** p95 over the rolling window, ms. */
|
|
94
|
+
readonly p95Ms: number;
|
|
95
|
+
readonly strikes: number;
|
|
96
|
+
readonly budgetMs: number;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* A guest whose CADENCE its owner drives (design-009 §7): compiled behaviors
|
|
100
|
+
* run as ordinary pipeline systems, in their declared phase, inside the tick —
|
|
101
|
+
* so nothing here can call their bodies. What they still need is everything
|
|
102
|
+
* ELSE a guest gets: one breaker, one ledger, one doctor row, one seam total.
|
|
103
|
+
* That is what this handle is — the breaker, detached from the scheduler.
|
|
104
|
+
*/
|
|
105
|
+
export interface DrivenGuest {
|
|
106
|
+
/** Breaker verdict. Cheap and never throws — poll it from a system `runIf`. */
|
|
107
|
+
suspended(): boolean;
|
|
108
|
+
/**
|
|
109
|
+
* Run `body` inside this guest's fault domain, ACCRUING its cost into the
|
|
110
|
+
* current frame's charge. Returns false when the guest is suspended (the
|
|
111
|
+
* body did not run) or the body threw (contained).
|
|
112
|
+
*
|
|
113
|
+
* Accrual, not per-call recording, is what makes the budget mean "per frame"
|
|
114
|
+
* for a CHUNKED body: a tick system's body runs once per matching chunk, and
|
|
115
|
+
* recording each chunk as its own invocation would compare a 2ms frame budget
|
|
116
|
+
* against a fraction of the work it was meant to bound. The accrued total is
|
|
117
|
+
* closed as ONE invocation at the guest slot, every frame.
|
|
118
|
+
*/
|
|
119
|
+
run(body: () => void): boolean;
|
|
120
|
+
/**
|
|
121
|
+
* Report a fault the caller already CONTAINED — a per-instance hook throw
|
|
122
|
+
* that the delivery loop caught so one bad node cannot kill a 1000-node
|
|
123
|
+
* behavior (BF-D18). It takes the ordinary throw ladder.
|
|
124
|
+
*/
|
|
125
|
+
fault(err: unknown): void;
|
|
126
|
+
/** Idempotent. */
|
|
127
|
+
remove(): void;
|
|
128
|
+
}
|
|
129
|
+
export interface DrivenGuestSpec {
|
|
130
|
+
readonly id: string;
|
|
131
|
+
readonly budgetMs?: number;
|
|
132
|
+
readonly ledger?: GuestLedgerRecord;
|
|
133
|
+
/** Owed-work reporter for the frame gate's settle walk. */
|
|
134
|
+
readonly busy?: () => boolean;
|
|
135
|
+
}
|
|
136
|
+
export interface GuestRegistry {
|
|
137
|
+
/** Register a guest. Returns a remover (idempotent; disposes the instance). */
|
|
138
|
+
add(spec: GuestSpec): () => void;
|
|
139
|
+
/**
|
|
140
|
+
* Register a guest the registry does NOT call (see {@link DrivenGuest}).
|
|
141
|
+
* Its cost still lands in this frame's seam total, its strikes in the same
|
|
142
|
+
* ledger, and its row in `list()`.
|
|
143
|
+
*/
|
|
144
|
+
addDriven(spec: DrivenGuestSpec): DrivenGuest;
|
|
145
|
+
/** Live status for devtools/doctor — never throws, safe to poll. */
|
|
146
|
+
list(): readonly GuestStatus[];
|
|
147
|
+
/** Fires whenever a guest's persistable breaker state changes. */
|
|
148
|
+
onLedgerChange(cb: (id: string, record: GuestLedgerRecord) => void): () => void;
|
|
149
|
+
/** Clear a suspension (the doctor's explicit action). Cumulative strikes
|
|
150
|
+
* survive; the rolling window is cleared so probation starts fresh. */
|
|
151
|
+
resume(id: string): void;
|
|
152
|
+
}
|
|
153
|
+
/** The engine-facing half (step drives it; hosts never see these). */
|
|
154
|
+
export interface GuestRegistryInternal extends GuestRegistry {
|
|
155
|
+
runAll(frame: GuestFrame): void;
|
|
156
|
+
/** Any live guest reporting owed work — the engine's settle reporter. */
|
|
157
|
+
anyBusy(): boolean;
|
|
158
|
+
dispose(): void;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Breaker constants (design-009 §12 / door ED-D3). Exported so tests and the
|
|
162
|
+
* downstream doctor cite ONE source rather than re-deriving thresholds.
|
|
163
|
+
*/
|
|
164
|
+
export declare const GUEST_BUDGET: {
|
|
165
|
+
/** Default per-invocation budget when a guest declares none. */
|
|
166
|
+
readonly defaultBudgetMs: 2;
|
|
167
|
+
/** The whole guest seam's sustained budget — also the per-guest ceiling. */
|
|
168
|
+
readonly seamCapMs: 8;
|
|
169
|
+
/** Rolling window length, in invocations. */
|
|
170
|
+
readonly window: 120;
|
|
171
|
+
/** Trip: more than this many of the last `window` invocations over budget. */
|
|
172
|
+
readonly overBudgetTrip: 30;
|
|
173
|
+
/** Trip: this many CONSECUTIVE invocations at ≥ `heavyFactor` × budget. */
|
|
174
|
+
readonly consecutiveHeavy: 3;
|
|
175
|
+
readonly heavyFactor: 4;
|
|
176
|
+
/** Trip: this many CONSECUTIVE invocations over `stallMs` (one GC pause or
|
|
177
|
+
* debugger breakpoint must never suspend — hence 2, not 1). */
|
|
178
|
+
readonly consecutiveStall: 2;
|
|
179
|
+
readonly stallMs: 50;
|
|
180
|
+
/** Trip: this many consecutive throws. */
|
|
181
|
+
readonly consecutiveThrows: 3;
|
|
182
|
+
/** Frames the whole seam may sit over `seamCapMs` before the worst offender
|
|
183
|
+
* is suspended. */
|
|
184
|
+
readonly seamOverFrames: 3;
|
|
185
|
+
};
|
|
186
|
+
export interface GuestRegistryOpts {
|
|
187
|
+
/**
|
|
188
|
+
* Dev leniency: while true, TIMING strikes log and never suspend (the throw
|
|
189
|
+
* ladder still bites). Wired to `engine.enableTelemetry()` — i.e. devtools
|
|
190
|
+
* attached — so stepping in a debugger cannot suspend a healthy guest.
|
|
191
|
+
*/
|
|
192
|
+
readonly lenient?: () => boolean;
|
|
193
|
+
readonly onFault?: (id: string, err: unknown) => void;
|
|
194
|
+
readonly onNotice?: (message: string) => void;
|
|
195
|
+
}
|
|
196
|
+
export declare function createGuestRegistry(world: World, opts?: GuestRegistryOpts): GuestRegistryInternal;
|
|
@@ -50,6 +50,14 @@ export interface ReflectorRegistry {
|
|
|
50
50
|
/** Every registered reflector, registration order (devtools enumeration —
|
|
51
51
|
* independent of telemetry; a reflector idle since boot still lists). */
|
|
52
52
|
names(): readonly string[];
|
|
53
|
+
/** Arm flush timing (idempotent). Costs one `performance.now()` pair per
|
|
54
|
+
* flushed reflector per frame, so it rides `engine.enableTelemetry()` and is
|
|
55
|
+
* off by default — the same opt-in posture as per-system timing. */
|
|
56
|
+
armTelemetry(): void;
|
|
57
|
+
/** Total µs of the most recent `flushAll` (0 until armed). */
|
|
58
|
+
lastFlushMicros(): number;
|
|
59
|
+
/** Per-reflector µs of the most recent `flushAll` (empty until armed). */
|
|
60
|
+
lastFlushDetail(): ReadonlyMap<string, number>;
|
|
53
61
|
}
|
|
54
62
|
export declare function createReflectorRegistry(world: World, opts?: {
|
|
55
63
|
onFault?: (name: string, err: unknown) => void;
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
* remain reviewed constants (RUNTIME_BUDGETS) in v1 — recorded.
|
|
20
20
|
*/
|
|
21
21
|
import type { Entity, World } from "@vibecook/strata-ecs";
|
|
22
|
+
import { type BehaviorRuntime } from "../behavior/runtime";
|
|
23
|
+
import type { AnyBehaviorDef } from "../behavior/types";
|
|
22
24
|
import { type Engine } from "../engine/engine";
|
|
23
25
|
import type { FrameControl } from "../engine/frame-control";
|
|
24
26
|
import { type InteractionStack } from "../interaction/install";
|
|
@@ -42,6 +44,13 @@ export interface CanvasEngineOpts {
|
|
|
42
44
|
}[];
|
|
43
45
|
/** Registered tools (definition happens at defineTool; validated). */
|
|
44
46
|
readonly tools?: readonly Tool[];
|
|
47
|
+
/**
|
|
48
|
+
* Behaviors this engine runs (design-009). Definition is the import
|
|
49
|
+
* side-effect; listing them here COMPILES and installs them — a behavior that
|
|
50
|
+
* is defined but never registered costs nothing and does nothing, which is
|
|
51
|
+
* what makes "a plugin declared it" and "this engine runs it" separable.
|
|
52
|
+
*/
|
|
53
|
+
readonly behaviors?: readonly AnyBehaviorDef[];
|
|
45
54
|
readonly budgets?: {
|
|
46
55
|
readonly keepMounted?: number;
|
|
47
56
|
readonly fboBytes?: number;
|
|
@@ -166,6 +175,13 @@ export interface StageControl {
|
|
|
166
175
|
export interface CanvasEngine {
|
|
167
176
|
readonly world: World;
|
|
168
177
|
readonly engine: Engine;
|
|
178
|
+
/**
|
|
179
|
+
* The behavior runtime (design-009 §6): attach/detach/has/read for
|
|
180
|
+
* runtime-class behaviors, plus `list()` for devtools and the doctor.
|
|
181
|
+
* DURABLE attachment is deliberately absent — it is a document op and goes
|
|
182
|
+
* through `tx.attach` inside a transaction, so it syncs and undoes.
|
|
183
|
+
*/
|
|
184
|
+
readonly behaviors: BehaviorRuntime;
|
|
169
185
|
readonly stack: InteractionStack;
|
|
170
186
|
readonly runtime: WidgetRuntime & {
|
|
171
187
|
uninstall(): void;
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import type { Component, Entity, OrderPlace, Relation, Resource, Tag, World } from "@vibecook/strata-ecs";
|
|
15
15
|
import type { Mutator } from "@vibecook/strata-ecs/durable";
|
|
16
16
|
import { type ComponentInit, type Prefab } from "../schema/prefab";
|
|
17
|
+
import type { LiveWriter } from "./live-writer";
|
|
17
18
|
export interface GuardedTx {
|
|
18
19
|
spawnPrefab(prefab: Prefab, overrides?: readonly ComponentInit[]): Entity;
|
|
19
20
|
/** Raw spawn — entity is unknown to the guard (prefer spawnPrefab). */
|
|
@@ -32,6 +33,29 @@ export interface GuardedTx {
|
|
|
32
33
|
/** Reorder within the current parent's sibling sequence — ordered relations only (petition 8). */
|
|
33
34
|
moveRelation(e: Entity, r: Relation, place: OrderPlace): void;
|
|
34
35
|
setResource<S>(res: Resource<S>, v: S): void;
|
|
36
|
+
/**
|
|
37
|
+
* Animation-integrated write (petition I15): ONE commit owns the capture, the
|
|
38
|
+
* durable final, and the glide. `from` is read AT CALL TIME (pre-commit by
|
|
39
|
+
* construction), the final lands in THIS transaction — inheriting its
|
|
40
|
+
* `undoable` — and after a successful seal the tail installs the verified
|
|
41
|
+
* public-surface glide: `TransformTween` (which GRANTS the divergence) then a
|
|
42
|
+
* live rewind to `from`. One commit, one undo entry, no double-write.
|
|
43
|
+
*
|
|
44
|
+
* `animateMs` absent/0 = snap (no tween attached at all). A `Grab`-held
|
|
45
|
+
* entity is SKIPPED — the gesture owns its divergence. An entity already
|
|
46
|
+
* tweening is RETARGETED (its `toX/toY` rewritten) rather than restarted, so
|
|
47
|
+
* a re-layout mid-glide re-aims instead of completing at a stale target.
|
|
48
|
+
*
|
|
49
|
+
* Requires `opts.live` (the session's LiveWriter) — without it the rewind
|
|
50
|
+
* could not be divergence-guarded, so the call refuses rather than writing
|
|
51
|
+
* around the guard.
|
|
52
|
+
*/
|
|
53
|
+
move(e: Entity, to: {
|
|
54
|
+
x: number;
|
|
55
|
+
y: number;
|
|
56
|
+
}, opts?: {
|
|
57
|
+
animateMs?: number;
|
|
58
|
+
}): void;
|
|
35
59
|
}
|
|
36
60
|
interface GuardedEditor {
|
|
37
61
|
set<S>(c: Component<S>, v: S): GuardedEditor;
|
|
@@ -39,6 +63,7 @@ interface GuardedEditor {
|
|
|
39
63
|
interface TxDoc {
|
|
40
64
|
transaction(fn: (tx: Mutator) => void, opts?: {
|
|
41
65
|
undoable?: boolean;
|
|
66
|
+
meta?: Record<string, unknown>;
|
|
42
67
|
}): void;
|
|
43
68
|
}
|
|
44
69
|
export interface GuardedTxOpts {
|
|
@@ -48,6 +73,38 @@ export interface GuardedTxOpts {
|
|
|
48
73
|
* first-⌘Z-removes-a-seed quirk). Default true: one tx = one undo step.
|
|
49
74
|
*/
|
|
50
75
|
readonly undoable?: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* The session's live writer. Required by {@link GuardedTx.move} (its
|
|
78
|
+
* post-commit rewind is a live write to a doc cell, legal only under the
|
|
79
|
+
* tween grant) and by the post-seal retarget sweep below.
|
|
80
|
+
*/
|
|
81
|
+
readonly live?: LiveWriter;
|
|
82
|
+
/** Optional entity → doc-key probe; when absent every entity is treated as
|
|
83
|
+
* durable-if-Position-eligible. Used to skip runtime-only movers' tx write. */
|
|
84
|
+
readonly keyOf?: (e: Entity) => string | undefined;
|
|
85
|
+
/**
|
|
86
|
+
* Per-commit PROVENANCE (strata 0.12.0, petition 9): a small record carried
|
|
87
|
+
* in-CRDT with the batch, so a receiving peer can say who wrote this and why.
|
|
88
|
+
* The behavior framework stamps `{ behavior, label }` on every `ctx.commit`
|
|
89
|
+
* (design-009 §4.4 — `label` is a stable machine id, never display text).
|
|
90
|
+
* Note that shallow saves erase provenance below the compaction boundary, so
|
|
91
|
+
* it is diagnostics, never a data dependency.
|
|
92
|
+
*/
|
|
93
|
+
readonly meta?: Record<string, unknown>;
|
|
51
94
|
}
|
|
52
95
|
export declare function guardedTransaction(doc: TxDoc, world: World, run: (tx: GuardedTx) => void, opts?: GuardedTxOpts): void;
|
|
96
|
+
/**
|
|
97
|
+
* Retarget every live tween onto its DURABLE truth — the undo/redo chokepoint
|
|
98
|
+
* (petition I15). Undo does not pass through {@link guardedTransaction}: it is
|
|
99
|
+
* strata's own local batch, so a glide in flight when ⌘Z lands would otherwise
|
|
100
|
+
* complete at the pre-undo target and strand the cell exactly as above.
|
|
101
|
+
*
|
|
102
|
+
* `durablePositionOf` reads the committed value (the doc's, not the runtime's).
|
|
103
|
+
* A tween whose entity has no durable Position is left alone — nothing to
|
|
104
|
+
* reconcile against.
|
|
105
|
+
*/
|
|
106
|
+
export declare function retargetTweensToDoc(world: World, entities: Iterable<Entity>, durablePositionOf: (e: Entity) => {
|
|
107
|
+
x: number;
|
|
108
|
+
y: number;
|
|
109
|
+
} | undefined): void;
|
|
53
110
|
export {};
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* scene systems · doc kit · publish step · engine facade · reflector registry.
|
|
4
4
|
* Import wall: strata-ecs + @ice/kernel ONLY — never react/dom/three (enforced).
|
|
5
5
|
*/
|
|
6
|
-
export declare const CORE_VERSION = "0.
|
|
6
|
+
export declare const CORE_VERSION = "0.6.0";
|
|
7
7
|
export { defineSchemaOnce, hmrInvalidateOnSchemaChange } from "./boot/hmr";
|
|
8
8
|
export { All, Any, Not, Related, createWorld, defineQuery, defineSystem, defineTickSystem, enumOf, field, Local, } from "@vibecook/strata-ecs";
|
|
9
9
|
export type { Batch, Component, Condition, Entity, OrderPlace, Pipeline, Query, Relation, Resource, System, SystemCtx, Tag, TickSystem, World, } from "@vibecook/strata-ecs";
|
|
@@ -15,6 +15,12 @@ export { createPipelineRegistry, PHASE_GROUPS, type AnySystem, type PhaseGroup,
|
|
|
15
15
|
export { createReflectorRegistry, type ReflectorDef, type ReflectorObserve, type ReflectorQuerySpec, type ReflectorRegistry, } from "./engine/reflectors";
|
|
16
16
|
export { createEngine, type Engine, type EngineOpts, type FrameTelemetry, type PublishHook, type SystemRunRecord, } from "./engine/engine";
|
|
17
17
|
export { createFrameControl, SETTLE_CAP, type FrameControl } from "./engine/frame-control";
|
|
18
|
+
export { createGuestRegistry, GUEST_BUDGET, type GuestFactory, type GuestFrame, type GuestGeneration, type GuestInstance, type GuestLedgerRecord, type GuestRegistry, type GuestRegistryOpts, type GuestRun, type GuestSpec, type GuestStatus, } from "./engine/guests";
|
|
19
|
+
export { behaviors, defineBehavior } from "./behavior/define-behavior";
|
|
20
|
+
export { BEHAVIOR_DEFAULT_PHASE, BEHAVIOR_PHASES, type AnyBehaviorAttachSpec, type AnyBehaviorDef, type BehaviorAttachSpec, type BehaviorChanges, type BehaviorCtxBase, type BehaviorCtxFor, type BehaviorFrame, type BehaviorHandle, type BehaviorHooks, type BehaviorPhase, type BehaviorQuery, type BehaviorQuerySpec, type BehaviorRead, type BehaviorSchema, type BehaviorSpec, type BehaviorStore, type BehaviorTx, type BehaviorWorld, type DataOf, type DurableBehaviorCtx, type EphemeralBehaviorCtx, type PeerFacet, type RuntimeBehaviorCtx, } from "./behavior/types";
|
|
21
|
+
export { compileBehavior, isDurableEligible, type CompiledBehavior } from "./behavior/compile";
|
|
22
|
+
export { behaviorMarkerKey, classifyBehaviorVersion, docBehaviorVersion, stampBehaviorMarker, BEHAVIOR_MARKER_PREFIX, type BehaviorMeta, type BehaviorVerdict, } from "./behavior/migrate";
|
|
23
|
+
export { createBehaviorHarness, type BehaviorHarness, type BehaviorHarnessOpts, type HarnessCommit, } from "./behavior/harness";
|
|
18
24
|
export { devGuardsEnabled, setDevGuards } from "./guards/dev";
|
|
19
25
|
export { createLiveWriter, type LiveWriter, type LiveWriterOpts } from "./guards/live-writer";
|
|
20
26
|
export { guardedTransaction, type GuardedTx, type GuardedTxOpts } from "./guards/guarded-tx";
|
|
@@ -22,6 +28,7 @@ export { writeRuntimeResource } from "./guards/resource-writer";
|
|
|
22
28
|
export * from "./catalog";
|
|
23
29
|
export * from "./helpers/phase-set";
|
|
24
30
|
export * from "./helpers/version-stamps";
|
|
31
|
+
export * from "./helpers/churn-guard";
|
|
25
32
|
export * from "./ops/arrange";
|
|
26
33
|
export * from "./ops/selection";
|
|
27
34
|
export * from "./ops/cascade";
|
|
@@ -48,9 +55,9 @@ export type { DurableUndoStatusValue } from "@vibecook/strata-ecs/durable";
|
|
|
48
55
|
export { createDocSession, openDocSession, type DocSession, type DocSessionOpts, type OpenDocResult, } from "./doc/doc-kit";
|
|
49
56
|
export { runMigrations, type MigrationCtx, type MigrationOutcome } from "./doc/migrate";
|
|
50
57
|
export { p, type JsonShape, type PropSpec, type PropsDecl, type StandardSchemaV1 } from "./widget/props";
|
|
51
|
-
export { WidgetEquipped, defineWidget, renames, widgets, type SizeMode, type WidgetDef, type WidgetGroup, type WidgetInteraction, type WidgetPortDecl, type WidgetRename, type WidgetRenameEntry, type WidgetSurface, type WidgetType, } from "./widget/define-widget";
|
|
58
|
+
export { WidgetEquipped, defineWidget, renames, widgets, type SizeMode, type WidgetDef, type WidgetGroup, type WidgetInteraction, type WidgetPortDecl, type WidgetBehaviorDecl, type WidgetBehaviorEntry, type WidgetRename, type WidgetRenameEntry, type WidgetSurface, type WidgetType, } from "./widget/define-widget";
|
|
52
59
|
export { createWidgetEquipSystem } from "./widget/equip";
|
|
53
|
-
export { attachSpawnParent, spawnWidget, widgetSpawnInits, type SpawnWidgetOpts } from "./widget/spawn";
|
|
60
|
+
export { attachSpawnBehaviors, attachSpawnParent, spawnWidget, widgetSpawnInits, type SpawnWidgetOpts } from "./widget/spawn";
|
|
54
61
|
export { setWidgetProps } from "./widget/set-props";
|
|
55
62
|
export { createWidgetRuntime, installWidgetRuntime, type MountEntry, type WidgetMountStore, type WidgetRuntime, } from "./widget/mount-store";
|
|
56
63
|
export * from "./settings/defaults";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Component, Tag } from "@vibecook/strata-ecs";
|
|
2
|
+
import type { AnyBehaviorAttachSpec, AnyBehaviorDef } from "../behavior/types";
|
|
2
3
|
import { type Prefab } from "../schema/prefab";
|
|
3
4
|
import type { PropSpec, PropsDecl } from "./props";
|
|
4
5
|
export type WidgetSurface = "dom" | "gl";
|
|
@@ -37,6 +38,16 @@ export interface WidgetRenameEntry {
|
|
|
37
38
|
/** Legacy `<oldType>:<group>` components, index-aligned with `widget.groups`. */
|
|
38
39
|
readonly legacyGroups: readonly Component[];
|
|
39
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* A behavior pre-attachment on a widget type: the bare handle for defaults, or
|
|
43
|
+
* `Behavior.with({...})` for a starting value (design-009 §6).
|
|
44
|
+
*/
|
|
45
|
+
export type WidgetBehaviorDecl = AnyBehaviorDef | AnyBehaviorAttachSpec;
|
|
46
|
+
/** Normalized pre-attachment (registry truth for the spawn + equip paths). */
|
|
47
|
+
export interface WidgetBehaviorEntry {
|
|
48
|
+
readonly behavior: AnyBehaviorDef;
|
|
49
|
+
readonly data: Readonly<Record<string, unknown>>;
|
|
50
|
+
}
|
|
40
51
|
export interface WidgetInteraction {
|
|
41
52
|
readonly selectable?: boolean;
|
|
42
53
|
readonly movable?: boolean;
|
|
@@ -153,6 +164,21 @@ export interface WidgetDef {
|
|
|
153
164
|
* read-only, and arms the live zombie sweep on every writable session.
|
|
154
165
|
*/
|
|
155
166
|
readonly renamedFrom?: readonly WidgetRename[];
|
|
167
|
+
/**
|
|
168
|
+
* Behaviors every instance of this type carries (design-009 §6). The store
|
|
169
|
+
* class routes HOW, exactly as it routes everything else:
|
|
170
|
+
* - `durable` behaviors are attached as post-spawn `addComponent` calls in
|
|
171
|
+
* the SAME spawn transaction — document truth, so they sync and undo with
|
|
172
|
+
* the widget. (Not spawn-init overrides: those hit the prefab's
|
|
173
|
+
* all-builds eligibility throw in `instantiate.ts`.)
|
|
174
|
+
* - `runtime` behaviors are stamped at PROJECTION by the equip system,
|
|
175
|
+
* beside the capability tags. That is the only mechanism that also equips
|
|
176
|
+
* a widget arriving from a remote peer or a restored file, which a rider
|
|
177
|
+
* attached at spawn time would miss on every peer but the author's.
|
|
178
|
+
* Ephemeral behaviors are refused: an ephemeral instance IS the local
|
|
179
|
+
* presence peer, so it cannot ride a widget at all.
|
|
180
|
+
*/
|
|
181
|
+
readonly behaviors?: readonly WidgetBehaviorDecl[];
|
|
156
182
|
/**
|
|
157
183
|
* fromVersion → idempotent absolute transform (M9's `runMigrations` runs the
|
|
158
184
|
* chain at open; `prev`/return are flat prop records spanning every group).
|
|
@@ -203,6 +229,8 @@ export interface WidgetType {
|
|
|
203
229
|
/** Escape ownership under an exclusive claim ("release" = engine-reserved). */
|
|
204
230
|
readonly keyboardEscape: "release" | "widget";
|
|
205
231
|
readonly migrate: Readonly<Record<number, (prev: Record<string, unknown>) => Record<string, unknown>>>;
|
|
232
|
+
/** Pre-attached behaviors, normalized (spawn tx for durable, equip for runtime). */
|
|
233
|
+
readonly behaviors: readonly WidgetBehaviorEntry[];
|
|
206
234
|
/** Normalized author preview component (opaque; react narrows). null = none declared. */
|
|
207
235
|
readonly previewComponent: unknown;
|
|
208
236
|
/** Prop overrides for the default real-component preview mount (validated names). */
|
|
@@ -25,7 +25,7 @@ export interface StandardSchemaV1<Input = unknown, Output = Input> {
|
|
|
25
25
|
};
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
-
export type PropSpec = StringSpec | NumberSpec | BooleanSpec | EnumSpec | JsonSpec;
|
|
28
|
+
export type PropSpec = StringSpec | NumberSpec | BooleanSpec | EnumSpec | JsonSpec | EntityKeySpec;
|
|
29
29
|
export interface StringSpec extends StandardSchemaV1<unknown, string> {
|
|
30
30
|
readonly kind: "string";
|
|
31
31
|
readonly default?: string;
|
|
@@ -52,6 +52,19 @@ export interface JsonSpec extends StandardSchemaV1<unknown, unknown> {
|
|
|
52
52
|
/** Serialized default (derived from the inner root when not given). */
|
|
53
53
|
readonly default?: string;
|
|
54
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* A DURABLE entity reference (design-009 §4.2): the doc key of another entity,
|
|
57
|
+
* stored as a plain string cell. This is the ONLY legal cross-entity reference
|
|
58
|
+
* in behavior data — Law 7 bans `eid` from durable cells because a runtime
|
|
59
|
+
* handle means nothing to the peer that receives it, while a doc key survives
|
|
60
|
+
* the wire, the file, and a reload. Empty string = no reference; resolution is
|
|
61
|
+
* `ctx.entityFor(key)`, which may legitimately return undefined while the
|
|
62
|
+
* target is not yet projected.
|
|
63
|
+
*/
|
|
64
|
+
export interface EntityKeySpec extends StandardSchemaV1<unknown, string> {
|
|
65
|
+
readonly kind: "entityKey";
|
|
66
|
+
readonly default?: string;
|
|
67
|
+
}
|
|
55
68
|
/** Shapes legal only INSIDE p.json (validation-only, never components). */
|
|
56
69
|
export type JsonShape = {
|
|
57
70
|
readonly kind: "string";
|
|
@@ -91,8 +104,29 @@ export declare const p: {
|
|
|
91
104
|
json(inner: JsonShape, opts?: {
|
|
92
105
|
default?: unknown;
|
|
93
106
|
}): JsonSpec;
|
|
107
|
+
/**
|
|
108
|
+
* A durable reference to another entity by DOC KEY (design-009 §4.2).
|
|
109
|
+
* Behavior-facing: `defineWidget` props have no use for it (a widget's
|
|
110
|
+
* references are relations), but nothing rejects it there either — it is an
|
|
111
|
+
* ordinary string cell with a declared meaning.
|
|
112
|
+
*/
|
|
113
|
+
entityKey(opts?: {
|
|
114
|
+
default?: string;
|
|
115
|
+
}): EntityKeySpec;
|
|
94
116
|
array(item: JsonShape): JsonShape;
|
|
95
117
|
object(fields: Readonly<Record<string, JsonShape>>): JsonShape;
|
|
96
118
|
};
|
|
97
119
|
/** A widget props declaration: top-level named specs (the compiler's input). */
|
|
98
120
|
export type PropsDecl = Readonly<Record<string, PropSpec>>;
|
|
121
|
+
/**
|
|
122
|
+
* The CELL default for a spec — the value a whole-component write fills in for
|
|
123
|
+
* an untouched field, and the value a generated strata field declares.
|
|
124
|
+
*
|
|
125
|
+
* One function, three callers (widget spawn defaults, `defineWidget`'s
|
|
126
|
+
* essential set, `defineBehavior`'s attach defaults): each used to carry its
|
|
127
|
+
* own else-if chain over `spec.kind`, and each chain had to be found and
|
|
128
|
+
* amended when `p.entityKey` was added — one of them by inspection rather than
|
|
129
|
+
* by a type error, since the chains end in a catch-all `else`. It lives here,
|
|
130
|
+
* beside the specs whose shape it reads.
|
|
131
|
+
*/
|
|
132
|
+
export declare function defaultValueOf(spec: PropSpec): string | number | boolean;
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import type { Entity, OrderPlace, Relation, World } from "@vibecook/strata-ecs";
|
|
10
10
|
import type { DurableStore } from "@vibecook/strata-ecs/durable";
|
|
11
|
+
import { type GuardedTx } from "../guards/guarded-tx";
|
|
11
12
|
import { type ComponentInit } from "../schema/prefab";
|
|
13
|
+
import { type WidgetType } from "./define-widget";
|
|
12
14
|
export interface SpawnWidgetOpts {
|
|
13
15
|
readonly x: number;
|
|
14
16
|
readonly y: number;
|
|
@@ -47,4 +49,16 @@ export declare function widgetSpawnInits(type: string, opts: SpawnWidgetOpts): {
|
|
|
47
49
|
prefab: import("../schema/prefab").Prefab;
|
|
48
50
|
overrides: ComponentInit[];
|
|
49
51
|
};
|
|
52
|
+
/**
|
|
53
|
+
* Attach a widget type's DURABLE pre-attached behaviors (design-009 §6), as
|
|
54
|
+
* post-spawn `addComponent` calls inside the spawn transaction.
|
|
55
|
+
*
|
|
56
|
+
* Post-spawn rather than spawn-init overrides: `instantiate` checks every
|
|
57
|
+
* override against the prefab's eligible set, and a behavior is eligible on
|
|
58
|
+
* every entity by definition (BF-D6) — it is not in any prefab's set and never
|
|
59
|
+
* will be. Runtime pre-attachments are NOT here: they ride projection (the
|
|
60
|
+
* equip system), which is the only path that also equips a widget arriving
|
|
61
|
+
* from a peer or a restored file.
|
|
62
|
+
*/
|
|
63
|
+
export declare function attachSpawnBehaviors(tx: GuardedTx, widget: WidgetType, e: Entity): void;
|
|
50
64
|
export declare function spawnWidget(store: DurableStore, world: World, type: string, opts: SpawnWidgetOpts): Entity;
|
|
@@ -17,6 +17,15 @@
|
|
|
17
17
|
* The dt clamp is what makes resume safe: a freeze that lasted an hour hands
|
|
18
18
|
* `step` an enormous `now`, and core clamps the delta (frame-info.ts), so
|
|
19
19
|
* tweens advance by one ordinary frame instead of teleporting to their end.
|
|
20
|
+
*
|
|
21
|
+
* THE LOOP OUTLIVES A THROWING STEP (2026-08-15, petition I14). Until this fix
|
|
22
|
+
* the reschedule sat on the line AFTER `engine.step(now)`, so ONE throw from a
|
|
23
|
+
* publish hook (which propagate loudly by design — engine.ts) escaped the rAF
|
|
24
|
+
* callback, left `handle` un-renewed, and killed the canvas PERMANENTLY: with
|
|
25
|
+
* `parked` still false, `wake` early-returns forever, so even freeze/thaw could
|
|
26
|
+
* not restart it. Recovery needed a fresh `startRafLoop`. The next frame is now
|
|
27
|
+
* scheduled in a `finally`, so a faulting frame is loud but survivable — the
|
|
28
|
+
* throw still reaches the browser's error handler; only the death is gone.
|
|
20
29
|
*/
|
|
21
30
|
import type { Engine } from "@ice/core";
|
|
22
31
|
export declare function startRafLoop(engine: Engine): () => void;
|