55## Overview
66
77- ** Context & Goals** :
8+
89 - Unify and expand the scripting API to cover common gameplay needs while preserving sandbox safety and performance budgets.
910 - Align ` src/game/scripts/script-api.d.ts ` with runtime implementations in ` src/core/lib/scripting/ScriptAPI.ts ` and generate types from source to prevent drift.
1011 - Introduce additional subsystems to scripts: Events, Audio, Timers/Scheduling, Queries/Tags, Entity Ops (spawn/destroy/enable), and Input Actions.
1920## Proposed Solution
2021
2122- ** High‑level Summary** :
23+
2224 - Standardize API naming and surface; add a thin compatibility layer for existing global names to avoid breaking changes.
2325 - Add new script APIs: Event, Audio, Timer/Scheduler, Query/Tags, Prefab/EntityOps, and enrich Three helpers (raycast utils, visibility, animate already returns Promises).
2426 - Replace mock input with real Input Service, add action querying, and basic gamepad support passthrough.
2527 - Introduce typed parameters via Zod schemas and optional codegen for editor UX and script hints.
2628 - Generate ` script-api.d.ts ` from runtime TypeScript sources during dev (via existing Vite plugin) to keep types in sync.
29+ - Add first‑class Entity References in Script component UI and an ` Entities ` API to safely resolve and manipulate referenced entities.
2730
2831- ** Architecture & Directory Structure** :
32+
2933 ```
3034 src/core/lib/scripting/
3135 ├── ScriptAPI.ts # Align + extend (source of truth)
3842 │ ├── AudioAPI.ts # play/stop, positional tie-ins
3943 │ ├── TimerAPI.ts # timers, nextTick, frame waiters
4044 │ ├── QueryAPI.ts # tags, raycasts, entity queries
41- │ └── PrefabAPI.ts # spawn/destroy/enable/disable clones
45+ │ ├── PrefabAPI.ts # spawn/destroy/enable/disable clones
46+ │ └── EntitiesAPI.ts # resolve entity refs → safe IEntityScriptAPI
4247 └── ScriptExecutor.ts # Wire new APIs into context
4348
4449 src/plugins/
5055## Implementation Plan
5156
5257- ** Phase 1: API Alignment (0.5 day)**
58+
5359 1 . Normalize names across runtime and d.ts (keep ` IEntityScriptAPI ` , ` IThreeJSAPI ` , ` IInputAPI ` ).
5460 2 . Add ` compatAliases.ts ` to expose legacy global names (` IScriptEntityAPI ` , ` IScriptThreeAPI ` ) mapped to the canonical ones.
5561 3 . Audit ScriptExecutor context construction; ensure new fields are optional and back‑compatible.
5662
5763- ** Phase 2: Input & Time (0.5 day)**
64+
5865 1 . Replace mock Input in ` ScriptSystem ` with a real input provider (hook/service adapter).
5966 2 . Add ` input.actions.isPressed(name) ` and basic gamepad passthrough.
6067 3 . Expose ` time: { time, deltaTime, frameCount } ` and add ` TimerAPI ` with frame‑budgeted scheduler.
6168
6269- ** Phase 3: Events & Audio (0.75 day)**
70+
6371 1 . ` EventAPI ` with ` on ` , ` off ` , ` emit ` , auto‑scoped to entity and auto‑cleanup on destroy.
6472 2 . ` AudioAPI ` bridging ` SoundManager ` /Howler: ` play(url, opts) ` , ` stop(handle) ` , optional positional binding to current entity.
6573
6674- ** Phase 4: Query/Tags & Entity Ops (0.75 day)**
75+
6776 1 . ` QueryAPI ` : tag queries, ` findByTag(name) ` , ` raycastAll ` , ` raycastFirst ` , world position helpers.
6877 2 . ` PrefabAPI ` : ` spawn(prefabId, overrides?) ` , ` destroy(entityId) ` , ` setActive(entityId, active) ` with ECS checks.
78+ 3 . ` EntitiesAPI ` : resolve entity references to safe ` IEntityScriptAPI ` , lookup by id/name/tag, null‑safe wrappers.
6979
7080- ** Phase 5: Type Generation & Parameters (0.5 day)**
81+
7182 1 . Generate ` script-api.d.ts ` from runtime interfaces (Vite dev). Add banner "AUTO‑GENERATED".
7283 2 . Add optional ` parametersSchema?: z.ZodSchema ` per script; validate at runtime; expose typed ` parameters ` .
84+ 3 . Script component UI: add ` EntityRef ` and ` EntityRef[] ` parameter types with entity picker + drag‑drop from hierarchy. Persist ` { entityId?, guid?, path? } ` for stability across reloads.
7385
7486- ** Phase 6: Tests & Docs (0.5 day)**
7587 1 . Unit tests for each API and context lifecycle cleanup.
99111## Technical Details
100112
101113### Canonical interfaces (source of truth)
114+
102115``` ts
103116// src/core/lib/scripting/ScriptAPI.ts (additions only)
117+ export interface IEntityRef {
118+ entityId? : number ; // fast path when stable
119+ guid? : string ; // stable id if available
120+ path? : string ; // fallback scene path (e.g., Root/Enemy[2]/Weapon)
121+ }
122+
123+ export interface IEntitiesAPI {
124+ fromRef(ref : IEntityRef | number | string ): IEntityScriptAPI | null ; // accepts id/guid/path
125+ get(entityId : number ): IEntityScriptAPI | null ;
126+ findByName(name : string ): IEntityScriptAPI [];
127+ findByTag(tag : string ): IEntityScriptAPI [];
128+ exists(entityId : number ): boolean ;
129+ }
130+
104131export interface IEventAPI {
105132 on<T extends string >(type : T , handler : (payload : unknown ) => void ): () => void ;
106133 off<T extends string >(type : T , handler : (payload : unknown ) => void ): void ;
@@ -141,11 +168,13 @@ export interface IScriptContext {
141168 timer: ITimerAPI ;
142169 query: IQueryAPI ;
143170 prefab: IPrefabAPI ;
171+ entities: IEntitiesAPI ; // NEW: resolve entity references safely
144172 parameters: Record <string , unknown >; // validated when schema provided
145173}
146174```
147175
148176### Context wiring
177+
149178``` ts
150179// src/core/lib/scripting/ScriptExecutor.ts (inside createScriptContext)
151180return {
@@ -160,11 +189,13 @@ return {
160189 timer: createTimerAPI (entityId ),
161190 query: createQueryAPI (entityId , () => getSceneRef ()),
162191 prefab: createPrefabAPI (entityId ),
192+ entities: createEntitiesAPI (),
163193 parameters ,
164194};
165195```
166196
167197### Event API
198+
168199``` ts
169200// src/core/lib/scripting/apis/EventAPI.ts
170201import { emitter } from ' @/core/lib/events' ;
@@ -184,6 +215,7 @@ export function createEventAPI(entityId: number): IEventAPI {
184215```
185216
186217### Timer API (frame‑budgeted)
218+
187219``` ts
188220// src/core/lib/scripting/apis/TimerAPI.ts
189221export function createTimerAPI(entityId : number ): ITimerAPI {
@@ -193,18 +225,36 @@ export function createTimerAPI(entityId: number): ITimerAPI {
193225```
194226
195227### Query API
228+
196229``` ts
197230// src/core/lib/scripting/apis/QueryAPI.ts
198231export function createQueryAPI(entityId : number , getScene : () => THREE .Scene | null ): IQueryAPI {
199232 return {
200233 findByTag : (tag ) => /* map tag registry refs to entityIds */ [],
201- raycastFirst : (o , d ) => createThreeJSAPI (entityId , () => null , getScene ).raycast (o , d )[0 ] ?? null ,
234+ raycastFirst : (o , d ) =>
235+ createThreeJSAPI (entityId , () => null , getScene ).raycast (o , d )[0 ] ?? null ,
202236 raycastAll : (o , d ) => createThreeJSAPI (entityId , () => null , getScene ).raycast (o , d ),
203237 };
204238}
205239```
206240
241+ ### Entities API
242+
243+ ``` ts
244+ // src/core/lib/scripting/apis/EntitiesAPI.ts
245+ export function createEntitiesAPI(): IEntitiesAPI {
246+ return {
247+ fromRef : (ref ) => /* resolve via entityId → guid → path; wrap with createEntityAPI */ null ,
248+ get : (id ) => /* wrap or null */ null ,
249+ findByName : (name ) => /* search registry by name */ [],
250+ findByTag : (tag ) => /* reuse Query/Tags mapping, but return IEntityScriptAPI */ [],
251+ exists : (id ) => /* check ECS/componentRegistry */ false ,
252+ };
253+ }
254+ ```
255+
207256### DTS generation (dev only)
257+
208258``` ts
209259// src/core/lib/scripting/adapters/dtsGenerator.ts
210260// Walk ScriptAPI.ts interfaces, emit a global ambient d.ts mirroring names and JSDoc.
@@ -247,9 +297,22 @@ function onStart() {
247297}
248298```
249299
300+ ``` ts
301+ // 4) Entity references from Script component UI
302+ // Assume parameters.target is of type EntityRef (set via Inspector picker)
303+ function onStart() {
304+ const target = entities .fromRef (parameters .target as EntityRef );
305+ if (target ) {
306+ const [x, y, z] = target .transform .position ;
307+ target .transform .setPosition (x , y + 1 , z );
308+ }
309+ }
310+ ```
311+
250312## Testing Strategy
251313
252314- ** Unit Tests** :
315+
253316 - EventAPI: subscribe/emit/off, auto‑cleanup on destroy.
254317 - TimerAPI: timeout/interval/nextTick/waitFrames; frame budget adherence.
255318 - AudioAPI: play/stop, positional attach when entity has mesh.
@@ -263,14 +326,15 @@ function onStart() {
263326
264327## Edge Cases
265328
266- | Edge Case | Remediation |
267- | --- | --- |
268- | Long‑running timers blocking frame | Scheduler slices work; cap per‑frame callbacks; warn via console.
269- | Event listener leak | Auto‑unsubscribe on ` onDestroy ` ; scoped off function set.
270- | Missing scene/mesh for raycast/audio attach | Return empty results/no‑op, log debug message.
271- | d.ts drift from runtime | Generate from source each dev boot; add CI check.
272- | Old global names used in scripts | Provide alias globals; deprecate with console warnings.
273- | Input device differences | Fallbacks for unsupported gamepad features; document mapping.
329+ | Edge Case | Remediation |
330+ | ------------------------------------------- | ----------------------------------------------------------------- |
331+ | Long‑running timers blocking frame | Scheduler slices work; cap per‑frame callbacks; warn via console. |
332+ | Event listener leak | Auto‑unsubscribe on ` onDestroy ` ; scoped off function set. |
333+ | Missing scene/mesh for raycast/audio attach | Return empty results/no‑op, log debug message. |
334+ | d.ts drift from runtime | Generate from source each dev boot; add CI check. |
335+ | Old global names used in scripts | Provide alias globals; deprecate with console warnings. |
336+ | Input device differences | Fallbacks for unsupported gamepad features; document mapping. |
337+ | Stale/missing entity reference | ` entities.fromRef() ` returns null; show warning; optional repair. |
274338
275339## Sequence Diagram
276340
@@ -294,13 +358,14 @@ sequenceDiagram
294358
295359## Risks & Mitigations
296360
297- | Risk | Mitigation |
298- | --- | --- |
299- | Security surface increases | Keep whitelist, proxies; no direct Three mutables; scoped events.
300- | Performance regressions | Frame budgets in scheduler; Promises resolved via RAF; benchmarks.
301- | Breaking changes in API names | Provide compatibility aliases; deprecation warnings; migration doc.
302- | Type drift | Automated d.ts generation and CI check.
303- | Input provider variability | Feature detection; no‑op stubs when not supported.
361+ | Risk | Mitigation |
362+ | ----------------------------- | ------------------------------------------------------------------- |
363+ | Security surface increases | Keep whitelist, proxies; no direct Three mutables; scoped events. |
364+ | Performance regressions | Frame budgets in scheduler; Promises resolved via RAF; benchmarks. |
365+ | Breaking changes in API names | Provide compatibility aliases; deprecation warnings; migration doc. |
366+ | Type drift | Automated d.ts generation and CI check. |
367+ | Input provider variability | Feature detection; no‑op stubs when not supported. |
368+ | Entity identity across reload | Store guid/path fallback; runtime repair to current entityId. |
304369
305370## Timeline
306371
@@ -319,6 +384,7 @@ sequenceDiagram
319384- Input in scripts uses real provider; action querying works.
320385- Back‑compat names continue to work with deprecation notices.
321386- Unit and integration tests pass; CI check prevents d.ts drift.
387+ - Script component UI supports ` EntityRef ` parameters with picker/drag‑drop; scripts resolve refs via ` entities.fromRef() ` .
322388
323389## Conclusion
324390
@@ -331,5 +397,3 @@ This plan establishes a consistent, ergonomic, and safe Script API that covers e
331397- Three.js + R3F for scene access; entity mesh registration via ` ThreeJSEntityRegistry ` .
332398- Zod for parameter validation; TS path aliases per ` tsconfig ` .
333399- No physics API expansion in this phase; add later if required by ` 2-10-physics-system.md ` .
334-
335-
0 commit comments