@thewhateverapp/tile-sdk 0.13.37 → 0.14.1

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.
Files changed (38) hide show
  1. package/dist/scene/SceneContext.d.ts +167 -0
  2. package/dist/scene/SceneContext.d.ts.map +1 -0
  3. package/dist/scene/SceneContext.js +86 -0
  4. package/dist/scene/SceneFromJson.d.ts +27 -0
  5. package/dist/scene/SceneFromJson.d.ts.map +1 -0
  6. package/dist/scene/SceneFromJson.js +74 -0
  7. package/dist/scene/SceneRenderer.d.ts +26 -0
  8. package/dist/scene/SceneRenderer.d.ts.map +1 -0
  9. package/dist/scene/SceneRenderer.js +201 -0
  10. package/dist/scene/camera/CameraController.d.ts +6 -0
  11. package/dist/scene/camera/CameraController.d.ts.map +1 -0
  12. package/dist/scene/camera/CameraController.js +84 -0
  13. package/dist/scene/components/ComponentRunner.d.ts +22 -0
  14. package/dist/scene/components/ComponentRunner.d.ts.map +1 -0
  15. package/dist/scene/components/ComponentRunner.js +197 -0
  16. package/dist/scene/effects/GlowFilter.d.ts +38 -0
  17. package/dist/scene/effects/GlowFilter.d.ts.map +1 -0
  18. package/dist/scene/effects/GlowFilter.js +40 -0
  19. package/dist/scene/effects/ParticleSystem.d.ts +52 -0
  20. package/dist/scene/effects/ParticleSystem.d.ts.map +1 -0
  21. package/dist/scene/effects/ParticleSystem.js +107 -0
  22. package/dist/scene/entities/EntityRenderer.d.ts +14 -0
  23. package/dist/scene/entities/EntityRenderer.d.ts.map +1 -0
  24. package/dist/scene/entities/EntityRenderer.js +203 -0
  25. package/dist/scene/index.d.ts +46 -0
  26. package/dist/scene/index.d.ts.map +1 -0
  27. package/dist/scene/index.js +50 -0
  28. package/dist/scene/input/InputManager.d.ts +18 -0
  29. package/dist/scene/input/InputManager.d.ts.map +1 -0
  30. package/dist/scene/input/InputManager.js +86 -0
  31. package/dist/scene/physics/PhysicsEngine.d.ts +15 -0
  32. package/dist/scene/physics/PhysicsEngine.d.ts.map +1 -0
  33. package/dist/scene/physics/PhysicsEngine.js +252 -0
  34. package/dist/scene/timeline/TimelineExecutor.d.ts +6 -0
  35. package/dist/scene/timeline/TimelineExecutor.d.ts.map +1 -0
  36. package/dist/scene/timeline/TimelineExecutor.js +236 -0
  37. package/dist/spec/schema.d.ts +12 -12
  38. package/package.json +14 -2
@@ -0,0 +1,167 @@
1
+ import { type MutableRefObject } from 'react';
2
+ import type { SceneSpecV1, Entity, CameraConfig } from '@thewhateverapp/scene-sdk';
3
+ import type { Engine, Body } from 'matter-js';
4
+ /**
5
+ * Runtime entity state - mutable for performance
6
+ */
7
+ export interface EntityState {
8
+ /** Entity definition from spec */
9
+ entity: Entity;
10
+ /** Current position (updated by physics/components) */
11
+ x: number;
12
+ y: number;
13
+ /** Current rotation in radians */
14
+ rotation: number;
15
+ /** Current scale */
16
+ scaleX: number;
17
+ scaleY: number;
18
+ /** Current velocity (for physics/movement) */
19
+ velocityX: number;
20
+ velocityY: number;
21
+ /** Whether entity is visible */
22
+ visible: boolean;
23
+ /** Current fill color (can be animated) */
24
+ fill?: string;
25
+ /** Current alpha (can be animated) */
26
+ alpha: number;
27
+ /** Matter.js body (if entity has physics) */
28
+ body?: Body;
29
+ /** Whether entity is destroyed */
30
+ destroyed: boolean;
31
+ /** Custom component state */
32
+ componentState: Record<string, unknown>;
33
+ }
34
+ /**
35
+ * Player-specific state for dash games
36
+ */
37
+ export interface PlayerState {
38
+ /** Is player on ground */
39
+ grounded: boolean;
40
+ /** Is player dead */
41
+ dead: boolean;
42
+ /** Current jump count (for double jump) */
43
+ jumpCount: number;
44
+ /** Last checkpoint position */
45
+ checkpointX: number;
46
+ checkpointY: number;
47
+ /** Is touching a jump orb */
48
+ touchingOrb: string | null;
49
+ /** Gravity direction (1 = down, -1 = up) */
50
+ gravityDir: number;
51
+ /** Current speed multiplier */
52
+ speedMultiplier: number;
53
+ /** Death count */
54
+ deaths: number;
55
+ /** Level complete */
56
+ complete: boolean;
57
+ }
58
+ /**
59
+ * Camera state
60
+ */
61
+ export interface CameraState {
62
+ x: number;
63
+ y: number;
64
+ zoom: number;
65
+ shakeIntensity: number;
66
+ shakeTimeRemaining: number;
67
+ }
68
+ /**
69
+ * Input state
70
+ */
71
+ export interface InputState {
72
+ /** Is jump/action pressed (space, tap, or up arrow) */
73
+ jumpPressed: boolean;
74
+ /** Is currently touching (for hold detection) */
75
+ touching: boolean;
76
+ /** Keys currently held */
77
+ keys: Record<string, boolean>;
78
+ }
79
+ /**
80
+ * Timeline state
81
+ */
82
+ export interface TimelineState {
83
+ /** Elapsed time in ms */
84
+ elapsedMs: number;
85
+ /** Current beat (based on BPM) */
86
+ currentBeat: number;
87
+ /** Index of next event to process */
88
+ nextEventIndex: number;
89
+ /** Active tweens */
90
+ activeTweens: ActiveTween[];
91
+ }
92
+ /**
93
+ * Active tween animation
94
+ */
95
+ export interface ActiveTween {
96
+ targetId: string;
97
+ property: string;
98
+ startValue: number;
99
+ endValue: number;
100
+ startTime: number;
101
+ duration: number;
102
+ easing: string;
103
+ }
104
+ /**
105
+ * Scene context value - all scene runtime state
106
+ */
107
+ export interface SceneContextValue {
108
+ /** Compiled scene spec */
109
+ spec: SceneSpecV1;
110
+ /** Entity states by ID */
111
+ entities: MutableRefObject<Map<string, EntityState>>;
112
+ /** Layer order (front to back) */
113
+ layers: string[];
114
+ /** Player state (for dash games) */
115
+ player: MutableRefObject<PlayerState>;
116
+ /** Camera state */
117
+ camera: MutableRefObject<CameraState>;
118
+ /** Input state */
119
+ input: MutableRefObject<InputState>;
120
+ /** Timeline state */
121
+ timeline: MutableRefObject<TimelineState>;
122
+ /** Matter.js engine */
123
+ engine: MutableRefObject<Engine | null>;
124
+ /** Emit an event to parent */
125
+ emitEvent: (event: string, data?: unknown) => void;
126
+ /** Get entity state by ID */
127
+ getEntity: (id: string) => EntityState | undefined;
128
+ /** Spawn an entity from prefab */
129
+ spawnEntity: (prefabId: string, x: number, y: number) => void;
130
+ /** Destroy an entity */
131
+ destroyEntity: (id: string) => void;
132
+ /** Respawn player at checkpoint */
133
+ respawnPlayer: () => void;
134
+ /** BPM for beat calculations */
135
+ bpm: number;
136
+ /** Start time for timeline */
137
+ startTime: number;
138
+ }
139
+ /**
140
+ * Scene context
141
+ */
142
+ export declare const SceneContext: import("react").Context<SceneContextValue | null>;
143
+ /**
144
+ * Hook to access scene context
145
+ */
146
+ export declare function useScene(): SceneContextValue;
147
+ /**
148
+ * Create initial entity state from entity definition
149
+ */
150
+ export declare function createEntityState(entity: Entity): EntityState;
151
+ /**
152
+ * Create initial player state
153
+ */
154
+ export declare function createPlayerState(): PlayerState;
155
+ /**
156
+ * Create initial camera state from config
157
+ */
158
+ export declare function createCameraState(config?: CameraConfig): CameraState;
159
+ /**
160
+ * Create initial input state
161
+ */
162
+ export declare function createInputState(): InputState;
163
+ /**
164
+ * Create initial timeline state
165
+ */
166
+ export declare function createTimelineState(): TimelineState;
167
+ //# sourceMappingURL=SceneContext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SceneContext.d.ts","sourceRoot":"","sources":["../../src/scene/SceneContext.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,86 @@
1
+ 'use client';
2
+ import { createContext, useContext } from 'react';
3
+ /**
4
+ * Scene context
5
+ */
6
+ export const SceneContext = createContext(null);
7
+ /**
8
+ * Hook to access scene context
9
+ */
10
+ export function useScene() {
11
+ const context = useContext(SceneContext);
12
+ if (!context) {
13
+ throw new Error('useScene must be used within a SceneRenderer');
14
+ }
15
+ return context;
16
+ }
17
+ /**
18
+ * Create initial entity state from entity definition
19
+ */
20
+ export function createEntityState(entity) {
21
+ return {
22
+ entity,
23
+ x: entity.transform.x,
24
+ y: entity.transform.y,
25
+ rotation: (entity.transform.rotation ?? 0) * (Math.PI / 180), // Convert to radians
26
+ scaleX: entity.transform.scaleX ?? 1,
27
+ scaleY: entity.transform.scaleY ?? 1,
28
+ velocityX: 0,
29
+ velocityY: 0,
30
+ visible: entity.render?.visible ?? true,
31
+ fill: entity.render?.fill,
32
+ alpha: entity.render?.alpha ?? 1,
33
+ destroyed: false,
34
+ componentState: {},
35
+ };
36
+ }
37
+ /**
38
+ * Create initial player state
39
+ */
40
+ export function createPlayerState() {
41
+ return {
42
+ grounded: false,
43
+ dead: false,
44
+ jumpCount: 0,
45
+ checkpointX: 0,
46
+ checkpointY: 0,
47
+ touchingOrb: null,
48
+ gravityDir: 1,
49
+ speedMultiplier: 1,
50
+ deaths: 0,
51
+ complete: false,
52
+ };
53
+ }
54
+ /**
55
+ * Create initial camera state from config
56
+ */
57
+ export function createCameraState(config) {
58
+ return {
59
+ x: config?.initialX ?? 0,
60
+ y: config?.initialY ?? 0,
61
+ zoom: config?.zoom ?? 1,
62
+ shakeIntensity: 0,
63
+ shakeTimeRemaining: 0,
64
+ };
65
+ }
66
+ /**
67
+ * Create initial input state
68
+ */
69
+ export function createInputState() {
70
+ return {
71
+ jumpPressed: false,
72
+ touching: false,
73
+ keys: {},
74
+ };
75
+ }
76
+ /**
77
+ * Create initial timeline state
78
+ */
79
+ export function createTimelineState() {
80
+ return {
81
+ elapsedMs: 0,
82
+ currentBeat: 0,
83
+ nextEventIndex: 0,
84
+ activeTweens: [],
85
+ };
86
+ }
@@ -0,0 +1,27 @@
1
+ import React from 'react';
2
+ import { type SceneRendererProps } from './SceneRenderer';
3
+ /**
4
+ * Props for SceneFromJson
5
+ */
6
+ export interface SceneFromJsonProps extends Omit<SceneRendererProps, 'spec'> {
7
+ /** The scene spec JSON object (imported from scene.json) */
8
+ json: unknown;
9
+ /** Show validation errors in UI instead of throwing */
10
+ showErrors?: boolean;
11
+ }
12
+ /**
13
+ * SceneFromJson - Renders a scene from a JSON object with validation
14
+ *
15
+ * @example
16
+ * ```tsx
17
+ * // In your tile page:
18
+ * import { SceneFromJson } from '@thewhateverapp/tile-sdk/scene';
19
+ * import sceneJson from './scene.json';
20
+ *
21
+ * export default function TilePage() {
22
+ * return <SceneFromJson json={sceneJson} />;
23
+ * }
24
+ * ```
25
+ */
26
+ export declare function SceneFromJson({ json, showErrors, onEvent, ...props }: SceneFromJsonProps): React.JSX.Element;
27
+ //# sourceMappingURL=SceneFromJson.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SceneFromJson.d.ts","sourceRoot":"","sources":["../../src/scene/SceneFromJson.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,EAAiB,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAEzE;;GAEG;AACH,MAAM,WAAW,kBAAmB,SAAQ,IAAI,CAAC,kBAAkB,EAAE,MAAM,CAAC;IAC1E,4DAA4D;IAC5D,IAAI,EAAE,OAAO,CAAC;IACd,uDAAuD;IACvD,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,EAC5B,IAAI,EACJ,UAAiB,EACjB,OAAO,EACP,GAAG,KAAK,EACT,EAAE,kBAAkB,qBAuEpB"}
@@ -0,0 +1,74 @@
1
+ 'use client';
2
+ import React from 'react';
3
+ import { validateScene } from '@thewhateverapp/scene-sdk';
4
+ import { SceneRenderer } from './SceneRenderer';
5
+ /**
6
+ * SceneFromJson - Renders a scene from a JSON object with validation
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * // In your tile page:
11
+ * import { SceneFromJson } from '@thewhateverapp/tile-sdk/scene';
12
+ * import sceneJson from './scene.json';
13
+ *
14
+ * export default function TilePage() {
15
+ * return <SceneFromJson json={sceneJson} />;
16
+ * }
17
+ * ```
18
+ */
19
+ export function SceneFromJson({ json, showErrors = true, onEvent, ...props }) {
20
+ // Validate the JSON
21
+ const validationResult = React.useMemo(() => {
22
+ try {
23
+ return validateScene(json);
24
+ }
25
+ catch (error) {
26
+ return {
27
+ valid: false,
28
+ errors: [{ path: 'root', message: String(error), code: 'PARSE_ERROR' }],
29
+ warnings: [],
30
+ };
31
+ }
32
+ }, [json]);
33
+ // Report validation errors to agent for correction
34
+ React.useEffect(() => {
35
+ if (!validationResult.valid) {
36
+ const errorMessage = `Scene Validation Errors:\n${validationResult.errors
37
+ .map((e) => `${e.path}: ${e.message}`)
38
+ .join('\n')}`;
39
+ // Report to agent via preview error reporting mechanism
40
+ if (typeof window !== 'undefined' && window.__PREVIEW_REPORT_ERROR__) {
41
+ window.__PREVIEW_REPORT_ERROR__(errorMessage, null, null);
42
+ }
43
+ }
44
+ }, [validationResult]);
45
+ // Show errors if validation failed
46
+ if (!validationResult.valid) {
47
+ if (showErrors) {
48
+ return (React.createElement("div", { style: {
49
+ width: '100%',
50
+ height: '100%',
51
+ backgroundColor: '#1a0a0a',
52
+ color: '#ff4444',
53
+ padding: 16,
54
+ fontFamily: 'monospace',
55
+ fontSize: 12,
56
+ overflow: 'auto',
57
+ } },
58
+ React.createElement("div", { style: { fontWeight: 'bold', marginBottom: 8 } }, "Scene Validation Errors:"),
59
+ validationResult.errors.map((error, i) => (React.createElement("div", { key: i, style: { marginBottom: 4 } },
60
+ React.createElement("span", { style: { color: '#ff8888' } },
61
+ error.path,
62
+ ":"),
63
+ " ",
64
+ error.message)))));
65
+ }
66
+ // Throw if not showing errors
67
+ throw new Error(`Scene validation failed: ${validationResult.errors.map((e) => e.message).join(', ')}`);
68
+ }
69
+ // Show warnings in console
70
+ if (validationResult.warnings.length > 0) {
71
+ console.warn('Scene validation warnings:', validationResult.warnings);
72
+ }
73
+ return (React.createElement(SceneRenderer, { spec: json, onEvent: onEvent, ...props }));
74
+ }
@@ -0,0 +1,26 @@
1
+ import React from 'react';
2
+ import type { SceneSpecV1 } from '@thewhateverapp/scene-sdk';
3
+ /**
4
+ * Props for SceneRenderer
5
+ */
6
+ export interface SceneRendererProps {
7
+ /** Scene specification to render */
8
+ spec: SceneSpecV1;
9
+ /** Callback for scene events (player.death, level.complete, etc.) */
10
+ onEvent?: (event: string, data?: unknown) => void;
11
+ /** Whether the scene is paused */
12
+ paused?: boolean;
13
+ /** Width override (default: TILE_WIDTH) */
14
+ width?: number;
15
+ /** Height override (default: TILE_HEIGHT) */
16
+ height?: number;
17
+ /** Enable debug rendering */
18
+ debug?: boolean;
19
+ }
20
+ /**
21
+ * SceneRenderer - Renders a SceneSpecV1 with physics, components, and timeline
22
+ */
23
+ export declare function SceneRenderer({ spec: inputSpec, onEvent, paused, width, height, debug, }: SceneRendererProps): React.JSX.Element;
24
+ import { useScene } from './SceneContext';
25
+ export { useScene };
26
+ //# sourceMappingURL=SceneRenderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SceneRenderer.d.ts","sourceRoot":"","sources":["../../src/scene/SceneRenderer.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAkD,MAAM,OAAO,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAU,MAAM,2BAA2B,CAAC;AAqBrE;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,oCAAoC;IACpC,IAAI,EAAE,WAAW,CAAC;IAClB,qEAAqE;IACrE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD,kCAAkC;IAClC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,2CAA2C;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6CAA6C;IAC7C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6BAA6B;IAC7B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,EAC5B,IAAI,EAAE,SAAS,EACf,OAAO,EACP,MAAc,EACd,KAAkB,EAClB,MAAoB,EACpB,KAAa,GACd,EAAE,kBAAkB,qBA8BpB;AAuOD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,CAAC"}
@@ -0,0 +1,201 @@
1
+ 'use client';
2
+ import React, { useEffect, useRef, useCallback, useMemo } from 'react';
3
+ import { compileScene } from '@thewhateverapp/scene-sdk';
4
+ import { PixiGame, useGameLoop, Container } from '../pixi';
5
+ import { SceneContext, createEntityState, createPlayerState, createCameraState, createInputState, createTimelineState, } from './SceneContext';
6
+ import { EntityRenderer } from './entities/EntityRenderer';
7
+ import { usePhysicsEngine } from './physics/PhysicsEngine';
8
+ import { useInputManager } from './input/InputManager';
9
+ import { useComponentRunner } from './components/ComponentRunner';
10
+ import { useTimelineExecutor } from './timeline/TimelineExecutor';
11
+ import { useCameraController } from './camera/CameraController';
12
+ import { TILE_WIDTH, TILE_HEIGHT } from '../react/PixiGame';
13
+ /**
14
+ * SceneRenderer - Renders a SceneSpecV1 with physics, components, and timeline
15
+ */
16
+ export function SceneRenderer({ spec: inputSpec, onEvent, paused = false, width = TILE_WIDTH, height = TILE_HEIGHT, debug = false, }) {
17
+ // Compile patterns once on mount
18
+ const compiledSpec = useMemo(() => compileScene(inputSpec), [inputSpec]);
19
+ // Get background color from spec
20
+ const backgroundColor = useMemo(() => {
21
+ const bg = compiledSpec.style?.background?.color;
22
+ if (bg) {
23
+ return parseInt(bg.replace('#', ''), 16);
24
+ }
25
+ return 0x0a0a1a; // Default dark background
26
+ }, [compiledSpec]);
27
+ return (React.createElement(PixiGame, { width: width, height: height, background: backgroundColor, paused: paused },
28
+ React.createElement(SceneContent, { spec: compiledSpec, onEvent: onEvent, paused: paused, width: width, height: height, debug: debug })));
29
+ }
30
+ /**
31
+ * Inner component that runs inside PixiGame context
32
+ */
33
+ function SceneContent({ spec, onEvent, paused, width, height, debug, }) {
34
+ // Initialize refs for all state
35
+ const entitiesRef = useRef(new Map());
36
+ const playerRef = useRef(createPlayerState());
37
+ const cameraRef = useRef(createCameraState(spec.camera));
38
+ const inputRef = useRef(createInputState());
39
+ const timelineRef = useRef(createTimelineState());
40
+ const engineRef = useRef(null);
41
+ const startTimeRef = useRef(Date.now());
42
+ // Get layers from spec or use defaults
43
+ const layers = useMemo(() => {
44
+ if (spec.layers && spec.layers.length > 0) {
45
+ return spec.layers;
46
+ }
47
+ // Default dash layers
48
+ return ['bg', 'deco', 'solids', 'hazards', 'orbs', 'player', 'fx', 'ui'];
49
+ }, [spec.layers]);
50
+ // Get BPM from spec
51
+ const bpm = spec.meta?.bpm ?? 120;
52
+ // Event emitter callback
53
+ const emitEvent = useCallback((event, data) => {
54
+ onEvent?.(event, data);
55
+ }, [onEvent]);
56
+ // Get entity by ID
57
+ const getEntity = useCallback((id) => {
58
+ return entitiesRef.current.get(id);
59
+ }, []);
60
+ // Spawn entity from prefab
61
+ const spawnEntity = useCallback((prefabId, x, y) => {
62
+ const prefab = spec.prefabs?.[prefabId];
63
+ if (!prefab) {
64
+ console.warn(`Prefab not found: ${prefabId}`);
65
+ return;
66
+ }
67
+ const id = `${prefabId}_${Date.now()}`;
68
+ const entity = {
69
+ id,
70
+ kind: prefab.kind ?? 'rect',
71
+ layer: prefab.layer ?? 'fx',
72
+ transform: {
73
+ x,
74
+ y,
75
+ ...prefab.transform,
76
+ },
77
+ geom: prefab.geom ?? { w: 20, h: 20 },
78
+ render: prefab.render,
79
+ body: prefab.body,
80
+ tags: prefab.tags,
81
+ components: prefab.components,
82
+ };
83
+ entitiesRef.current.set(id, createEntityState(entity));
84
+ }, [spec.prefabs]);
85
+ // Destroy entity
86
+ const destroyEntity = useCallback((id) => {
87
+ const entity = entitiesRef.current.get(id);
88
+ if (entity) {
89
+ entity.destroyed = true;
90
+ // Body cleanup happens in physics engine
91
+ }
92
+ }, []);
93
+ // Respawn player at checkpoint
94
+ const respawnPlayer = useCallback(() => {
95
+ const player = playerRef.current;
96
+ player.dead = false;
97
+ player.jumpCount = 0;
98
+ player.gravityDir = 1;
99
+ // Find player entity and reset position
100
+ for (const [, state] of entitiesRef.current) {
101
+ if (state.entity.tags?.includes('player')) {
102
+ state.x = player.checkpointX || 128;
103
+ state.y = player.checkpointY || (height - 100);
104
+ state.velocityX = 0;
105
+ state.velocityY = 0;
106
+ state.rotation = 0;
107
+ break;
108
+ }
109
+ }
110
+ emitEvent('player.respawn', { x: player.checkpointX, y: player.checkpointY });
111
+ }, [height, emitEvent]);
112
+ // Build context value
113
+ const contextValue = useMemo(() => ({
114
+ spec,
115
+ entities: entitiesRef,
116
+ layers,
117
+ player: playerRef,
118
+ camera: cameraRef,
119
+ input: inputRef,
120
+ timeline: timelineRef,
121
+ engine: engineRef,
122
+ emitEvent,
123
+ getEntity,
124
+ spawnEntity,
125
+ destroyEntity,
126
+ respawnPlayer,
127
+ bpm,
128
+ startTime: startTimeRef.current,
129
+ }), [spec, layers, bpm, emitEvent, getEntity, spawnEntity, destroyEntity, respawnPlayer]);
130
+ // Initialize entities from spec
131
+ useEffect(() => {
132
+ const entityMap = new Map();
133
+ for (const entity of spec.entities) {
134
+ entityMap.set(entity.id, createEntityState(entity));
135
+ }
136
+ entitiesRef.current = entityMap;
137
+ // Find player and set initial checkpoint
138
+ for (const entity of spec.entities) {
139
+ if (entity.tags?.includes('player')) {
140
+ playerRef.current.checkpointX = entity.transform.x;
141
+ playerRef.current.checkpointY = entity.transform.y;
142
+ break;
143
+ }
144
+ }
145
+ // Reset start time
146
+ startTimeRef.current = Date.now();
147
+ timelineRef.current = createTimelineState();
148
+ }, [spec]);
149
+ // Initialize physics engine
150
+ usePhysicsEngine(contextValue, width, height);
151
+ // Initialize input handling
152
+ useInputManager(contextValue);
153
+ // Run component logic
154
+ useComponentRunner(contextValue);
155
+ // Run timeline
156
+ useTimelineExecutor(contextValue);
157
+ // Update camera
158
+ useCameraController(contextValue, width, height);
159
+ // Main game loop
160
+ useGameLoop((delta) => {
161
+ if (paused)
162
+ return;
163
+ // Update timeline elapsed time
164
+ const now = Date.now();
165
+ timelineRef.current.elapsedMs = now - startTimeRef.current;
166
+ timelineRef.current.currentBeat = (timelineRef.current.elapsedMs / 1000) * (bpm / 60);
167
+ }, !paused);
168
+ // Render entities grouped by layer
169
+ const renderLayers = useMemo(() => {
170
+ return layers.map((layer, layerIndex) => (React.createElement(Container, { key: layer, zIndex: layerIndex },
171
+ React.createElement(LayerRenderer, { layer: layer, debug: debug }))));
172
+ }, [layers, debug]);
173
+ return (React.createElement(SceneContext.Provider, { value: contextValue },
174
+ React.createElement(Container, { x: -cameraRef.current.x + width / 2, y: -cameraRef.current.y + height / 2, scale: cameraRef.current.zoom }, renderLayers)));
175
+ }
176
+ /**
177
+ * Renders all entities in a layer
178
+ */
179
+ function LayerRenderer({ layer, debug }) {
180
+ const { entities } = useScene();
181
+ // Filter and sort entities for this layer
182
+ const layerEntities = useMemo(() => {
183
+ const result = [];
184
+ for (const [, state] of entities.current) {
185
+ if (state.entity.layer === layer && !state.destroyed && state.visible) {
186
+ result.push(state);
187
+ }
188
+ }
189
+ // Sort by zIndex within layer
190
+ result.sort((a, b) => {
191
+ const zA = a.entity.render?.zIndex ?? 0;
192
+ const zB = b.entity.render?.zIndex ?? 0;
193
+ return zA - zB;
194
+ });
195
+ return result;
196
+ }, [entities, layer]);
197
+ return (React.createElement(React.Fragment, null, layerEntities.map((state) => (React.createElement(EntityRenderer, { key: state.entity.id, state: state, debug: debug })))));
198
+ }
199
+ // Re-export useScene for convenience
200
+ import { useScene } from './SceneContext';
201
+ export { useScene };
@@ -0,0 +1,6 @@
1
+ import type { SceneContextValue } from '../SceneContext';
2
+ /**
3
+ * Hook to control the camera
4
+ */
5
+ export declare function useCameraController(context: SceneContextValue, width: number, height: number): void;
6
+ //# sourceMappingURL=CameraController.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CameraController.d.ts","sourceRoot":"","sources":["../../../src/scene/camera/CameraController.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,iBAAiB,EAC1B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,QAsFf"}