cubeforge 0.4.13 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as React from 'react';
3
3
  import React__default, { CSSProperties, ReactNode, ReactElement } from 'react';
4
- import { Plugin, EntityId, ECSWorld, ScriptUpdateFn, NavGrid, WorldSnapshot, EventBus, AccessibilityOptions } from '@cubeforge/core';
5
- export { AccessibilityOptions, AssetProgress, Component, DeltaSnapshot, ECSWorld, Ease, EntityId, GameTimer, HierarchyComponent, HierarchySystem, HotReloadablePlugin, MergedRect, NavGrid, Plugin, PreloadManifest, ScriptUpdateFn, SpatialHash, TimelineEntry, TransformComponent, TweenHandle, TweenOptions, TweenTimeline, Vec2Like, WorldSnapshot, WorldTransformComponent, alignment, announceToScreenReader, applyDeltaSnapshot, arrive, cohesion, createHierarchy, createTag, createTimeline, createTimer, createTransform, definePlugin, evade, findByTag, flee, getAccessibilityOptions, getDescendants, hmrClearState, hmrLoadState, hmrSaveState, hotReloadPlugin, mergeTileColliders, patrol, preloadManifest, pursuit, removeParent, seek, separation, setAccessibilityOptions, setParent, smoothPath, tween, wander } from '@cubeforge/core';
4
+ import { Plugin, GameLoopMode, EntityId, ECSWorld, ScriptUpdateFn, NavGrid, WorldSnapshot, EventBus, AccessibilityOptions } from '@cubeforge/core';
5
+ export { AccessibilityOptions, AssetProgress, Component, DeltaSnapshot, ECSWorld, Ease, EntityId, GameLoopMode, GameTimer, HierarchyComponent, HierarchySystem, HotReloadablePlugin, MergedRect, NavGrid, Plugin, PreloadManifest, ScriptUpdateFn, SpatialHash, TimelineEntry, TransformComponent, TweenHandle, TweenOptions, TweenTimeline, Vec2Like, WorldSnapshot, WorldTransformComponent, alignment, announceToScreenReader, applyDeltaSnapshot, arrive, cohesion, createHierarchy, createTag, createTimeline, createTimer, createTransform, definePlugin, evade, findByTag, flee, getAccessibilityOptions, getDescendants, hmrClearState, hmrLoadState, hmrSaveState, hotReloadPlugin, isReducedMotionPreferred, mergeTileColliders, patrol, preloadManifest, pursuit, removeParent, seek, separation, setAccessibilityOptions, setParent, setReducedMotionOverride, smoothPath, tween, wander } from '@cubeforge/core';
6
6
  import { Sampling, BlendMode, SpriteShape, AnimatorStateDefinition, AnimatorParamValue, GradientType, GradientStop, MaskShape, PostProcessEffect, PostProcessOptions } from '@cubeforge/renderer';
7
7
  export { AnimationClipDefinition, AnimationStateComponent, AnimatorComponent, AnimatorCondition, AnimatorParamValue, AnimatorStateDefinition, AnimatorTransition, BlendMode, CircleShapeComponent, GradientComponent, GradientStop, GradientType, LineShapeComponent, MagFilterValue, MaskComponent, MaskShape, NineSliceComponent, ParallaxLayerComponent, Particle, ParticlePoolComponent, PolygonShapeComponent, PostProcessEffect, PostProcessOptions, PostProcessStack, RenderLayer, RenderLayerManager, RenderSystem, Sampling, SpriteComponent, SpriteShape, SquashStretchComponent, TextComponent, TextureFilter, TextureFilterValue, TrailComponent, chromaticAberrationEffect, createCircleShape, createGradient, createLineShape, createMask, createNineSlice, createPolygonShape, createPostProcessStack, createRenderLayerManager, createSprite, defaultLayers, scanlineEffect, vignetteEffect } from '@cubeforge/renderer';
8
8
  import { CombineRule, ColliderShape, JointType } from '@cubeforge/physics';
9
- export { AxisLock, BVH, BoxColliderComponent, BroadPhaseAABB, BroadPhasePair, COLLISION_DYNAMIC_DYNAMIC, COLLISION_DYNAMIC_KINEMATIC, COLLISION_DYNAMIC_STATIC, COLLISION_KINEMATIC_KINEMATIC, COLLISION_KINEMATIC_STATIC, CapsuleColliderComponent, CharacterCollision, CharacterController, CharacterControllerConfig, CircleColliderComponent, ColliderShape, CollisionPair, CollisionPipeline, CollisionPipelineResult, CombineRule, CompoundColliderComponent, ContactManifold, ContactPoint, ConvexPolygonColliderComponent, ConvexShape, DEFAULT_ACTIVE_COLLISION_TYPES, DebugCircle, DebugLine, DebugPoint, DebugRenderBackend, DebugRenderColors, DebugRenderFlags, DebugRenderOutput, DebugRenderPipeline, EPAResult, Float64Pool, GJKContactManifold, GJKResult, HalfSpaceColliderComponent, HeightFieldColliderComponent, Island, IslandDetector, JointComponent, JointMotor, JointSnapshot, JointType, KahanSum, MotorMode, MoveResult, MultibodyArticulation, MultibodyLink, PhysicsBodySnapshot, PhysicsHooks, ObjectPool as PhysicsObjectPool, PhysicsSnapshot, PointProjection, QueryOpts, QueryShape, RaycastHit, RigidBodyComponent, SegmentColliderComponent, Spatial3, SpatialInertia3, SweepAndPrune, TOIBody, TOIResult, TriMeshColliderComponent, Triangle2D, TriangleColliderComponent, addForce, addForceAtPoint, addTorque, applyImpulse, applyImpulseAtPoint, applyTorqueImpulse, boxArea, boxShape, buildBVH, capsuleArea, capsuleShape, circleArea, circleShape, computeTOI, containsPoint, createCompoundCollider, createConvexPolygonCollider, createHalfSpaceCollider, createHeightFieldCollider, createJoint, createLink, createMultibody, createSegmentCollider, createTriMeshCollider, createTriangleCollider, dMath, deterministicAtan2, deterministicCos, deterministicSin, deterministicSqrt, epa, generateDeterministicPairs, gjk, gjkEpaQuery, intersectAABB, intersectRay, intersectShape, isDeterministicMode, kineticEnergy, overlapBox, overlapCircle, pairKey, polygonArea, polygonMassProperties, polygonShape, potentialEnergy, predictPosition, projectPoint, queryBVH, queryBVHCircle, raycast, raycastAll, recomputeMassFromColliders, resetAllPools, resetForces, resetTorques, resolveTOI, restoreSnapshot, setAdditionalMass, setDeterministicMode, setMassProperties, setNextKinematicPosition, setNextKinematicRotation, shapeCast, snapshotFromBytes, snapshotFromJSON, snapshotHash, snapshotToBytes, snapshotToJSON, sortEntities, sweepBox, takeSnapshot, triangleArea, triangleMassProperties, velocityAtPoint } from '@cubeforge/physics';
9
+ export { AxisLock, BoxColliderComponent, COLLISION_DYNAMIC_DYNAMIC, COLLISION_DYNAMIC_KINEMATIC, COLLISION_DYNAMIC_STATIC, COLLISION_KINEMATIC_KINEMATIC, COLLISION_KINEMATIC_STATIC, CapsuleColliderComponent, CharacterCollision, CharacterController, CharacterControllerConfig, CircleColliderComponent, ColliderShape, CollisionPair, CollisionPipeline, CollisionPipelineResult, CombineRule, CompoundColliderComponent, ContactManifold, ContactPoint, ConvexPolygonColliderComponent, DEFAULT_ACTIVE_COLLISION_TYPES, DebugCircle, DebugLine, DebugPoint, DebugRenderBackend, DebugRenderColors, DebugRenderFlags, DebugRenderOutput, DebugRenderPipeline, HalfSpaceColliderComponent, HeightFieldColliderComponent, JointComponent, JointMotor, JointSnapshot, JointType, MotorMode, MoveResult, PhysicsBodySnapshot, PhysicsHooks, PhysicsSnapshot, PointProjection, QueryOpts, QueryShape, RaycastHit, RigidBodyComponent, SegmentColliderComponent, TriMeshColliderComponent, TriangleColliderComponent, addForce, addForceAtPoint, addTorque, applyImpulse, applyImpulseAtPoint, applyTorqueImpulse, boxArea, capsuleArea, circleArea, containsPoint, createCompoundCollider, createConvexPolygonCollider, createHalfSpaceCollider, createHeightFieldCollider, createJoint, createSegmentCollider, createTriMeshCollider, createTriangleCollider, intersectAABB, intersectRay, intersectShape, kineticEnergy, overlapBox, overlapCircle, polygonArea, polygonMassProperties, potentialEnergy, predictPosition, projectPoint, raycast, raycastAll, recomputeMassFromColliders, resetForces, resetTorques, restoreSnapshot, setAdditionalMass, setMassProperties, setNextKinematicPosition, setNextKinematicRotation, shapeCast, snapshotFromBytes, snapshotFromJSON, snapshotHash, snapshotToBytes, snapshotToJSON, sweepBox, takeSnapshot, triangleArea, triangleMassProperties, velocityAtPoint } from '@cubeforge/physics';
10
10
  import { InputManager, ActionBindings, InputContextName, PlayerInput, InputRecorderControls, TouchPoint, InputBufferOptions, InputBuffer, ComboDefinition } from '@cubeforge/input';
11
11
  export { ActionBindings, AxisBinding, BufferedAction, ComboDefinition, ComboDetector, ComboDetectorOptions, InputBuffer, InputBufferOptions, InputContextName, InputManager, InputMap, InputRecorderControls, InputRecording, InputRecording as InputRecordingData, PlayerInput, TouchPoint, createInputMap, createInputRecorder, createPlayerInput, globalInputContext } from '@cubeforge/input';
12
12
  import { AnimationClip } from '@cubeforge/gameplay';
@@ -23,7 +23,7 @@ interface GameControls {
23
23
  resume(): void;
24
24
  reset(): void;
25
25
  }
26
- interface GameProps {
26
+ interface GameProps$1 {
27
27
  width?: number;
28
28
  height?: number;
29
29
  /** Pixels per second squared downward (default 980) */
@@ -60,11 +60,45 @@ interface GameProps {
60
60
  sampling?: Sampling;
61
61
  /** Custom plugins to register after core systems. Each plugin's systems run after Render. */
62
62
  plugins?: Plugin[];
63
+ /**
64
+ * Loop mode (default 'realtime'):
65
+ * - 'realtime' — continuous 60fps tick. Use for action games, anything with
66
+ * continuous motion or physics.
67
+ * - 'onDemand' — sleeps until input arrives or a component calls markDirty().
68
+ * Use for puzzle games, turn-based games, visual novels, level editors, or any
69
+ * scene where nothing changes unless the user acts. Saves battery and CPU.
70
+ */
71
+ mode?: GameLoopMode;
63
72
  style?: CSSProperties;
64
73
  className?: string;
65
74
  children?: React__default.ReactNode;
66
75
  }
67
- declare function Game({ width, height, gravity, debug, devtools, scale, deterministic, seed, asyncAssets, sampling, onReady, plugins, style, className, children, }: GameProps): react_jsx_runtime.JSX.Element;
76
+ declare function Game({ width, height, gravity, debug, devtools, scale, deterministic, seed, asyncAssets, sampling, onReady, plugins, mode, style, className, children, }: GameProps$1): react_jsx_runtime.JSX.Element;
77
+
78
+ type GameProps = React__default.ComponentProps<typeof Game>;
79
+ /**
80
+ * Stage is an alias for {@link Game} tuned for static, puzzle, turn-based, and
81
+ * editor-style scenes. It defaults to:
82
+ *
83
+ * - `mode="onDemand"` — the loop sleeps until input arrives or a component calls
84
+ * `engine.loop.markDirty()`. Saves battery and CPU.
85
+ * - `gravity={0}` — no downward acceleration. Most non-action scenes don't need it.
86
+ *
87
+ * Every other prop behaves identically to `<Game>`. Use `<Stage>` when you're
88
+ * building something that isn't an action game: a level editor, a card game, a
89
+ * match-3 puzzle, a visual novel, a node-graph tool, etc.
90
+ *
91
+ * @example
92
+ * ```tsx
93
+ * <Stage width={800} height={600}>
94
+ * <Entity>
95
+ * <Transform x={100} y={100} />
96
+ * <Sprite src="card.png" width={80} height={120} />
97
+ * </Entity>
98
+ * </Stage>
99
+ * ```
100
+ */
101
+ declare function Stage(props: GameProps): react_jsx_runtime.JSX.Element;
68
102
 
69
103
  interface WorldProps {
70
104
  /** Gravitational acceleration in pixels/s² (default inherited from Game) */
@@ -183,10 +217,6 @@ interface RigidBodyProps {
183
217
  mass?: number;
184
218
  gravityScale?: number;
185
219
  isStatic?: boolean;
186
- /** @deprecated Use restitution on BoxCollider/CircleCollider instead */
187
- bounce?: number;
188
- /** @deprecated Use friction on BoxCollider/CircleCollider instead */
189
- friction?: number;
190
220
  vx?: number;
191
221
  vy?: number;
192
222
  /** Prevent any horizontal movement — velocity.x is zeroed every frame */
@@ -220,7 +250,7 @@ interface RigidBodyProps {
220
250
  /** Extra velocity solver iterations for constraints involving this body. Default 0 */
221
251
  additionalSolverIterations?: number;
222
252
  }
223
- declare function RigidBody({ mass, gravityScale, isStatic, bounce, friction, vx, vy, lockX, lockY, lockRotation, ccd, angularVelocity, angularDamping, linearDamping, density, restitution, dominance, isKinematic, enabled, maxLinearVelocity, maxAngularVelocity, additionalSolverIterations, }: RigidBodyProps): null;
253
+ declare function RigidBody({ mass, gravityScale, isStatic, vx, vy, lockX, lockY, lockRotation, ccd, angularVelocity, angularDamping, linearDamping, density, restitution, dominance, isKinematic, enabled, maxLinearVelocity, maxAngularVelocity, additionalSolverIterations, }: RigidBodyProps): null;
224
254
 
225
255
  interface BoxColliderProps {
226
256
  width: number;
@@ -750,8 +780,30 @@ interface TilemapProps {
750
780
  * row-run (legacy behaviour).
751
781
  */
752
782
  mergeColliders?: boolean;
783
+ /**
784
+ * Array of tile GIDs (global IDs) to treat as solid, regardless of layer.
785
+ * When set, only tiles whose GID is in this array generate colliders in
786
+ * the collision layer. Without this, ALL non-zero tiles in the collision
787
+ * layer produce colliders.
788
+ */
789
+ solidTiles?: number[];
790
+ /**
791
+ * When true, also scan visual layers for tiles whose GID matches
792
+ * `solidTiles` and auto-generate colliders from them — no separate
793
+ * collision layer required. Default: false.
794
+ */
795
+ autoColliders?: boolean;
796
+ /**
797
+ * Per-tile-GID collider properties. Keys are tile GIDs.
798
+ * Example: `{ 6: { isTrigger: true }, 7: { oneWay: true } }`
799
+ */
800
+ tileColliderProps?: Record<number, {
801
+ isTrigger?: boolean;
802
+ oneWay?: boolean;
803
+ layer?: string;
804
+ }>;
753
805
  }
754
- declare function Tilemap({ src, onSpawnObject, layerFilter, zIndex, collisionLayer, triggerLayer: triggerLayerName, onTileProperty, navGrid, mergeColliders, }: TilemapProps): React__default.ReactElement | null;
806
+ declare function Tilemap({ src, onSpawnObject, layerFilter, zIndex, collisionLayer, triggerLayer: triggerLayerName, onTileProperty, navGrid, mergeColliders, solidTiles, autoColliders, tileColliderProps: _tileColliderProps, }: TilemapProps): React__default.ReactElement | null;
755
807
 
756
808
  interface ParallaxLayerProps {
757
809
  /** Image URL to use as the background layer */
@@ -1832,6 +1884,100 @@ interface SceneManagerControls {
1832
1884
  */
1833
1885
  declare function useSceneManager(initialScene: string): SceneManagerControls;
1834
1886
 
1887
+ type TransitionEffect = {
1888
+ type: 'fade';
1889
+ duration?: number;
1890
+ color?: string;
1891
+ } | {
1892
+ type: 'wipe';
1893
+ duration?: number;
1894
+ direction?: 'left' | 'right' | 'up' | 'down';
1895
+ color?: string;
1896
+ } | {
1897
+ type: 'circle-close';
1898
+ duration?: number;
1899
+ color?: string;
1900
+ } | {
1901
+ type: 'instant';
1902
+ };
1903
+ interface SceneTransitionControls {
1904
+ /** Currently active scene (after transition completes). */
1905
+ current: string;
1906
+ /** Full scene stack. */
1907
+ stack: string[];
1908
+ /** Push scene with transition. */
1909
+ push(scene: string, transition?: TransitionEffect): void;
1910
+ /** Pop top scene with transition. Returns popped scene name. */
1911
+ pop(transition?: TransitionEffect): string | undefined;
1912
+ /** Replace current scene with transition. */
1913
+ replace(scene: string, transition?: TransitionEffect): void;
1914
+ /** Reset to a single scene with transition. */
1915
+ reset(scene: string, transition?: TransitionEffect): void;
1916
+ /** Transition progress 0–1 (0 = no overlay, 1 = fully covered). */
1917
+ progress: number;
1918
+ /** Current transition phase. */
1919
+ phase: 'idle' | 'out' | 'in';
1920
+ /** Active transition effect (for overlay rendering). */
1921
+ activeTransition: TransitionEffect | null;
1922
+ }
1923
+ /**
1924
+ * Scene manager with built-in visual transitions.
1925
+ *
1926
+ * @example
1927
+ * const scenes = useSceneTransition('gameplay')
1928
+ * scenes.push('pause', { type: 'fade', duration: 0.4, color: '#000' })
1929
+ * scenes.replace('gameOver', { type: 'circle-close', duration: 0.6 })
1930
+ *
1931
+ * // Render the overlay component to see transitions:
1932
+ * <SceneTransitionOverlay controls={scenes} />
1933
+ */
1934
+ declare function useSceneTransition(initialScene: string, defaultTransition?: TransitionEffect): SceneTransitionControls;
1935
+
1936
+ interface SceneTransitionOverlayProps {
1937
+ controls: SceneTransitionControls;
1938
+ }
1939
+ /**
1940
+ * Renders the visual overlay during scene transitions.
1941
+ * Place this as a sibling after your scene content, inside the Game wrapper.
1942
+ *
1943
+ * @example
1944
+ * const scenes = useSceneTransition('gameplay')
1945
+ *
1946
+ * <div style={{ position: 'relative' }}>
1947
+ * {scenes.current === 'gameplay' && <GameplayScene />}
1948
+ * {scenes.current === 'pause' && <PauseMenu />}
1949
+ * <SceneTransitionOverlay controls={scenes} />
1950
+ * </div>
1951
+ */
1952
+ declare function SceneTransitionOverlay({ controls }: SceneTransitionOverlayProps): React__default.DetailedReactHTMLElement<{
1953
+ style: {
1954
+ position: "absolute";
1955
+ inset: number;
1956
+ backgroundColor: string;
1957
+ WebkitMaskImage: `radial-gradient(circle ${number}vmax at 50% 50%, transparent 100%, black 100%)`;
1958
+ maskImage: `radial-gradient(circle ${number}vmax at 50% 50%, transparent 100%, black 100%)`;
1959
+ pointerEvents: "none";
1960
+ zIndex: number;
1961
+ };
1962
+ }, HTMLElement> | React__default.DetailedReactHTMLElement<{
1963
+ style: React__default.CSSProperties;
1964
+ }, HTMLElement> | null;
1965
+
1966
+ /**
1967
+ * Freeze gameplay for a short duration — physics and scripts stop
1968
+ * but the last frame stays rendered. Creates impactful collision feedback.
1969
+ *
1970
+ * @example
1971
+ * const hitstop = useHitstop()
1972
+ *
1973
+ * useCollisionEnter(() => {
1974
+ * hitstop.freeze(0.08) // 80ms freeze on hit
1975
+ * })
1976
+ */
1977
+ declare function useHitstop(): {
1978
+ freeze: (seconds: number) => void;
1979
+ };
1980
+
1835
1981
  /**
1836
1982
  * Returns a stable `InputBuffer` instance that persists across renders.
1837
1983
  *
@@ -2000,6 +2146,956 @@ interface SquashStretchControls {
2000
2146
  */
2001
2147
  declare function useSquashStretch(): SquashStretchControls;
2002
2148
 
2149
+ interface HistoryOptions {
2150
+ /** Maximum number of snapshots to keep. Oldest entries are evicted. Default 50. */
2151
+ capacity?: number;
2152
+ /**
2153
+ * When true, `Ctrl/Cmd+Z` triggers undo and `Ctrl/Cmd+Shift+Z` (or `Ctrl+Y`) triggers
2154
+ * redo. Listens on `window`. Default false — users are expected to wire their own
2155
+ * UI and shortcuts.
2156
+ */
2157
+ bindKeyboardShortcuts?: boolean;
2158
+ }
2159
+ interface HistoryControls {
2160
+ /** Capture the current world state and push it onto the history stack. */
2161
+ push(): void;
2162
+ /** Revert to the previous snapshot. No-op if there's nothing to undo. */
2163
+ undo(): void;
2164
+ /** Re-apply a snapshot that was undone. No-op if there's nothing to redo. */
2165
+ redo(): void;
2166
+ /** Clear the entire history stack. */
2167
+ clear(): void;
2168
+ /** True if `undo()` would do anything. */
2169
+ canUndo: boolean;
2170
+ /** True if `redo()` would do anything. */
2171
+ canRedo: boolean;
2172
+ /** Number of snapshots currently stored. */
2173
+ length: number;
2174
+ }
2175
+ /**
2176
+ * Undo/redo for the entire ECS world, backed by the snapshot system.
2177
+ *
2178
+ * The typical pattern is "commit then push" — after the user completes a logical
2179
+ * action (move a piece, paint a tile, rotate a shape), call `push()` to capture
2180
+ * the new state. `undo()` rewinds to the previous push, `redo()` replays forward.
2181
+ *
2182
+ * In onDemand loop mode, undo/redo automatically re-render by calling
2183
+ * `engine.loop.markDirty()`.
2184
+ *
2185
+ * @example
2186
+ * ```tsx
2187
+ * function PuzzleBoard() {
2188
+ * const history = useHistory({ capacity: 100, bindKeyboardShortcuts: true })
2189
+ *
2190
+ * const onMove = (from, to) => {
2191
+ * applyMove(from, to)
2192
+ * history.push() // commit the move so it can be undone
2193
+ * }
2194
+ *
2195
+ * return (
2196
+ * <>
2197
+ * <Board onMove={onMove} />
2198
+ * <button disabled={!history.canUndo} onClick={history.undo}>Undo</button>
2199
+ * <button disabled={!history.canRedo} onClick={history.redo}>Redo</button>
2200
+ * </>
2201
+ * )
2202
+ * }
2203
+ * ```
2204
+ */
2205
+ declare function useHistory(options?: HistoryOptions): HistoryControls;
2206
+
2207
+ interface SelectOptions {
2208
+ /** If true, add to the current selection instead of replacing it. */
2209
+ additive?: boolean;
2210
+ }
2211
+ interface SelectionControls {
2212
+ /** The currently selected entity IDs, in selection order. */
2213
+ selected: EntityId[];
2214
+ /** Select one entity. Pass `{ additive: true }` to add to existing selection. */
2215
+ select(id: EntityId, opts?: SelectOptions): void;
2216
+ /** Remove an entity from the selection. */
2217
+ deselect(id: EntityId): void;
2218
+ /** Toggle an entity's selection state. */
2219
+ toggle(id: EntityId): void;
2220
+ /** Clear the entire selection. */
2221
+ clear(): void;
2222
+ /** True if the given entity is currently selected. */
2223
+ isSelected(id: EntityId): boolean;
2224
+ }
2225
+ interface SelectionProps {
2226
+ /** Initial selection when the provider mounts. */
2227
+ initial?: EntityId[];
2228
+ /** Called every time the selection changes. */
2229
+ onChange?: (selected: EntityId[]) => void;
2230
+ children?: ReactNode;
2231
+ }
2232
+ /**
2233
+ * Provides a selection state to descendants. Works with {@link TransformHandles}
2234
+ * to render drag-to-move/resize/rotate handles, or with any custom UI.
2235
+ *
2236
+ * @example
2237
+ * ```tsx
2238
+ * <Stage>
2239
+ * <Selection>
2240
+ * <Entity>
2241
+ * <Sprite src="card.png" width={80} height={120} />
2242
+ * </Entity>
2243
+ * <TransformHandles />
2244
+ * </Selection>
2245
+ * </Stage>
2246
+ * ```
2247
+ */
2248
+ declare function Selection({ initial, onChange, children }: SelectionProps): react_jsx_runtime.JSX.Element;
2249
+ /**
2250
+ * Reads and mutates the selection state from inside a {@link Selection} provider.
2251
+ *
2252
+ * @example
2253
+ * ```tsx
2254
+ * function Card({ id }: { id: number }) {
2255
+ * const { select, isSelected } = useSelection()
2256
+ * return <button onClick={() => select(id)}>{isSelected(id) ? '✓' : ''} Card</button>
2257
+ * }
2258
+ * ```
2259
+ */
2260
+ declare function useSelection(): SelectionControls;
2261
+
2262
+ interface TransformHandlesProps {
2263
+ /** Whether to show the rotation handle above the top edge. Default true. */
2264
+ showRotationHandle?: boolean;
2265
+ /** Whether corner/edge handles can resize the entity (by scaling). Default true. */
2266
+ showResizeHandles?: boolean;
2267
+ /** Whether dragging the body translates the entity. Default true. */
2268
+ enableTranslate?: boolean;
2269
+ /** Color of the selection outline and handles. Default '#4fc3f7'. */
2270
+ color?: string;
2271
+ /** Handle size in CSS pixels. Default 10. */
2272
+ handleSize?: number;
2273
+ }
2274
+ /**
2275
+ * Renders interactive resize/rotate/move handles for every entity currently in the
2276
+ * {@link Selection} context. Handles are positioned via a DOM overlay on top of the
2277
+ * game canvas, so they respect CSS cursors (e.g. `nwse-resize`) and are fully
2278
+ * styleable.
2279
+ *
2280
+ * Dragging the body translates, dragging corners/edges scales, and dragging the
2281
+ * handle above the top edge rotates. In onDemand loop mode every mutation calls
2282
+ * `markDirty()` so the canvas re-renders.
2283
+ *
2284
+ * @example
2285
+ * ```tsx
2286
+ * <Stage>
2287
+ * <Selection initial={[cardId]}>
2288
+ * <Entity>…</Entity>
2289
+ * <TransformHandles />
2290
+ * </Selection>
2291
+ * </Stage>
2292
+ * ```
2293
+ */
2294
+ declare function TransformHandles({ showRotationHandle, showResizeHandles, enableTranslate, color, handleSize, }: TransformHandlesProps): react_jsx_runtime.JSX.Element | null;
2295
+
2296
+ interface SnapOptions {
2297
+ /**
2298
+ * Grid cell size in world units. If set, points are snapped to the nearest
2299
+ * multiple of `grid`. Can be a single number (uniform) or `{ x, y }`.
2300
+ */
2301
+ grid?: number | {
2302
+ x: number;
2303
+ y: number;
2304
+ };
2305
+ /**
2306
+ * When true, snap to the edges/centers of other entities that have a Sprite or
2307
+ * collider within `threshold` distance. Default false.
2308
+ */
2309
+ snapToEntities?: boolean;
2310
+ /**
2311
+ * Maximum distance in world units at which entity-edge snapping triggers.
2312
+ * Default 8.
2313
+ */
2314
+ threshold?: number;
2315
+ /**
2316
+ * Entity IDs to exclude from entity snapping (e.g. the entity being dragged).
2317
+ */
2318
+ exclude?: EntityId[];
2319
+ }
2320
+ interface SnapResult {
2321
+ x: number;
2322
+ y: number;
2323
+ /** True if the returned point was snapped (to grid or to an entity). */
2324
+ snapped: boolean;
2325
+ /** 'grid' | 'entity' | null, describing which snap fired. */
2326
+ source: 'grid' | 'entity' | null;
2327
+ }
2328
+ interface SnapControls {
2329
+ /** Snap a world-space point to the nearest grid cell and/or nearby entity edge. */
2330
+ snap(x: number, y: number): SnapResult;
2331
+ /** Snap just to the grid, ignoring any entity edges. */
2332
+ snapToGrid(x: number, y: number): {
2333
+ x: number;
2334
+ y: number;
2335
+ };
2336
+ }
2337
+ /**
2338
+ * Returns snap helpers for puzzle games, level editors, and tile-based placement.
2339
+ *
2340
+ * @example
2341
+ * ```tsx
2342
+ * function DraggablePiece() {
2343
+ * const { snap } = useSnap({ grid: 32, snapToEntities: true, threshold: 6 })
2344
+ * const onDrag = (x: number, y: number) => {
2345
+ * const result = snap(x, y)
2346
+ * piece.x = result.x
2347
+ * piece.y = result.y
2348
+ * }
2349
+ * // …
2350
+ * }
2351
+ * ```
2352
+ */
2353
+ declare function useSnap(options?: SnapOptions): SnapControls;
2354
+
2355
+ interface EditableTextProps {
2356
+ /** Current text value. */
2357
+ value: string;
2358
+ /** Called on every keystroke with the new value. */
2359
+ onChange: (next: string) => void;
2360
+ /** Called when the user presses Enter (single-line mode) or Ctrl+Enter (multiline). */
2361
+ onSubmit?: (value: string) => void;
2362
+ /** Called when the input loses focus. */
2363
+ onBlur?: (value: string) => void;
2364
+ /** Width of the input in world units. Default 200. */
2365
+ width?: number;
2366
+ /** Height of the input in world units. Default 40. */
2367
+ height?: number;
2368
+ /** Font size in world units. Default 16. */
2369
+ fontSize?: number;
2370
+ /** CSS font family. Default 'inherit'. */
2371
+ fontFamily?: string;
2372
+ /** Text color. Default '#ffffff'. */
2373
+ color?: string;
2374
+ /** Background color. Default 'transparent'. */
2375
+ background?: string;
2376
+ /** Border. Default 'none'. */
2377
+ border?: string;
2378
+ /** Padding in CSS pixels. Default 4. */
2379
+ padding?: number;
2380
+ /** Text alignment. Default 'left'. */
2381
+ align?: 'left' | 'center' | 'right';
2382
+ /** Placeholder text when empty. */
2383
+ placeholder?: string;
2384
+ /** Maximum number of characters. */
2385
+ maxLength?: number;
2386
+ /** If true, renders a `<textarea>` instead of an `<input>`. */
2387
+ multiline?: boolean;
2388
+ /** If true, the input auto-focuses when mounted. */
2389
+ autoFocus?: boolean;
2390
+ /** Disable the input. */
2391
+ disabled?: boolean;
2392
+ }
2393
+ /**
2394
+ * A canvas-positioned editable text field backed by a real HTML `<input>` or
2395
+ * `<textarea>` overlaid on the game canvas. Position follows the entity's
2396
+ * Transform and the active Camera2D every frame.
2397
+ *
2398
+ * Essential for word games, name entry, level editor labels, form-shaped UIs,
2399
+ * and anywhere you'd otherwise try to reinvent text input inside WebGL.
2400
+ *
2401
+ * @example
2402
+ * ```tsx
2403
+ * function NameCard({ name, setName }: { name: string; setName: (s: string) => void }) {
2404
+ * return (
2405
+ * <Entity>
2406
+ * <Transform x={100} y={100} />
2407
+ * <EditableText
2408
+ * value={name}
2409
+ * onChange={setName}
2410
+ * width={220}
2411
+ * height={40}
2412
+ * background="#1e2a3a"
2413
+ * color="#e0e7f1"
2414
+ * placeholder="Your name"
2415
+ * autoFocus
2416
+ * />
2417
+ * </Entity>
2418
+ * )
2419
+ * }
2420
+ * ```
2421
+ */
2422
+ declare function EditableText({ value, onChange, onSubmit, onBlur, width, height, fontSize, fontFamily, color, background, border, padding, align, placeholder, maxLength, multiline, autoFocus, disabled, }: EditableTextProps): react_jsx_runtime.JSX.Element | null;
2423
+
2424
+ /**
2425
+ * Snapshot utilities that capture the current canvas to an image. Useful for:
2426
+ * - "Save your creation" / "Share" features in puzzle games and editors
2427
+ * - Thumbnails of user-created levels
2428
+ * - Automated test fixtures
2429
+ * - Social media share cards
2430
+ *
2431
+ * All helpers work with both WebGL2 and Canvas 2D contexts (the underlying
2432
+ * `canvas.toBlob` / `canvas.toDataURL` APIs are context-agnostic).
2433
+ *
2434
+ * **Important**: For WebGL contexts, you must call these helpers *immediately*
2435
+ * after the render tick, while the framebuffer still contains the rendered
2436
+ * frame. Between frames the browser may clear the WebGL drawing buffer.
2437
+ */
2438
+ interface ExportOptions {
2439
+ /** Image MIME type. Default 'image/png'. */
2440
+ type?: 'image/png' | 'image/jpeg' | 'image/webp';
2441
+ /** Quality (0–1) for jpeg/webp. Ignored for png. Default 0.92. */
2442
+ quality?: number;
2443
+ /**
2444
+ * Optional region to crop, in CSS pixels relative to the canvas top-left.
2445
+ * If omitted, the whole canvas is exported.
2446
+ */
2447
+ region?: {
2448
+ x: number;
2449
+ y: number;
2450
+ width: number;
2451
+ height: number;
2452
+ };
2453
+ /**
2454
+ * Scale multiplier. 1 = source resolution. 2 = double (useful for HiDPI exports).
2455
+ * Default 1.
2456
+ */
2457
+ scale?: number;
2458
+ }
2459
+ /**
2460
+ * Capture the current canvas content to a Blob. Resolves to `null` if the browser
2461
+ * refuses the encode (very rare — usually only on dead contexts).
2462
+ *
2463
+ * Must be called immediately after a render tick for WebGL canvases. In onDemand
2464
+ * loop mode, call `engine.loop.markDirty()` and then export from within the next
2465
+ * `requestAnimationFrame` to guarantee a fresh frame.
2466
+ *
2467
+ * @example
2468
+ * ```ts
2469
+ * const blob = await exportToBlob(engine.canvas, { type: 'image/png' })
2470
+ * if (blob) {
2471
+ * const url = URL.createObjectURL(blob)
2472
+ * window.open(url)
2473
+ * }
2474
+ * ```
2475
+ */
2476
+ declare function exportToBlob(canvas: HTMLCanvasElement, options?: ExportOptions): Promise<Blob | null>;
2477
+ /**
2478
+ * Capture the current canvas content to a base64 data URL. Synchronous.
2479
+ *
2480
+ * @example
2481
+ * ```ts
2482
+ * const dataUrl = exportToDataURL(engine.canvas, { type: 'image/jpeg', quality: 0.8 })
2483
+ * const img = new Image()
2484
+ * img.src = dataUrl
2485
+ * ```
2486
+ */
2487
+ declare function exportToDataURL(canvas: HTMLCanvasElement, options?: ExportOptions): string;
2488
+ /**
2489
+ * Capture the canvas and trigger a browser download. Convenience wrapper around
2490
+ * {@link exportToBlob} + an anchor click.
2491
+ *
2492
+ * @example
2493
+ * ```ts
2494
+ * await downloadCanvas(engine.canvas, 'my-puzzle.png')
2495
+ * ```
2496
+ */
2497
+ declare function downloadCanvas(canvas: HTMLCanvasElement, filename: string, options?: ExportOptions): Promise<void>;
2498
+
2499
+ interface A11yNodeProps {
2500
+ /**
2501
+ * Accessible label read by screen readers. Required — this is what the user
2502
+ * will hear when the element is focused.
2503
+ */
2504
+ label: string;
2505
+ /** Optional longer description (maps to aria-description). */
2506
+ description?: string;
2507
+ /**
2508
+ * ARIA role. Defaults to 'button' if `onActivate` is provided, else 'presentation'.
2509
+ * Common values: 'button', 'checkbox', 'link', 'img', 'listitem', 'option'.
2510
+ */
2511
+ role?: string;
2512
+ /**
2513
+ * Called when the user activates the element (click, Enter, or Space).
2514
+ * If provided, the element becomes keyboard focusable and gains role='button'.
2515
+ */
2516
+ onActivate?: (entityId: EntityId) => void;
2517
+ /** Controls tab order. Default 0 (natural order), or -1 to skip. */
2518
+ tabIndex?: number;
2519
+ /** ARIA pressed state for toggle buttons. */
2520
+ pressed?: boolean;
2521
+ /** ARIA checked state for checkboxes/switches. */
2522
+ checked?: boolean;
2523
+ /** ARIA disabled state. */
2524
+ disabled?: boolean;
2525
+ /** ARIA selected state for list options. */
2526
+ selected?: boolean;
2527
+ /** Override the element's bounding box in world units. If omitted, derived from Sprite/BoxCollider/CircleCollider. */
2528
+ bounds?: {
2529
+ width: number;
2530
+ height: number;
2531
+ };
2532
+ }
2533
+ /**
2534
+ * Mirrors the parent {@link Entity} into a focusable, screen-reader-friendly
2535
+ * DOM element positioned over the canvas. Enables keyboard navigation and
2536
+ * accessibility for puzzle games, turn-based games, and any scene where the
2537
+ * canvas content has meaningful structure.
2538
+ *
2539
+ * The element is visually hidden (clipped to 1px) but focusable and in the
2540
+ * document flow, so screen readers announce it and Tab cycles through it. When
2541
+ * focused, it dispatches `onActivate(entityId)` on click, Enter, or Space.
2542
+ *
2543
+ * @example
2544
+ * ```tsx
2545
+ * <Entity>
2546
+ * <Transform x={100} y={100} />
2547
+ * <Sprite src="red-pawn.png" width={48} height={48} />
2548
+ * <A11yNode
2549
+ * label="Red pawn at E4"
2550
+ * description="Press Enter to select"
2551
+ * onActivate={(id) => selectPawn(id)}
2552
+ * />
2553
+ * </Entity>
2554
+ * ```
2555
+ */
2556
+ declare function A11yNode({ label, description, role, onActivate, tabIndex, pressed, checked, disabled, selected, bounds, }: A11yNodeProps): react_jsx_runtime.JSX.Element | null;
2557
+
2558
+ interface VectorPathProps {
2559
+ /**
2560
+ * SVG path data (the `d` attribute). Supports the full SVG path syntax including
2561
+ * cubic and quadratic beziers, arcs, and close commands.
2562
+ *
2563
+ * Coordinates are in world units relative to the parent entity's Transform.
2564
+ *
2565
+ * @example `M0,0 C10,-20 40,-20 50,0`
2566
+ */
2567
+ d: string;
2568
+ /** Stroke color. Default '#ffffff'. */
2569
+ stroke?: string;
2570
+ /** Stroke width in world units. Default 2. */
2571
+ strokeWidth?: number;
2572
+ /** Stroke dash pattern, e.g. '4 2'. */
2573
+ strokeDasharray?: string;
2574
+ /** Line cap style. Default 'round'. */
2575
+ strokeLinecap?: 'butt' | 'round' | 'square';
2576
+ /** Line join style. Default 'round'. */
2577
+ strokeLinejoin?: 'miter' | 'round' | 'bevel';
2578
+ /** Fill color. Default 'none' (no fill). */
2579
+ fill?: string;
2580
+ /** Fill rule. Default 'nonzero'. */
2581
+ fillRule?: 'nonzero' | 'evenodd';
2582
+ /** Opacity (0–1). Default 1. */
2583
+ opacity?: number;
2584
+ /** Hide the path. */
2585
+ visible?: boolean;
2586
+ }
2587
+ /**
2588
+ * A vector path (cubic/quadratic beziers, arcs, polylines) positioned in world
2589
+ * space and rendered as a DOM SVG overlay above the game canvas.
2590
+ *
2591
+ * Follows the parent {@link Entity}'s Transform and the active {@link Camera2D}
2592
+ * every frame, so zoom/pan work automatically. Accepts the full SVG path syntax
2593
+ * in the `d` prop.
2594
+ *
2595
+ * **Trade-off**: Because this is a DOM overlay (not a WebGL draw), paths do not
2596
+ * participate in the post-process stack (vignette, scanlines, chromatic
2597
+ * aberration, etc.) and always render above all sprites. If you need paths to
2598
+ * be affected by post-FX or z-ordered with sprites, rasterize your artwork into
2599
+ * an image and use `<Sprite>` instead.
2600
+ *
2601
+ * @example
2602
+ * ```tsx
2603
+ * <Entity>
2604
+ * <Transform x={100} y={100} />
2605
+ * <VectorPath
2606
+ * d="M 0 0 C 20 -40, 60 -40, 80 0 S 140 40, 160 0"
2607
+ * stroke="#4fc3f7"
2608
+ * strokeWidth={3}
2609
+ * fill="none"
2610
+ * />
2611
+ * </Entity>
2612
+ * ```
2613
+ */
2614
+ declare function VectorPath({ d, stroke, strokeWidth, strokeDasharray, strokeLinecap, strokeLinejoin, fill, fillRule, opacity, visible, }: VectorPathProps): react_jsx_runtime.JSX.Element | null;
2615
+
2616
+ interface GridOptions<T> {
2617
+ /** Number of columns. */
2618
+ width: number;
2619
+ /** Number of rows. */
2620
+ height: number;
2621
+ /**
2622
+ * Default cell value. Can be a plain value (each cell shares the reference) or a
2623
+ * factory `(x, y) => T` called for each cell during initialization.
2624
+ */
2625
+ fill: T | ((x: number, y: number) => T);
2626
+ }
2627
+ interface GridCell<T> {
2628
+ x: number;
2629
+ y: number;
2630
+ value: T;
2631
+ }
2632
+ interface GridControls<T> {
2633
+ width: number;
2634
+ height: number;
2635
+ /** Read a cell value. Returns undefined for out-of-bounds coords. */
2636
+ get(x: number, y: number): T | undefined;
2637
+ /** Write a cell value. No-op for out-of-bounds coords. */
2638
+ set(x: number, y: number, value: T): void;
2639
+ /** True if (x, y) is within the grid bounds. */
2640
+ inBounds(x: number, y: number): boolean;
2641
+ /** Swap the values of two cells. */
2642
+ swap(ax: number, ay: number, bx: number, by: number): void;
2643
+ /** Iterate over every cell. */
2644
+ forEach(cb: (cell: GridCell<T>) => void): void;
2645
+ /** Find the first cell matching a predicate. */
2646
+ find(pred: (cell: GridCell<T>) => boolean): GridCell<T> | undefined;
2647
+ /** Count the cells matching a predicate. */
2648
+ count(pred: (cell: GridCell<T>) => boolean): number;
2649
+ /**
2650
+ * Return the 4-neighborhood (N/E/S/W) or 8-neighborhood (including diagonals)
2651
+ * of a cell, as `{ x, y, value }` entries. Out-of-bounds neighbors are omitted.
2652
+ */
2653
+ neighbors(x: number, y: number, diagonal?: boolean): GridCell<T>[];
2654
+ /** Reset every cell to a new value (or factory). */
2655
+ fill(value: T | ((x: number, y: number) => T)): void;
2656
+ /** Deep-copy the grid as a 2D array `arr[y][x]`. */
2657
+ toArray(): T[][];
2658
+ /** Replace the entire grid contents from a 2D array. */
2659
+ fromArray(arr: T[][]): void;
2660
+ }
2661
+ /**
2662
+ * A reactive 2D board state for puzzle and turn-based games. Calls to `set`,
2663
+ * `swap`, `fill`, and `fromArray` trigger a re-render and call
2664
+ * `engine.loop.markDirty()`, so in onDemand mode the canvas updates automatically.
2665
+ *
2666
+ * The underlying storage is a flat array indexed `y * width + x`.
2667
+ *
2668
+ * @example
2669
+ * ```tsx
2670
+ * type Tile = 'empty' | 'wall' | 'box'
2671
+ *
2672
+ * function Sokoban() {
2673
+ * const board = useGrid<Tile>({ width: 10, height: 10, fill: 'empty' })
2674
+ * board.set(1, 1, 'wall')
2675
+ * const walls = board.count((c) => c.value === 'wall')
2676
+ * // render by iterating board.forEach(...)
2677
+ * }
2678
+ * ```
2679
+ */
2680
+ declare function useGrid<T>({ width, height, fill }: GridOptions<T>): GridControls<T>;
2681
+
2682
+ interface TurnSystemOptions<P> {
2683
+ /** Ordered list of player identifiers. Must contain at least one. */
2684
+ players: P[];
2685
+ /** Index of the player who starts. Default 0. */
2686
+ initialIndex?: number;
2687
+ /** Called every time the active player changes, *after* the change is applied. */
2688
+ onTurnStart?: (info: {
2689
+ player: P;
2690
+ index: number;
2691
+ turn: number;
2692
+ }) => void;
2693
+ /** Called just before the active player changes. */
2694
+ onTurnEnd?: (info: {
2695
+ player: P;
2696
+ index: number;
2697
+ turn: number;
2698
+ }) => void;
2699
+ /**
2700
+ * When non-zero, `nextTurn()` delays the player switch by this many seconds.
2701
+ * Useful for letting AI moves animate before the control is handed back.
2702
+ * Default 0 (instant).
2703
+ */
2704
+ aiDelay?: number;
2705
+ }
2706
+ interface TurnSystemControls<P> {
2707
+ /** The currently active player. */
2708
+ activePlayer: P;
2709
+ /** The index of the currently active player. */
2710
+ activeIndex: number;
2711
+ /** Turn counter — increments every time the active player changes. Starts at 0. */
2712
+ turn: number;
2713
+ /** Advance to the next player in the list. Wraps around at the end. */
2714
+ nextTurn(): void;
2715
+ /** Go back to the previous player. */
2716
+ prevTurn(): void;
2717
+ /** Jump directly to a specific player by index or identifier. */
2718
+ skipTo(target: number | P): void;
2719
+ /** Reset the system to its initial state. */
2720
+ reset(): void;
2721
+ /** True while an `aiDelay` is pending between `nextTurn()` being called and the switch. */
2722
+ isPending: boolean;
2723
+ }
2724
+ /**
2725
+ * Turn manager for turn-based games (chess, cards, strategy). Tracks the active
2726
+ * player, fires lifecycle callbacks, and supports an optional delay before
2727
+ * handing control back — useful when an AI player makes a move and you want the
2728
+ * animation to finish before the next player can act.
2729
+ *
2730
+ * @example
2731
+ * ```tsx
2732
+ * function Chess() {
2733
+ * const turns = useTurnSystem<'white' | 'black'>({
2734
+ * players: ['white', 'black'],
2735
+ * aiDelay: 0.5,
2736
+ * onTurnStart: ({ player }) => console.log(`${player}'s move`),
2737
+ * })
2738
+ *
2739
+ * const onMoveMade = () => {
2740
+ * // apply the move...
2741
+ * turns.nextTurn()
2742
+ * }
2743
+ * }
2744
+ * ```
2745
+ */
2746
+ declare function useTurnSystem<P>({ players, initialIndex, onTurnStart, onTurnEnd, aiDelay, }: TurnSystemOptions<P>): TurnSystemControls<P>;
2747
+
2748
+ interface HoverableOptions {
2749
+ /** Override the entity ID to hit-test. Defaults to the surrounding <Entity> context. */
2750
+ entityId?: EntityId;
2751
+ /**
2752
+ * Custom bounds in world units. If omitted, derived from Sprite → BoxCollider →
2753
+ * CircleCollider on the target entity.
2754
+ */
2755
+ bounds?: {
2756
+ width: number;
2757
+ height: number;
2758
+ };
2759
+ /** Called once when the pointer enters the entity. */
2760
+ onEnter?: (id: EntityId) => void;
2761
+ /** Called once when the pointer leaves the entity. */
2762
+ onLeave?: (id: EntityId) => void;
2763
+ /** Disable hover tracking entirely. */
2764
+ disabled?: boolean;
2765
+ }
2766
+ interface HoverableControls {
2767
+ /** True while the pointer is over the entity's bounding box. */
2768
+ isHovered: boolean;
2769
+ }
2770
+ /**
2771
+ * Reactive "is the pointer over this entity?" state. Hit-tests against the
2772
+ * entity's Sprite / BoxCollider / CircleCollider bounds every pointermove,
2773
+ * respecting the active Camera2D zoom and pan.
2774
+ *
2775
+ * Calls `engine.loop.markDirty()` on state change, so in onDemand mode the
2776
+ * canvas re-renders automatically (useful for "highlight under cursor" effects).
2777
+ *
2778
+ * @example
2779
+ * ```tsx
2780
+ * function Card() {
2781
+ * const { isHovered } = useHoverable({
2782
+ * onEnter: (id) => console.log('enter', id),
2783
+ * })
2784
+ * return (
2785
+ * <Entity>
2786
+ * <Transform x={100} y={100} />
2787
+ * <Sprite src="card.png" width={80} height={120} opacity={isHovered ? 1 : 0.8} />
2788
+ * </Entity>
2789
+ * )
2790
+ * }
2791
+ * ```
2792
+ */
2793
+ declare function useHoverable(options?: HoverableOptions): HoverableControls;
2794
+
2795
+ interface DraggableOptions {
2796
+ /** Override the entity ID to drag. Defaults to the surrounding <Entity> context. */
2797
+ entityId?: EntityId;
2798
+ /**
2799
+ * A "tag" string that droppables can filter on via their `accepts` list.
2800
+ * E.g. `tag: 'card'` + droppable `accepts: ['card', 'token']`.
2801
+ */
2802
+ tag?: string;
2803
+ /** Constrain dragging to a world-space rectangle. */
2804
+ bounds?: {
2805
+ minX: number;
2806
+ minY: number;
2807
+ maxX: number;
2808
+ maxY: number;
2809
+ };
2810
+ /**
2811
+ * Optional snap control from {@link useSnap}. While dragging, the entity's
2812
+ * position will be passed through `snap.snap(x, y)` before being written back.
2813
+ */
2814
+ snap?: SnapControls;
2815
+ /** Called when the drag begins. */
2816
+ onDragStart?: (info: {
2817
+ entityId: EntityId;
2818
+ x: number;
2819
+ y: number;
2820
+ }) => void;
2821
+ /** Called on every pointermove during the drag. */
2822
+ onDrag?: (info: {
2823
+ entityId: EntityId;
2824
+ x: number;
2825
+ y: number;
2826
+ }) => void;
2827
+ /** Called when the drag ends (pointerup). */
2828
+ onDragEnd?: (info: {
2829
+ entityId: EntityId;
2830
+ x: number;
2831
+ y: number;
2832
+ droppedOn: EntityId | null;
2833
+ }) => void;
2834
+ /** Disable dragging. */
2835
+ disabled?: boolean;
2836
+ }
2837
+ interface DraggableControls {
2838
+ /** True while a drag is in progress on this entity. */
2839
+ isDragging: boolean;
2840
+ /** Current world-space position of the dragged entity. */
2841
+ position: {
2842
+ x: number;
2843
+ y: number;
2844
+ };
2845
+ }
2846
+ /**
2847
+ * Pointer-driven drag for an entity. Attach to any entity with a Transform and
2848
+ * a Sprite/BoxCollider/CircleCollider (for hit-testing the initial grab).
2849
+ *
2850
+ * On pointerdown over the entity's bounds, dragging begins. Subsequent
2851
+ * pointermoves update the entity's Transform. On pointerup, the drag ends and
2852
+ * the `droppedOn` field of {@link onDragEnd} reports which {@link useDroppable}
2853
+ * (if any) is under the pointer.
2854
+ *
2855
+ * @example
2856
+ * ```tsx
2857
+ * function Card() {
2858
+ * const snap = useSnap({ grid: 32 })
2859
+ * const { isDragging } = useDraggable({ snap, tag: 'card' })
2860
+ * return (
2861
+ * <Entity>
2862
+ * <Transform x={100} y={100} />
2863
+ * <Sprite src="card.png" width={64} height={96} opacity={isDragging ? 0.6 : 1} />
2864
+ * </Entity>
2865
+ * )
2866
+ * }
2867
+ * ```
2868
+ */
2869
+ declare function useDraggable(options?: DraggableOptions): DraggableControls;
2870
+ interface DroppableOptions {
2871
+ /** Override the entity ID acting as the drop zone. Defaults to <Entity> context. */
2872
+ entityId?: EntityId;
2873
+ /**
2874
+ * Tags this zone will accept. If omitted, accepts any drag. If set, only drags
2875
+ * whose `useDraggable` was configured with a matching `tag` can land here.
2876
+ */
2877
+ accepts?: string[];
2878
+ /** Override the drop-zone bounds in world units. */
2879
+ bounds?: {
2880
+ width: number;
2881
+ height: number;
2882
+ };
2883
+ /** Called when an acceptable drag is released over this zone. */
2884
+ onDrop?: (info: {
2885
+ droppedEntityId: EntityId;
2886
+ x: number;
2887
+ y: number;
2888
+ }) => void;
2889
+ /** Called when an acceptable drag enters the zone. */
2890
+ onEnter?: (info: {
2891
+ droppedEntityId: EntityId;
2892
+ }) => void;
2893
+ /** Called when an acceptable drag leaves the zone. */
2894
+ onLeave?: (info: {
2895
+ droppedEntityId: EntityId;
2896
+ }) => void;
2897
+ /** Disable this drop zone. */
2898
+ disabled?: boolean;
2899
+ }
2900
+ interface DroppableControls {
2901
+ /** True while an acceptable drag is hovering over this zone. */
2902
+ isOver: boolean;
2903
+ /** The entity ID of the drag currently hovering, or null. */
2904
+ hoveredBy: EntityId | null;
2905
+ }
2906
+ /**
2907
+ * A drop zone for drag-and-drop. Pair with {@link useDraggable} to build
2908
+ * puzzle games, card games, inventory systems, level editors, and more.
2909
+ *
2910
+ * @example
2911
+ * ```tsx
2912
+ * function Foundation() {
2913
+ * const { isOver } = useDroppable({
2914
+ * accepts: ['card'],
2915
+ * onDrop: ({ droppedEntityId }) => placeCard(droppedEntityId),
2916
+ * })
2917
+ * return (
2918
+ * <Entity>
2919
+ * <BoxCollider width={80} height={120} />
2920
+ * <Sprite color={isOver ? '#4fc3f7' : '#333'} width={80} height={120} />
2921
+ * </Entity>
2922
+ * )
2923
+ * }
2924
+ * ```
2925
+ */
2926
+ declare function useDroppable(options?: DroppableOptions): DroppableControls;
2927
+
2928
+ interface FocusableOptions {
2929
+ /** Called when this entity gains keyboard focus. */
2930
+ onFocus?: (id: EntityId) => void;
2931
+ /** Called when this entity loses keyboard focus. */
2932
+ onBlur?: (id: EntityId) => void;
2933
+ /** Called when the user activates this entity (Enter/Space). */
2934
+ onActivate?: (id: EntityId) => void;
2935
+ /** If true and nothing else is focused when this mounts, take initial focus. */
2936
+ autoFocus?: boolean;
2937
+ }
2938
+ /**
2939
+ * Register an entity as keyboard-focusable. Pair with {@link useKeyboardFocus}
2940
+ * and optionally {@link FocusRing} for a full accessible keyboard navigation
2941
+ * flow. Arrow keys move focus to the spatially-nearest registered entity in
2942
+ * the arrow's direction; Enter and Space trigger `onActivate`.
2943
+ *
2944
+ * @example
2945
+ * ```tsx
2946
+ * function Card({ id }: { id: number }) {
2947
+ * useFocusable({ onActivate: () => selectCard(id) })
2948
+ * return <Entity id={id}>…</Entity>
2949
+ * }
2950
+ * ```
2951
+ */
2952
+ declare function useFocusable(options?: FocusableOptions): void;
2953
+ interface KeyboardFocusControls {
2954
+ /** The currently focused entity ID, or null. */
2955
+ focused: EntityId | null;
2956
+ /** Manually set focus to a specific entity. */
2957
+ focus(id: EntityId | null): void;
2958
+ /** Move focus to the next registered entity (insertion order). */
2959
+ next(): void;
2960
+ /** Move focus to the previous registered entity. */
2961
+ prev(): void;
2962
+ /** Move focus in a world-space direction using spatial nearest-neighbor. */
2963
+ move(direction: 'up' | 'down' | 'left' | 'right'): void;
2964
+ /** Activate the currently focused entity (fires its `onActivate`). */
2965
+ activate(): void;
2966
+ }
2967
+ /**
2968
+ * Keyboard-driven focus navigation across all {@link useFocusable} entities in
2969
+ * the scene. Arrow keys move focus spatially (nearest neighbor in the arrow's
2970
+ * direction), Tab/Shift+Tab cycles through registration order, and Enter/Space
2971
+ * activates the focused entity.
2972
+ *
2973
+ * @example
2974
+ * ```tsx
2975
+ * function Board() {
2976
+ * useKeyboardFocus() // arrow keys automatically move between cards
2977
+ * return (
2978
+ * <>
2979
+ * <FocusRing />
2980
+ * <Card id={1} />
2981
+ * <Card id={2} />
2982
+ * </>
2983
+ * )
2984
+ * }
2985
+ * ```
2986
+ */
2987
+ declare function useKeyboardFocus(): KeyboardFocusControls;
2988
+
2989
+ interface FocusRingProps {
2990
+ /** Ring color. Default '#4fc3f7'. */
2991
+ color?: string;
2992
+ /** Ring width in CSS pixels. Default 3. */
2993
+ width?: number;
2994
+ /** Extra padding around the entity bounds in CSS pixels. Default 4. */
2995
+ padding?: number;
2996
+ /** Border radius in CSS pixels. Default 4. */
2997
+ borderRadius?: number;
2998
+ /** Show a pulsing animation. Default true. */
2999
+ pulse?: boolean;
3000
+ }
3001
+ /**
3002
+ * Visual indicator of the currently keyboard-focused entity. Pairs with
3003
+ * {@link useKeyboardFocus} and {@link useFocusable} to provide a full sighted
3004
+ * keyboard navigation experience.
3005
+ *
3006
+ * Renders as a DOM overlay outline that tracks the focused entity's Transform
3007
+ * and bounds. Respects `prefers-reduced-motion` (disables the pulse when set).
3008
+ *
3009
+ * @example
3010
+ * ```tsx
3011
+ * <Stage>
3012
+ * <FocusRing color="#4fc3f7" />
3013
+ * {/* cards that use useFocusable() *\/}
3014
+ * </Stage>
3015
+ * ```
3016
+ */
3017
+ declare function FocusRing({ color, width, padding, borderRadius, pulse, }: FocusRingProps): react_jsx_runtime.JSX.Element | null;
3018
+
3019
+ /**
3020
+ * Scene save/load helpers. Thin wrappers around the ECS snapshot API that
3021
+ * handle JSON serialization and localStorage persistence for editors,
3022
+ * "save your creation" features, and quick dev-time checkpoints.
3023
+ *
3024
+ * For full-featured game saves that include non-ECS state (audio settings,
3025
+ * progression flags, player profiles), use {@link useSave} or {@link useIDBSave}
3026
+ * from `cubeforge`.
3027
+ */
3028
+ interface SceneSaveOptions {
3029
+ /** Pretty-print the JSON output. Default false. */
3030
+ pretty?: boolean;
3031
+ }
3032
+ /**
3033
+ * Serialize the entire ECS world to a JSON string. The returned string can be
3034
+ * passed back to {@link loadScene} to restore the exact same state.
3035
+ *
3036
+ * @example
3037
+ * ```ts
3038
+ * const json = saveScene(engine)
3039
+ * localStorage.setItem('my-level', json)
3040
+ * ```
3041
+ */
3042
+ declare function saveScene(engine: EngineState, options?: SceneSaveOptions): string;
3043
+ /**
3044
+ * Restore the ECS world from a JSON string previously produced by
3045
+ * {@link saveScene}. Clears the current world state before applying. In
3046
+ * onDemand loop mode, automatically calls `markDirty()` to re-render.
3047
+ *
3048
+ * Throws if the input isn't valid JSON or doesn't match the snapshot shape.
3049
+ *
3050
+ * @example
3051
+ * ```ts
3052
+ * const json = localStorage.getItem('my-level')
3053
+ * if (json) loadScene(engine, json)
3054
+ * ```
3055
+ */
3056
+ declare function loadScene(engine: EngineState, json: string): void;
3057
+ /**
3058
+ * Save the current scene to `localStorage` under the given key. Returns true
3059
+ * on success, false if the browser rejected the write (quota exceeded,
3060
+ * incognito mode, or localStorage unavailable).
3061
+ *
3062
+ * @example
3063
+ * ```ts
3064
+ * if (saveSceneToLocalStorage(engine, 'my-game:autosave')) {
3065
+ * toast.show('Saved!')
3066
+ * }
3067
+ * ```
3068
+ */
3069
+ declare function saveSceneToLocalStorage(engine: EngineState, key: string, options?: SceneSaveOptions): boolean;
3070
+ /**
3071
+ * Load a scene previously saved with {@link saveSceneToLocalStorage}. Returns
3072
+ * true if a scene was found and successfully loaded, false otherwise.
3073
+ *
3074
+ * @example
3075
+ * ```ts
3076
+ * if (!loadSceneFromLocalStorage(engine, 'my-game:autosave')) {
3077
+ * // No autosave — start fresh
3078
+ * seedInitialScene(engine)
3079
+ * }
3080
+ * ```
3081
+ */
3082
+ declare function loadSceneFromLocalStorage(engine: EngineState, key: string): boolean;
3083
+ /**
3084
+ * Delete a saved scene from localStorage. Returns true if anything was deleted.
3085
+ */
3086
+ declare function deleteSavedScene(key: string): boolean;
3087
+ /**
3088
+ * List all scene keys saved via {@link saveSceneToLocalStorage} that match a
3089
+ * prefix. Useful for implementing a "load slot" picker.
3090
+ *
3091
+ * @example
3092
+ * ```ts
3093
+ * const slots = listSavedScenes('my-game:')
3094
+ * // → ['my-game:slot1', 'my-game:slot2', 'my-game:autosave']
3095
+ * ```
3096
+ */
3097
+ declare function listSavedScenes(prefix?: string): string[];
3098
+
2003
3099
  declare function playClip(world: ECSWorld, entityId: EntityId, clipName: string): void;
2004
3100
  declare function setAnimationState(world: ECSWorld, entityId: EntityId, stateName: string): void;
2005
3101
  declare function setAnimatorParam(world: ECSWorld, entityId: EntityId, name: string, value: AnimatorParamValue): void;
@@ -2106,4 +3202,4 @@ declare function useRemotePlayer(config: {
2106
3202
  opts?: RemotePlayerOptions;
2107
3203
  }): RemotePlayerControls;
2108
3204
 
2109
- export { type AccessibilityControls, AnimatedSprite, type AnimatedSpriteProps, Animation, type AnimationSet, Animator, AssetLoader, type BoundInputMap, BoxCollider, Camera2D, type CameraControls, type CameraLookaheadOptions, CameraZone, CapsuleCollider, Checkpoint, Circle, CircleCollider, type ComboDetectorResult, CompoundCollider, ConvexCollider, type CoordinateHelpers, type CoroutineControls, type CoroutineFactory, type CoroutineYield, Entity, Game, type GameControls, type GamepadState, type GestureHandlers, type GestureOptions, Gradient, type HMRControls, HalfSpaceCollider, HeightFieldCollider, type InputContextControls, Joint, Line, Mask, MovingPlatform, type NetworkSyncOptions, NineSlice, ParallaxLayer, ParticleEmitter, type ParticlePreset, type PauseControls, type PinchEvent, Polygon, type PreloadState, type ProfilerData, type RemotePlayerControls, type RemotePlayerOptions, RigidBody, type SceneManagerControls, ScreenFlash, type ScreenFlashHandle, Script, SegmentCollider, type SnapshotControls, Sprite, type SpriteAtlas, SquashStretch, type SquashStretchControls, type SwipeEvent, Text, type TiledLayer, type TiledObject, Tilemap, type TimerControls, type TouchControls, Trail, Transform, TriMeshCollider, TriangleCollider, type VirtualInputState, VirtualJoystick, type VirtualJoystickProps, type Waypoint, World, createAtlas, defineAnimations, definePrefab, playClip, setAnimationState, setAnimatorParam, useAccessibility, useAudioListener, useCamera, useCameraLookahead, useComboDetector, useCoordinates, useCoroutine, useDestroyEntity, useEntity, useEvent, useEvents, useGame, useGamepad, useGamepadHaptics, useGestures, useHMR, useInput, useInputBuffer, useInputContext, useInputMap, useInputRecorder, useLocalMultiplayer, useNetworkSync, useParent, usePause, usePlayerInput, usePostProcess, usePreload, useProfiler, useRemotePlayer, useSceneManager, useSnapshot, useSquashStretch, useTimer, useTouch, useVirtualInput, useWebGLPostProcess, useWorldQuery, wait, waitFrames, waitUntil };
3205
+ export { A11yNode, type A11yNodeProps, type AccessibilityControls, AnimatedSprite, type AnimatedSpriteProps, Animation, type AnimationSet, Animator, AssetLoader, type BoundInputMap, BoxCollider, Camera2D, type CameraControls, type CameraLookaheadOptions, CameraZone, CapsuleCollider, Checkpoint, Circle, CircleCollider, type ComboDetectorResult, CompoundCollider, ConvexCollider, type CoordinateHelpers, type CoroutineControls, type CoroutineFactory, type CoroutineYield, type DraggableControls, type DraggableOptions, type DroppableControls, type DroppableOptions, EditableText, type EditableTextProps, Entity, type ExportOptions, FocusRing, type FocusRingProps, type FocusableOptions, Game, type GameControls, type GamepadState, type GestureHandlers, type GestureOptions, Gradient, type GridCell, type GridControls, type GridOptions, type HMRControls, HalfSpaceCollider, HeightFieldCollider, type HistoryControls, type HistoryOptions, type HoverableControls, type HoverableOptions, type InputContextControls, Joint, type KeyboardFocusControls, Line, Mask, MovingPlatform, type NetworkSyncOptions, NineSlice, ParallaxLayer, ParticleEmitter, type ParticlePreset, type PauseControls, type PinchEvent, Polygon, type PreloadState, type ProfilerData, type RemotePlayerControls, type RemotePlayerOptions, RigidBody, type SceneManagerControls, type SceneSaveOptions, type SceneTransitionControls, SceneTransitionOverlay, ScreenFlash, type ScreenFlashHandle, Script, SegmentCollider, type SelectOptions, Selection, type SelectionControls, type SelectionProps, type SnapControls, type SnapOptions, type SnapResult, type SnapshotControls, Sprite, type SpriteAtlas, SquashStretch, type SquashStretchControls, Stage, type SwipeEvent, Text, type TiledLayer, type TiledObject, Tilemap, type TimerControls, type TouchControls, Trail, Transform, TransformHandles, type TransformHandlesProps, type TransitionEffect, TriMeshCollider, TriangleCollider, type TurnSystemControls, type TurnSystemOptions, VectorPath, type VectorPathProps, type VirtualInputState, VirtualJoystick, type VirtualJoystickProps, type Waypoint, World, createAtlas, defineAnimations, definePrefab, deleteSavedScene, downloadCanvas, exportToBlob, exportToDataURL, listSavedScenes, loadScene, loadSceneFromLocalStorage, playClip, saveScene, saveSceneToLocalStorage, setAnimationState, setAnimatorParam, useAccessibility, useAudioListener, useCamera, useCameraLookahead, useComboDetector, useCoordinates, useCoroutine, useDestroyEntity, useDraggable, useDroppable, useEntity, useEvent, useEvents, useFocusable, useGame, useGamepad, useGamepadHaptics, useGestures, useGrid, useHMR, useHistory, useHitstop, useHoverable, useInput, useInputBuffer, useInputContext, useInputMap, useInputRecorder, useKeyboardFocus, useLocalMultiplayer, useNetworkSync, useParent, usePause, usePlayerInput, usePostProcess, usePreload, useProfiler, useRemotePlayer, useSceneManager, useSceneTransition, useSelection, useSnap, useSnapshot, useSquashStretch, useTimer, useTouch, useTurnSystem, useVirtualInput, useWebGLPostProcess, useWorldQuery, wait, waitFrames, waitUntil };