ecspresso 0.13.4 → 0.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +3 -6
  2. package/dist/index.js +2 -2
  3. package/dist/index.js.map +4 -4
  4. package/dist/plugins/ai/behavior-tree.d.ts +369 -0
  5. package/dist/plugins/ai/behavior-tree.js +4 -0
  6. package/dist/plugins/ai/behavior-tree.js.map +10 -0
  7. package/dist/plugins/ai/detection.js +2 -2
  8. package/dist/plugins/ai/detection.js.map +2 -2
  9. package/dist/plugins/ai/flocking.js +2 -2
  10. package/dist/plugins/ai/flocking.js.map +2 -2
  11. package/dist/plugins/ai/pathfinding.d.ts +163 -0
  12. package/dist/plugins/ai/pathfinding.js +4 -0
  13. package/dist/plugins/ai/pathfinding.js.map +10 -0
  14. package/dist/plugins/audio/audio.js +2 -2
  15. package/dist/plugins/audio/audio.js.map +2 -2
  16. package/dist/plugins/combat/health.js +2 -2
  17. package/dist/plugins/combat/health.js.map +2 -2
  18. package/dist/plugins/combat/projectile.js +2 -2
  19. package/dist/plugins/combat/projectile.js.map +2 -2
  20. package/dist/plugins/debug/diagnostics.js +3 -3
  21. package/dist/plugins/debug/diagnostics.js.map +2 -2
  22. package/dist/plugins/input/input.d.ts +105 -27
  23. package/dist/plugins/input/input.js +2 -2
  24. package/dist/plugins/input/input.js.map +3 -3
  25. package/dist/plugins/input/selection.js +2 -2
  26. package/dist/plugins/input/selection.js.map +2 -2
  27. package/dist/plugins/isometric/depth-sort.js +2 -2
  28. package/dist/plugins/isometric/depth-sort.js.map +2 -2
  29. package/dist/plugins/isometric/projection.js +2 -2
  30. package/dist/plugins/isometric/projection.js.map +2 -2
  31. package/dist/plugins/physics/collision.js +2 -2
  32. package/dist/plugins/physics/collision.js.map +2 -2
  33. package/dist/plugins/physics/collision3D.d.ts +83 -0
  34. package/dist/plugins/physics/collision3D.js +4 -0
  35. package/dist/plugins/physics/collision3D.js.map +13 -0
  36. package/dist/plugins/physics/physics2D.js +2 -2
  37. package/dist/plugins/physics/physics2D.js.map +2 -2
  38. package/dist/plugins/physics/physics3D.d.ts +140 -0
  39. package/dist/plugins/physics/physics3D.js +4 -0
  40. package/dist/plugins/physics/physics3D.js.map +11 -0
  41. package/dist/plugins/physics/steering.js +2 -2
  42. package/dist/plugins/physics/steering.js.map +2 -2
  43. package/dist/plugins/rendering/particles.js +2 -2
  44. package/dist/plugins/rendering/particles.js.map +2 -2
  45. package/dist/plugins/rendering/renderer2D.js +2 -2
  46. package/dist/plugins/rendering/renderer2D.js.map +3 -3
  47. package/dist/plugins/rendering/renderer3D.d.ts +247 -0
  48. package/dist/plugins/rendering/renderer3D.js +4107 -0
  49. package/dist/plugins/rendering/renderer3D.js.map +12 -0
  50. package/dist/plugins/rendering/sprite-animation.js +2 -2
  51. package/dist/plugins/rendering/sprite-animation.js.map +2 -2
  52. package/dist/plugins/rendering/tilemap.d.ts +230 -0
  53. package/dist/plugins/rendering/tilemap.js +4 -0
  54. package/dist/plugins/rendering/tilemap.js.map +11 -0
  55. package/dist/plugins/scripting/coroutine.js +2 -2
  56. package/dist/plugins/scripting/coroutine.js.map +2 -2
  57. package/dist/plugins/scripting/state-machine.js +2 -2
  58. package/dist/plugins/scripting/state-machine.js.map +2 -2
  59. package/dist/plugins/scripting/timers.js +2 -2
  60. package/dist/plugins/scripting/timers.js.map +2 -2
  61. package/dist/plugins/scripting/tween.js +2 -2
  62. package/dist/plugins/scripting/tween.js.map +2 -2
  63. package/dist/plugins/spatial/bounds.js +2 -2
  64. package/dist/plugins/spatial/bounds.js.map +2 -2
  65. package/dist/plugins/spatial/camera.js +2 -2
  66. package/dist/plugins/spatial/camera.js.map +2 -2
  67. package/dist/plugins/spatial/camera3D.d.ts +112 -0
  68. package/dist/plugins/spatial/camera3D.js +4 -0
  69. package/dist/plugins/spatial/camera3D.js.map +10 -0
  70. package/dist/plugins/spatial/spatial-index.js +2 -2
  71. package/dist/plugins/spatial/spatial-index.js.map +3 -3
  72. package/dist/plugins/spatial/spatial-index3D.d.ts +80 -0
  73. package/dist/plugins/spatial/spatial-index3D.js +4 -0
  74. package/dist/plugins/spatial/spatial-index3D.js.map +11 -0
  75. package/dist/plugins/spatial/transform.js +2 -2
  76. package/dist/plugins/spatial/transform.js.map +2 -2
  77. package/dist/plugins/spatial/transform3D.d.ts +148 -0
  78. package/dist/plugins/spatial/transform3D.js +4 -0
  79. package/dist/plugins/spatial/transform3D.js.map +10 -0
  80. package/dist/plugins/ui/ui.d.ts +116 -0
  81. package/dist/plugins/ui/ui.js +4 -0
  82. package/dist/plugins/ui/ui.js.map +11 -0
  83. package/dist/system-builder.d.ts +31 -0
  84. package/dist/utils/math.d.ts +65 -1
  85. package/dist/utils/narrowphase3D.d.ts +120 -0
  86. package/dist/utils/spatial-hash3D.d.ts +72 -0
  87. package/package.json +44 -4
@@ -1,4 +1,4 @@
1
- var G=((j)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(j,{get:(z,E)=>(typeof require<"u"?require:z)[E]}):j)(function(j){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+j+'" is not supported')});import{definePlugin as Z}from"ecspresso";function Y(j){return Object.freeze({frames:Object.freeze([...j.frames]),frameDuration:j.frameDuration??0.1,frameDurations:j.frameDurations?Object.freeze([...j.frameDurations]):null,loop:j.loop??"loop"})}function U(j,z){return Object.freeze({id:j,clips:Object.freeze({default:Y(z)}),defaultClip:"default"})}function q(j,z,E){let J={},H=Object.keys(z);for(let N of H)J[N]=Y(z[N]);let M=H[0];if(!M)throw Error("defineSpriteAnimations: clips object must have at least one key");return Object.freeze({id:j,clips:Object.freeze(J),defaultClip:E?.defaultClip??M})}function D(j,z){let E=z?.initial??j.defaultClip;return{spriteAnimation:{set:j,current:E,currentFrame:0,elapsed:0,playing:!0,speed:z?.speed??1,direction:1,totalLoops:z?.totalLoops??-1,completedLoops:0,justFinished:!1,onComplete:z?.onComplete}}}function K(j,z,E,J){let H=j.getComponent(z,"spriteAnimation");if(!H)return!1;if(!(E in H.set.clips))return!1;if(E!==H.current||J?.restart===!0)H.current=E,H.currentFrame=0,H.elapsed=0,H.direction=1,H.completedLoops=0,H.justFinished=!1;if(H.playing=!0,J?.speed!==void 0)H.speed=J.speed;return j.markChanged(z,"spriteAnimation"),!0}function S(j,z){let E=j.getComponent(z,"spriteAnimation");if(!E)return!1;return E.playing=!1,!0}function A(j,z){let E=j.getComponent(z,"spriteAnimation");if(!E)return!1;return E.playing=!0,!0}function W(j,z,E){j.playing=!1,j.justFinished=!0,j.onComplete?.({entityId:z,animation:j.current}),E.commands.removeComponent(z,"spriteAnimation")}function _(j,z,E,J){if(j.completedLoops++,z.loop==="once")return W(j,E,J),!1;if(j.totalLoops>0&&j.completedLoops>=j.totalLoops)return W(j,E,J),!1;if(z.loop==="pingPong")return j.direction=j.direction===1?-1:1,j.currentFrame+=j.direction,j.elapsed>0;return j.currentFrame=0,j.elapsed>0}function X(j,z,E,J){let H=j.currentFrame+j.direction;if(H>=z.frames.length||H<0)return _(j,z,E,J);return j.currentFrame=H,!0}function $(j,z,E,J){while(!0){let H=z.frameDurations!==null?z.frameDurations[j.currentFrame]??z.frameDuration:z.frameDuration;if(H<=0){if(!X(j,z,E,J))return;continue}let M=H-j.elapsed;if(M>0.000001)return;if(j.elapsed=M<0?-M:0,!X(j,z,E,J))return}}function b(j){let{systemGroup:z="spriteAnimation",priority:E=0,phase:J="update"}=j??{};return Z("spriteAnimation").withComponentTypes().withLabels().withGroups().install((H)=>{H.addSystem("sprite-animation-update").setPriority(E).inPhase(J).inGroup(z).addQuery("animations",{with:["spriteAnimation"]}).setProcess(({queries:M,dt:N,ecs:V})=>{for(let O of M.animations){let L=O.components.spriteAnimation,Q=L.set.clips[L.current];if(!Q)continue;if(L.justFinished){L.justFinished=!1;continue}if(!L.playing)continue;if(Q.frames.length<=1)continue;let R=L.currentFrame;if(L.elapsed+=N*L.speed,$(L,Q,O.id,V),L.currentFrame!==R||R===0)B(O.components,L,Q);if(L.currentFrame!==R)V.markChanged(O.id,"spriteAnimation")}})})}function B(j,z,E){let J=j.sprite;if(J&&typeof J==="object"&&"texture"in J)J.texture=E.frames[z.currentFrame]}export{S as stopAnimation,A as resumeAnimation,K as playAnimation,q as defineSpriteAnimations,U as defineSpriteAnimation,b as createSpriteAnimationPlugin,D as createSpriteAnimation};
1
+ var Z=Object.defineProperty;var _=(j)=>j;function $(j,z){this[j]=_.bind(null,z)}var U=(j,z)=>{for(var E in z)Z(j,E,{get:z[E],enumerable:!0,configurable:!0,set:$.bind(z,E)})};var q=(j,z)=>()=>(j&&(z=j(j=0)),z);var D=((j)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(j,{get:(z,E)=>(typeof require<"u"?require:z)[E]}):j)(function(j){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+j+'" is not supported')});import{definePlugin as B}from"ecspresso";function Y(j){return Object.freeze({frames:Object.freeze([...j.frames]),frameDuration:j.frameDuration??0.1,frameDurations:j.frameDurations?Object.freeze([...j.frameDurations]):null,loop:j.loop??"loop"})}function A(j,z){return Object.freeze({id:j,clips:Object.freeze({default:Y(z)}),defaultClip:"default"})}function b(j,z,E){let J={},H=Object.keys(z);for(let N of H)J[N]=Y(z[N]);let M=H[0];if(!M)throw Error("defineSpriteAnimations: clips object must have at least one key");return Object.freeze({id:j,clips:Object.freeze(J),defaultClip:E?.defaultClip??M})}function k(j,z){let E=z?.initial??j.defaultClip;return{spriteAnimation:{set:j,current:E,currentFrame:0,elapsed:0,playing:!0,speed:z?.speed??1,direction:1,totalLoops:z?.totalLoops??-1,completedLoops:0,justFinished:!1,onComplete:z?.onComplete}}}function w(j,z,E,J){let H=j.getComponent(z,"spriteAnimation");if(!H)return!1;if(!(E in H.set.clips))return!1;if(E!==H.current||J?.restart===!0)H.current=E,H.currentFrame=0,H.elapsed=0,H.direction=1,H.completedLoops=0,H.justFinished=!1;if(H.playing=!0,J?.speed!==void 0)H.speed=J.speed;return j.markChanged(z,"spriteAnimation"),!0}function h(j,z){let E=j.getComponent(z,"spriteAnimation");if(!E)return!1;return E.playing=!1,!0}function x(j,z){let E=j.getComponent(z,"spriteAnimation");if(!E)return!1;return E.playing=!0,!0}function W(j,z,E){j.playing=!1,j.justFinished=!0,j.onComplete?.({entityId:z,animation:j.current}),E.commands.removeComponent(z,"spriteAnimation")}function G(j,z,E,J){if(j.completedLoops++,z.loop==="once")return W(j,E,J),!1;if(j.totalLoops>0&&j.completedLoops>=j.totalLoops)return W(j,E,J),!1;if(z.loop==="pingPong")return j.direction=j.direction===1?-1:1,j.currentFrame+=j.direction,j.elapsed>0;return j.currentFrame=0,j.elapsed>0}function X(j,z,E,J){let H=j.currentFrame+j.direction;if(H>=z.frames.length||H<0)return G(j,z,E,J);return j.currentFrame=H,!0}function P(j,z,E,J){while(!0){let H=z.frameDurations!==null?z.frameDurations[j.currentFrame]??z.frameDuration:z.frameDuration;if(H<=0){if(!X(j,z,E,J))return;continue}let M=H-j.elapsed;if(M>0.000001)return;if(j.elapsed=M<0?-M:0,!X(j,z,E,J))return}}function g(j){let{systemGroup:z="spriteAnimation",priority:E=0,phase:J="update"}=j??{};return B("spriteAnimation").withComponentTypes().withLabels().withGroups().install((H)=>{H.addSystem("sprite-animation-update").setPriority(E).inPhase(J).inGroup(z).addQuery("animations",{with:["spriteAnimation"]}).setProcess(({queries:M,dt:N,ecs:V})=>{for(let O of M.animations){let L=O.components.spriteAnimation,Q=L.set.clips[L.current];if(!Q)continue;if(L.justFinished){L.justFinished=!1;continue}if(!L.playing)continue;if(Q.frames.length<=1)continue;let R=L.currentFrame;if(L.elapsed+=N*L.speed,P(L,Q,O.id,V),L.currentFrame!==R||R===0)T(O.components,L,Q);if(L.currentFrame!==R)V.markChanged(O.id,"spriteAnimation")}})})}function T(j,z,E){let J=j.sprite;if(J&&typeof J==="object"&&"texture"in J)J.texture=E.frames[z.currentFrame]}export{h as stopAnimation,x as resumeAnimation,w as playAnimation,b as defineSpriteAnimations,A as defineSpriteAnimation,g as createSpriteAnimationPlugin,k as createSpriteAnimation};
2
2
 
3
- //# debugId=9EF329142BBA6E6864756E2164756E21
3
+ //# debugId=25B3E2A395B1F4B164756E2164756E21
4
4
  //# sourceMappingURL=sprite-animation.js.map
@@ -4,7 +4,7 @@
4
4
  "sourcesContent": [
5
5
  "/**\n * Sprite Animation Plugin for ECSpresso\n *\n * ECS-native frame-based sprite animation. Advances through spritesheet frames,\n * handles loop modes (once, loop, pingPong), publishes completion events, and\n * syncs the current frame's texture to the PixiJS Sprite via structural access.\n *\n * Renderer2D is a required dependency — the `sprite` component comes from that plugin.\n * This plugin declares only `spriteAnimation` as its component type.\n */\n\nimport { definePlugin, type BasePluginOptions } from 'ecspresso';\nimport type { BaseWorld } from 'ecspresso';\n\n/** BaseWorld narrowed to sprite-animation components for typed access in helpers. */\ntype SpriteAnimationWorld = BaseWorld<SpriteAnimationComponentTypes>;\n\n// ==================== Loop Mode ====================\n\nexport type AnimationLoopMode = 'once' | 'loop' | 'pingPong';\n\n// ==================== Clip Types ====================\n\n/**\n * A single animation clip: an ordered sequence of texture frames with timing.\n * Immutable and shared across entities.\n */\nexport interface SpriteAnimationClip {\n\treadonly frames: readonly unknown[];\n\treadonly frameDuration: number;\n\treadonly frameDurations: readonly number[] | null;\n\treadonly loop: AnimationLoopMode;\n}\n\n/**\n * Input format for defining a clip. Accepts either uniform or per-frame timing.\n */\nexport interface SpriteAnimationClipInput {\n\t/** Array of PixiJS Texture objects */\n\tframes: readonly unknown[];\n\t/** Uniform seconds-per-frame (used when frameDurations is not provided) */\n\tframeDuration?: number;\n\t/** Per-frame durations in seconds (overrides frameDuration) */\n\tframeDurations?: readonly number[];\n\t/** Loop mode (default: 'loop') */\n\tloop?: AnimationLoopMode;\n}\n\n// ==================== Animation Set ====================\n\n/**\n * A named collection of animation clips. Immutable and shared across entities.\n * Parameterized by A (animation name union) for compile-time validation.\n */\nexport interface SpriteAnimationSet<A extends string = string> {\n\treadonly id: string;\n\treadonly clips: { readonly [K in A]: SpriteAnimationClip };\n\treadonly defaultClip: A;\n}\n\n// ==================== Component ====================\n\n/**\n * Per-entity runtime animation state.\n */\nexport interface SpriteAnimation<A extends string = string> {\n\treadonly set: SpriteAnimationSet<A>;\n\tcurrent: A;\n\tcurrentFrame: number;\n\telapsed: number;\n\tplaying: boolean;\n\tspeed: number;\n\tdirection: 1 | -1;\n\ttotalLoops: number;\n\tcompletedLoops: number;\n\tjustFinished: boolean;\n\tonComplete?: (data: SpriteAnimationEventData) => void;\n}\n\n/**\n * Component types provided by the sprite animation plugin.\n */\nexport interface SpriteAnimationComponentTypes<A extends string = string> {\n\tspriteAnimation: SpriteAnimation<A>;\n}\n\n// ==================== Event Types ====================\n\n/**\n * Data published when an animation completes.\n */\nexport interface SpriteAnimationEventData {\n\tentityId: number;\n\tanimation: string;\n}\n\n// ==================== Plugin Options ====================\n\nexport interface SpriteAnimationPluginOptions<G extends string = 'spriteAnimation'> extends BasePluginOptions<G> {}\n\n// ==================== Helper Functions ====================\n\nfunction buildClip(input: SpriteAnimationClipInput): SpriteAnimationClip {\n\treturn Object.freeze({\n\t\tframes: Object.freeze([...input.frames]),\n\t\tframeDuration: input.frameDuration ?? (1 / 10),\n\t\tframeDurations: input.frameDurations\n\t\t\t? Object.freeze([...input.frameDurations])\n\t\t\t: null,\n\t\tloop: input.loop ?? 'loop',\n\t});\n}\n\n/**\n * Define a single-clip animation set named 'default'.\n * For simple use cases like spinning coins, pulsing effects, etc.\n *\n * @param id Unique identifier for this animation set\n * @param clip Clip definition\n * @returns A frozen SpriteAnimationSet with one clip named 'default'\n */\nexport function defineSpriteAnimation(\n\tid: string,\n\tclip: SpriteAnimationClipInput,\n): SpriteAnimationSet<'default'> {\n\treturn Object.freeze({\n\t\tid,\n\t\tclips: Object.freeze({ default: buildClip(clip) }),\n\t\tdefaultClip: 'default' as const,\n\t});\n}\n\n/**\n * Define a multi-clip animation set with named animations.\n * Animation names are inferred from the keys of the clips object.\n *\n * @param id Unique identifier for this animation set\n * @param clips Object mapping animation names to clip definitions\n * @param options Optional configuration (defaultClip)\n * @returns A frozen SpriteAnimationSet with inferred animation name union\n */\nexport function defineSpriteAnimations<A extends string>(\n\tid: string,\n\tclips: Record<A, SpriteAnimationClipInput>,\n\toptions?: { defaultClip?: NoInfer<A> },\n): SpriteAnimationSet<A> {\n\tconst builtClips = {} as Record<A, SpriteAnimationClip>;\n\tconst keys = Object.keys(clips) as A[];\n\n\tfor (const key of keys) {\n\t\tbuiltClips[key] = buildClip(clips[key]);\n\t}\n\n\tconst firstKey = keys[0];\n\tif (!firstKey) {\n\t\tthrow new Error(`defineSpriteAnimations: clips object must have at least one key`);\n\t}\n\n\treturn Object.freeze({\n\t\tid,\n\t\tclips: Object.freeze(builtClips),\n\t\tdefaultClip: options?.defaultClip ?? firstKey,\n\t});\n}\n\n/**\n * Create a spriteAnimation component from an animation set.\n *\n * @param set The animation set to use\n * @param options Optional configuration (initial clip, speed, onComplete event)\n * @returns Component object suitable for spreading into spawn()\n */\nexport function createSpriteAnimation<A extends string>(\n\tset: SpriteAnimationSet<A>,\n\toptions?: {\n\t\tinitial?: A;\n\t\tspeed?: number;\n\t\ttotalLoops?: number;\n\t\tonComplete?: (data: SpriteAnimationEventData) => void;\n\t},\n): Pick<SpriteAnimationComponentTypes<A>, 'spriteAnimation'> {\n\tconst initial = options?.initial ?? set.defaultClip;\n\treturn {\n\t\tspriteAnimation: {\n\t\t\tset,\n\t\t\tcurrent: initial,\n\t\t\tcurrentFrame: 0,\n\t\t\telapsed: 0,\n\t\t\tplaying: true,\n\t\t\tspeed: options?.speed ?? 1,\n\t\t\tdirection: 1,\n\t\t\ttotalLoops: options?.totalLoops ?? -1,\n\t\t\tcompletedLoops: 0,\n\t\t\tjustFinished: false,\n\t\t\tonComplete: options?.onComplete,\n\t\t},\n\t};\n}\n\n/**\n * Switch an entity's current animation at runtime.\n * Resets state if switching to a different animation (or restart=true).\n *\n * @returns false if entity has no spriteAnimation or animation name doesn't exist\n */\nexport function playAnimation(\n\tecs: SpriteAnimationWorld,\n\tentityId: number,\n\tanimation: string,\n\toptions?: { restart?: boolean; speed?: number },\n): boolean {\n\tconst anim = ecs.getComponent(entityId, 'spriteAnimation');\n\tif (!anim) return false;\n\tif (!(animation in anim.set.clips)) return false;\n\n\tconst shouldReset = animation !== anim.current || options?.restart === true;\n\n\tif (shouldReset) {\n\t\tanim.current = animation;\n\t\tanim.currentFrame = 0;\n\t\tanim.elapsed = 0;\n\t\tanim.direction = 1;\n\t\tanim.completedLoops = 0;\n\t\tanim.justFinished = false;\n\t}\n\n\tanim.playing = true;\n\n\tif (options?.speed !== undefined) {\n\t\tanim.speed = options.speed;\n\t}\n\n\tecs.markChanged(entityId, 'spriteAnimation');\n\treturn true;\n}\n\n/**\n * Pause an entity's animation.\n *\n * @returns false if entity has no spriteAnimation\n */\nexport function stopAnimation(\n\tecs: SpriteAnimationWorld,\n\tentityId: number,\n): boolean {\n\tconst anim = ecs.getComponent(entityId, 'spriteAnimation');\n\tif (!anim) return false;\n\n\tanim.playing = false;\n\treturn true;\n}\n\n/**\n * Resume a paused animation.\n *\n * @returns false if entity has no spriteAnimation\n */\nexport function resumeAnimation(\n\tecs: SpriteAnimationWorld,\n\tentityId: number,\n): boolean {\n\tconst anim = ecs.getComponent(entityId, 'spriteAnimation');\n\tif (!anim) return false;\n\n\tanim.playing = true;\n\treturn true;\n}\n\n// ==================== Animation Processing Helpers ====================\n\nfunction completeAnimation(\n\tanim: SpriteAnimation,\n\tentityId: number,\n\tecs: SpriteAnimationWorld,\n): void {\n\tanim.playing = false;\n\tanim.justFinished = true;\n\n\tanim.onComplete?.({ entityId, animation: anim.current });\n\n\tecs.commands.removeComponent(entityId, 'spriteAnimation');\n}\n\nfunction handleBoundary(\n\tanim: SpriteAnimation,\n\tclip: SpriteAnimationClip,\n\tentityId: number,\n\tecs: SpriteAnimationWorld,\n): boolean {\n\tanim.completedLoops++;\n\n\tif (clip.loop === 'once') {\n\t\tcompleteAnimation(anim, entityId, ecs);\n\t\treturn false;\n\t}\n\n\t// Check finite loop count\n\tif (anim.totalLoops > 0 && anim.completedLoops >= anim.totalLoops) {\n\t\tcompleteAnimation(anim, entityId, ecs);\n\t\treturn false;\n\t}\n\n\tif (clip.loop === 'pingPong') {\n\t\tanim.direction = anim.direction === 1 ? -1 : 1;\n\t\t// Step one frame in the new direction from the boundary\n\t\tanim.currentFrame += anim.direction;\n\t\treturn anim.elapsed > 0;\n\t}\n\n\t// loop mode: wrap to frame 0\n\tanim.currentFrame = 0;\n\treturn anim.elapsed > 0;\n}\n\n/**\n * Advance to next frame. Returns true if processing should continue (more overflow),\n * false if animation completed or reached a boundary.\n */\nfunction advanceFrame(\n\tanim: SpriteAnimation,\n\tclip: SpriteAnimationClip,\n\tentityId: number,\n\tecs: SpriteAnimationWorld,\n): boolean {\n\tconst nextFrame = anim.currentFrame + anim.direction;\n\n\t// Check boundary\n\tif (nextFrame >= clip.frames.length || nextFrame < 0) {\n\t\treturn handleBoundary(anim, clip, entityId, ecs);\n\t}\n\n\tanim.currentFrame = nextFrame;\n\treturn true;\n}\n\nfunction processFrameAdvancement(\n\tanim: SpriteAnimation,\n\tclip: SpriteAnimationClip,\n\tentityId: number,\n\tecs: SpriteAnimationWorld,\n): void {\n\t// Process frame overflow\n\t// eslint-disable-next-line no-constant-condition\n\twhile (true) {\n\t\tconst frameDuration = clip.frameDurations !== null\n\t\t\t? (clip.frameDurations[anim.currentFrame] ?? clip.frameDuration)\n\t\t\t: clip.frameDuration;\n\n\t\tif (frameDuration <= 0) {\n\t\t\t// Zero-duration frame: advance immediately\n\t\t\tif (!advanceFrame(anim, clip, entityId, ecs)) return;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Floating-point-safe comparison: treat elapsed within 1μs of\n\t\t// frameDuration as having reached the boundary.\n\t\tconst remaining = frameDuration - anim.elapsed;\n\t\tif (remaining > 1e-6) return;\n\n\t\t// Frame complete — carry overflow (clamp negative remainders to 0)\n\t\tanim.elapsed = remaining < 0 ? -remaining : 0;\n\n\t\tif (!advanceFrame(anim, clip, entityId, ecs)) return;\n\t}\n}\n\n// ==================== Plugin Factory ====================\n\n/**\n * Create a sprite animation plugin for ECSpresso.\n *\n * Provides:\n * - Frame-based animation system processing spriteAnimation components\n * - Loop modes: once, loop, pingPong\n * - justFinished one-frame flag for completion detection\n * - onComplete event publishing\n * - Sprite texture sync via structural cross-plugin access\n * - Change detection via markChanged\n */\nexport function createSpriteAnimationPlugin<\n\tG extends string = 'spriteAnimation',\n>(\n\toptions?: SpriteAnimationPluginOptions<G>,\n) {\n\tconst {\n\t\tsystemGroup = 'spriteAnimation',\n\t\tpriority = 0,\n\t\tphase = 'update',\n\t} = options ?? {};\n\n\treturn definePlugin('spriteAnimation')\n\t\t.withComponentTypes<SpriteAnimationComponentTypes>()\n\t\t.withLabels<'sprite-animation-update'>()\n\t\t.withGroups<G>()\n\t\t.install((world) => {\n\t\t\tworld\n\t\t\t\t.addSystem('sprite-animation-update')\n\t\t\t\t.setPriority(priority)\n\t\t\t\t.inPhase(phase)\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.addQuery('animations', {\n\t\t\t\t\twith: ['spriteAnimation'],\n\t\t\t\t})\n\t\t\t\t.setProcess(({ queries, dt, ecs }) => {\n\t\t\t\t\tfor (const entity of queries.animations) {\n\t\t\t\t\t\tconst anim = entity.components.spriteAnimation as SpriteAnimation;\n\t\t\t\t\t\tconst clip = anim.set.clips[anim.current];\n\t\t\t\t\t\tif (!clip) continue;\n\n\t\t\t\t\t\t// Clear justFinished from previous frame\n\t\t\t\t\t\tif (anim.justFinished) {\n\t\t\t\t\t\t\tanim.justFinished = false;\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Skip paused animations\n\t\t\t\t\t\tif (!anim.playing) continue;\n\n\t\t\t\t\t\t// Skip single-frame clips\n\t\t\t\t\t\tif (clip.frames.length <= 1) continue;\n\n\t\t\t\t\t\tconst previousFrame = anim.currentFrame;\n\t\t\t\t\t\tanim.elapsed += dt * anim.speed;\n\n\t\t\t\t\t\tprocessFrameAdvancement(anim, clip, entity.id, ecs);\n\n\t\t\t\t\t\t// Sync sprite texture if frame changed\n\t\t\t\t\t\tif (anim.currentFrame !== previousFrame || previousFrame === 0) {\n\t\t\t\t\t\t\tsyncSpriteTexture(entity.components as Record<string, unknown>, anim, clip);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (anim.currentFrame !== previousFrame) {\n\t\t\t\t\t\t\tecs.markChanged(entity.id, 'spriteAnimation');\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t});\n}\n\n// ==================== Internal: Sprite Texture Sync ====================\n\n/**\n * Sync the sprite's texture to the current frame. Uses structural access\n * following the tween plugin's cross-component pattern.\n */\nfunction syncSpriteTexture(\n\tentityComponents: Record<string, unknown>,\n\tanim: SpriteAnimation,\n\tclip: SpriteAnimationClip,\n): void {\n\tconst sprite = entityComponents['sprite'];\n\tif (sprite && typeof sprite === 'object' && 'texture' in sprite) {\n\t\t(sprite as { texture: unknown }).texture = clip.frames[anim.currentFrame];\n\t}\n}\n"
6
6
  ],
7
- "mappings": "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",
8
- "debugId": "9EF329142BBA6E6864756E2164756E21",
7
+ "mappings": "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",
8
+ "debugId": "25B3E2A395B1F4B164756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -0,0 +1,230 @@
1
+ /**
2
+ * Tilemap plugin for ECSpresso.
3
+ *
4
+ * Two ingestion paths share a common `LoadedTilemap` shape:
5
+ * - `registerAsset` — load a Tiled `.tmj` file via the asset manager
6
+ * - `registerRuntime` — pass a pre-built tile-id array (procedural)
7
+ *
8
+ * Query methods (`isSolid`, `isOpaque`, `isWalkable`) read from `tileMetadata`
9
+ * regardless of source.
10
+ */
11
+ import { type BasePluginOptions } from 'ecspresso';
12
+ import type { WorldConfigFrom, EmptyConfig } from '../../type-utils';
13
+ import { type NavGrid } from '../ai/pathfinding';
14
+ import type { Vector2D } from '../../utils/math';
15
+ export declare const TILE_FLIP_HORIZONTAL = 2147483648;
16
+ export declare const TILE_FLIP_VERTICAL = 1073741824;
17
+ export declare const TILE_FLIP_DIAGONAL = 536870912;
18
+ export declare const TILE_GID_MASK = 536870911;
19
+ export interface DecodedGid {
20
+ id: number;
21
+ flipH: boolean;
22
+ flipV: boolean;
23
+ flipD: boolean;
24
+ }
25
+ export declare function decodeGid(gid: number): DecodedGid;
26
+ /** The three tile flag keys the query API understands. Custom keys flow through unchanged. */
27
+ export type TileFlag = 'solid' | 'blocksSight' | 'walkable';
28
+ /** Tile metadata. Known flag keys drive query methods; arbitrary custom keys are preserved. */
29
+ export interface TileMetadata {
30
+ solid?: boolean;
31
+ blocksSight?: boolean;
32
+ walkable?: boolean;
33
+ [key: string]: unknown;
34
+ }
35
+ export interface ObjectDef {
36
+ name: string;
37
+ type: string;
38
+ x: number;
39
+ y: number;
40
+ width: number;
41
+ height: number;
42
+ rotation: number;
43
+ properties: Record<string, string | number | boolean>;
44
+ }
45
+ export interface RuntimeTileset {
46
+ textureKey: string;
47
+ columns: number;
48
+ tileWidth: number;
49
+ tileHeight: number;
50
+ firstgid?: number;
51
+ }
52
+ export interface RuntimeLayer {
53
+ name: string;
54
+ tiles: Uint32Array | Uint8Array | readonly number[];
55
+ parallax?: Vector2D;
56
+ opacity?: number;
57
+ visible?: boolean;
58
+ }
59
+ export interface TilemapRuntimeData {
60
+ width: number;
61
+ height: number;
62
+ tileSize: number;
63
+ layers: readonly RuntimeLayer[];
64
+ tilesets: readonly RuntimeTileset[];
65
+ tileMetadata?: Record<number, TileMetadata>;
66
+ objectLayers?: readonly {
67
+ name: string;
68
+ objects: readonly ObjectDef[];
69
+ }[];
70
+ }
71
+ export interface LoadedLayer {
72
+ name: string;
73
+ tiles: Uint32Array;
74
+ parallax: Vector2D;
75
+ opacity: number;
76
+ visible: boolean;
77
+ }
78
+ export interface LoadedTileset {
79
+ textureKey: string;
80
+ columns: number;
81
+ tileWidth: number;
82
+ tileHeight: number;
83
+ firstgid: number;
84
+ }
85
+ export interface LoadedObjectLayer {
86
+ name: string;
87
+ objects: readonly ObjectDef[];
88
+ }
89
+ export interface LoadedTilemap {
90
+ readonly width: number;
91
+ readonly height: number;
92
+ readonly tileWidth: number;
93
+ readonly tileHeight: number;
94
+ readonly layers: readonly LoadedLayer[];
95
+ readonly tilesets: readonly LoadedTileset[];
96
+ readonly tileMetadata: ReadonlyMap<number, TileMetadata>;
97
+ readonly objectLayers: readonly LoadedObjectLayer[];
98
+ tileToWorld(tx: number, ty: number): Vector2D;
99
+ worldToTile(wx: number, wy: number): {
100
+ tx: number;
101
+ ty: number;
102
+ };
103
+ getTile(layerIndex: number, tx: number, ty: number): number;
104
+ isSolid(tx: number, ty: number): boolean;
105
+ isOpaque(tx: number, ty: number): boolean;
106
+ isWalkable(tx: number, ty: number): boolean;
107
+ buildNavGrid(layerIndex: number, costFn?: (tileId: number) => number): NavGrid;
108
+ getObjectLayer(name: string): readonly ObjectDef[];
109
+ getObjects(type: string): readonly ObjectDef[];
110
+ }
111
+ /** Subset of a Tiled `.tmj` (JSON) document we consume in v1. */
112
+ export interface TiledMap {
113
+ width: number;
114
+ height: number;
115
+ tilewidth: number;
116
+ tileheight: number;
117
+ tilesets: readonly TiledTileset[];
118
+ layers: readonly TiledLayer[];
119
+ }
120
+ export interface TiledTileset {
121
+ firstgid: number;
122
+ columns: number;
123
+ tilewidth: number;
124
+ tileheight: number;
125
+ image: string;
126
+ imagewidth: number;
127
+ imageheight: number;
128
+ tiles?: readonly TiledTileDef[];
129
+ }
130
+ export interface TiledTileDef {
131
+ id: number;
132
+ properties?: readonly TiledProperty[];
133
+ animation?: readonly {
134
+ tileid: number;
135
+ duration: number;
136
+ }[];
137
+ }
138
+ export interface TiledProperty {
139
+ name: string;
140
+ type: 'bool' | 'int' | 'float' | 'string' | 'color' | 'file' | 'object';
141
+ value: string | number | boolean;
142
+ }
143
+ export type TiledLayer = TiledTileLayer | TiledObjectLayer;
144
+ export interface TiledTileLayer {
145
+ type: 'tilelayer';
146
+ name: string;
147
+ width: number;
148
+ height: number;
149
+ data: readonly number[];
150
+ opacity: number;
151
+ visible: boolean;
152
+ parallaxx?: number;
153
+ parallaxy?: number;
154
+ }
155
+ export interface TiledObjectLayer {
156
+ type: 'objectgroup';
157
+ name: string;
158
+ objects: readonly TiledObject[];
159
+ }
160
+ export interface TiledObject {
161
+ id: number;
162
+ name?: string;
163
+ type?: string;
164
+ x: number;
165
+ y: number;
166
+ width?: number;
167
+ height?: number;
168
+ rotation?: number;
169
+ properties?: readonly TiledProperty[];
170
+ }
171
+ export interface ParseTiledOptions {
172
+ tilesetTextures: Record<string, string>;
173
+ }
174
+ export type TilemapCullingMode = 'viewport' | 'none';
175
+ export interface TilemapLayerComponent {
176
+ dataKey: string;
177
+ tilesetKey?: string;
178
+ layerIndex: number;
179
+ opacity: number;
180
+ parallax: Vector2D;
181
+ cullingMode: TilemapCullingMode;
182
+ cameraRef?: number;
183
+ tintFn?: (tx: number, ty: number) => number | null;
184
+ }
185
+ export interface TilemapColliderTag {
186
+ dataKey: string;
187
+ }
188
+ export interface TilemapComponentTypes {
189
+ tilemap: TilemapLayerComponent;
190
+ tilemapCollider: TilemapColliderTag;
191
+ }
192
+ export interface TilemapRegistry {
193
+ registerRuntime(dataKey: string, data: TilemapRuntimeData): LoadedTilemap;
194
+ registerAsset(dataKey: string, assetKey: string, options?: ParseTiledOptions): Promise<LoadedTilemap>;
195
+ get(dataKey: string): LoadedTilemap | undefined;
196
+ has(dataKey: string): boolean;
197
+ readonly entries: ReadonlyMap<string, LoadedTilemap>;
198
+ }
199
+ export interface TilemapResourceTypes {
200
+ tilemaps: TilemapRegistry;
201
+ }
202
+ export type TilemapWorldConfig = WorldConfigFrom<TilemapComponentTypes, EmptyConfig['events'], TilemapResourceTypes>;
203
+ export interface TilemapPluginOptions<G extends string = 'rendering'> extends BasePluginOptions<G> {
204
+ /** Optional collision layer name. When set, solid tiles auto-spawn `aabbCollider` strips. */
205
+ collisionLayer?: string;
206
+ /** Layers the auto-generated tile bodies collide with. */
207
+ collidesWith?: readonly string[];
208
+ }
209
+ export declare function createLoadedTilemap(data: TilemapRuntimeData): LoadedTilemap;
210
+ export declare function parseTiledJSON(map: TiledMap, options: ParseTiledOptions): LoadedTilemap;
211
+ export declare function createTilemapPlugin<G extends string = 'rendering'>(options?: TilemapPluginOptions<G>): import("ecspresso").Plugin<import("ecspresso").WithResources<import("ecspresso").WithComponents<EmptyConfig, TilemapComponentTypes>, TilemapResourceTypes>, EmptyConfig, never, G, never, never>;
212
+ /**
213
+ * Create a `tilemap` layer component for spreading into `spawn()`.
214
+ *
215
+ * @example
216
+ * ```typescript
217
+ * ecs.spawn({
218
+ * ...createTilemapLayer('dungeon', 0),
219
+ * ...createLocalTransform(0, 0),
220
+ * });
221
+ * ```
222
+ */
223
+ export declare function createTilemapLayer(dataKey: string, layerIndex: number, options?: {
224
+ tilesetKey?: string;
225
+ opacity?: number;
226
+ parallax?: Vector2D;
227
+ cullingMode?: TilemapCullingMode;
228
+ cameraRef?: number;
229
+ tintFn?: (tx: number, ty: number) => number | null;
230
+ }): Pick<TilemapComponentTypes, 'tilemap'>;
@@ -0,0 +1,4 @@
1
+ var f=Object.defineProperty;var T=(Q)=>Q;function I(Q,V){this[Q]=T.bind(null,V)}var s=(Q,V)=>{for(var J in V)f(Q,J,{get:V[J],enumerable:!0,configurable:!0,set:I.bind(V,J)})};var r=(Q,V)=>()=>(Q&&(V=Q(Q=0)),V);var d=((Q)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(Q,{get:(V,J)=>(typeof require<"u"?require:V)[J]}):Q)(function(Q){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+Q+'" is not supported')});import{definePlugin as S}from"ecspresso";var K={neighbors(Q,V,J){let Z=V%Q.width,Y=(V-Z)/Q.width,X=0;if(Z>0)J[X++]=V-1;if(Z<Q.width-1)J[X++]=V+1;if(Y>0)J[X++]=V-Q.width;if(Y<Q.height-1)J[X++]=V+Q.width;return X},stepCost(Q,V,J){return Q.cells[J]??0},heuristic(Q,V,J){let Z=V%Q.width,Y=(V-Z)/Q.width,X=J%Q.width,q=(J-X)/Q.width;return Math.abs(Z-X)+Math.abs(Y-q)}},M=(Q)=>{let V=()=>{throw Error(`pathfinding: topology '${Q}' is not implemented in v1`)};return{neighbors:V,stepCost:V,heuristic:V}},L=Object.freeze({square4:K,square8:M("square8"),"hex-pointy":M("hex-pointy"),"hex-flat":M("hex-flat")});function C(Q){let V=Q.topology??"square4",J=Q.cellSize??32,Z=Q.originX??0,Y=Q.originY??0,{width:X,height:q}=Q,B=Q.defaultCost??1;if(!Number.isInteger(X)||X<=0)throw Error(`pathfinding: width must be a positive integer, got ${X}`);if(!Number.isInteger(q)||q<=0)throw Error(`pathfinding: height must be a positive integer, got ${q}`);if(J<=0)throw Error(`pathfinding: cellSize must be > 0, got ${J}`);if(B<0||B>255)throw Error(`pathfinding: defaultCost must be in 0–255, got ${B}`);let W=X*q,D=Q.cells??new Uint8Array(W).fill(B);if(D.length!==W)throw Error(`pathfinding: cells length ${D.length} does not match width*height ${W}`);let H=1/J;return{topology:V,width:X,height:q,cellSize:J,originX:Z,originY:Y,cells:D,worldToCell:(j,k)=>{let z=Math.floor((j-Z)*H),R=Math.floor((k-Y)*H),O=z<0?0:z>=X?X-1:z;return(R<0?0:R>=q?q-1:R)*X+O},cellToWorld:(j)=>{let k=j%X,z=(j-k)/X;return{x:Z+(k+0.5)*J,y:Y+(z+0.5)*J}},cellFromXY:(j,k)=>k*X+j,cellToXY:(j)=>{let k=j%X;return{x:k,y:(j-k)/X}}}}function i(Q){return{pathRequest:{target:{x:Q.x,y:Q.y}}}}function v(Q,V,J){let Z=Q.size;Q.size=Z+1;while(Z>0){let Y=Z-1>>1;if((Q.priorities[Y]??0)<=J)break;Q.ids[Z]=Q.ids[Y]??0,Q.priorities[Z]=Q.priorities[Y]??0,Z=Y}Q.ids[Z]=V,Q.priorities[Z]=J}function w(Q){let V=Q.ids[0]??-1,J=Q.size-1;if(Q.size=J,J<=0)return V;let Z=Q.ids[J]??0,Y=Q.priorities[J]??0,X=0,q=J>>1;while(X<q){let B=(X<<1)+1,W=B+1;if(W<J&&(Q.priorities[W]??0)<(Q.priorities[B]??0))B=W;if((Q.priorities[B]??0)>=Y)break;Q.ids[X]=Q.ids[B]??0,Q.priorities[X]=Q.priorities[B]??0,X=B}return Q.ids[X]=Z,Q.priorities[X]=Y,V}function m(Q,V){let J=1,Z=V;while((Q[Z]??-1)!==-1)J++,Z=Q[Z]??-1;let Y=Array(J);Z=V;for(let X=J-1;X>=0;X--)if(Y[X]=Z,X>0)Z=Q[Z]??-1;return Y}function u(Q,V,J,Z){let Y=Q.cells.length;if(V<0||V>=Y)return null;if(J<0||J>=Y)return null;let X=Z?.maxNodesExpanded??1e4,q=Z?.blockedCells,B=Z?.goalTolerance??0,W=L[Q.topology],D=new Float32Array(Y);D.fill(Number.POSITIVE_INFINITY);let H=new Int32Array(Y);H.fill(-1);let U=new Uint8Array(Y),$={ids:new Int32Array(Y),priorities:new Float32Array(Y),size:0},N=[];D[V]=0,v($,V,W.heuristic(Q,V,J));let G=0;while($.size>0){if(G>=X)return null;let j=w($);if(U[j])continue;if(U[j]=1,G++,W.heuristic(Q,j,J)<=B)return m(H,j);N.length=0;let k=W.neighbors(Q,j,N);for(let z=0;z<k;z++){let R=N[z]??-1;if(R<0||U[R])continue;if((Q.cells[R]??0)===0)continue;if(q&&q.has(R))continue;let E=(D[j]??Number.POSITIVE_INFINITY)+W.stepCost(Q,j,R);if(E<(D[R]??Number.POSITIVE_INFINITY))D[R]=E,H[R]=j,v($,R,E+W.heuristic(Q,R,J))}}return null}function e(Q){let{grid:V,systemGroup:J="ai",priority:Z=150,phase:Y="update",maxRequestsPerFrame:X=4,maxNodesExpanded:q=1e4}=Q;return S("pathfinding").withComponentTypes().withEventTypes().withResourceTypes().withLabels().withGroups().requires().install((B)=>{B.addResource("navGrid",V),B.addSystem("pathfinding-request").setPriority(Z).inPhase(Y).inGroup(J).addQuery("requests",{with:["pathRequest","worldTransform"]}).setProcess(({queries:W,ecs:D})=>{let H=D.getResource("navGrid"),U=0;for(let $ of W.requests){if(U>=X)break;U++;let{pathRequest:N,worldTransform:G}=$.components,j=H.worldToCell(G.x,G.y),k=H.worldToCell(N.target.x,N.target.y),z=u(H,j,k,{maxNodesExpanded:q});if(D.commands.removeComponent($.id,"pathRequest"),z===null){D.eventBus.publish("pathBlocked",{entityId:$.id});continue}let R=z.slice(1).map((b)=>H.cellToWorld(b));if(D.eventBus.publish("pathFound",{entityId:$.id,path:R}),R.length===0)continue;let O=D.getComponent($.id,"path");if(O)O.waypoints=R,O.currentIndex=0,D.markChanged($.id,"path");else D.addComponent($.id,"path",{waypoints:R,currentIndex:0});let E=R[0];if(!E)continue;let A=D.getComponent($.id,"moveTarget");if(A)A.x=E.x,A.y=E.y,D.markChanged($.id,"moveTarget");else D.addComponent($.id,"moveTarget",{x:E.x,y:E.y})}}),B.addSystem("pathfinding-waypoint-advance").inGroup(J).setEventHandlers({arriveAtTarget({data:W,ecs:D}){let H=D.getComponent(W.entityId,"path");if(!H)return;let U=H.currentIndex+1;if(U>=H.waypoints.length){D.commands.removeComponent(W.entityId,"path");return}H.currentIndex=U,D.markChanged(W.entityId,"path");let $=H.waypoints[U];if(!$)return;D.commands.addComponent(W.entityId,"moveTarget",{x:$.x,y:$.y})}})})}import{definePlugin as x}from"ecspresso";var g=2147483648,y=1073741824,c=536870912,_=536870911;function $Q(Q){return{id:(Q&_)>>>0,flipH:(Q&g)!==0,flipV:(Q&y)!==0,flipD:(Q&c)!==0}}function h(Q){return Uint32Array.from(Q)}function F(Q){if(!Q)return{};let V={};for(let J of Q)V[J.name]=J.value;return V}function n(Q){let{width:V,height:J,tileSize:Z,layers:Y,tilesets:X}=Q;if(!Number.isFinite(V)||V<=0)throw Error(`tilemap: width must be > 0, got ${V}`);if(!Number.isFinite(J)||J<=0)throw Error(`tilemap: height must be > 0, got ${J}`);if(!Number.isFinite(Z)||Z<=0)throw Error(`tilemap: tileSize must be > 0, got ${Z}`);if(X.length===0)throw Error("tilemap: at least one tileset is required");let q=V*J,B=Y.map((U)=>{let $=h(U.tiles);if($.length!==q)throw Error(`tilemap: layer "${U.name}" tile count ${$.length} does not match width*height ${q}`);return{name:U.name,tiles:$,parallax:U.parallax?{x:U.parallax.x,y:U.parallax.y}:{x:1,y:1},opacity:U.opacity??1,visible:U.visible??!0}}),W=X.map((U,$)=>{if(U.firstgid===void 0&&$>0)throw Error(`tilemap: runtime tileset at index ${$} ("${U.textureKey}") must specify an explicit firstgid`);return{textureKey:U.textureKey,columns:U.columns,tileWidth:U.tileWidth,tileHeight:U.tileHeight,firstgid:U.firstgid??1}}),D=new Map;if(Q.tileMetadata)for(let[U,$]of Object.entries(Q.tileMetadata))D.set(Number(U),{...$});let H=(Q.objectLayers??[]).map((U)=>({name:U.name,objects:U.objects.map(($)=>({...$,properties:{...$.properties}}))}));return P({width:V,height:J,tileWidth:Z,tileHeight:Z,layers:B,tilesets:W,tileMetadata:D,objectLayers:H})}function p(Q,V){let{width:J,height:Z,tilewidth:Y,tileheight:X}=Q,q=J*Z,B=Q.tilesets.map((U)=>{let $=V.tilesetTextures[U.image];if(!$)throw Error(`tilemap: no texture key registered for tileset image "${U.image}"`);return{textureKey:$,columns:U.columns,tileWidth:U.tilewidth,tileHeight:U.tileheight,firstgid:U.firstgid}}),W=new Map;for(let U of Q.tilesets){if(!U.tiles)continue;for(let $ of U.tiles)W.set(U.firstgid+$.id,F($.properties))}let D=[],H=[];for(let U of Q.layers)if(U.type==="tilelayer"){if(U.data.length!==q)throw Error(`tilemap: layer "${U.name}" data length ${U.data.length} does not match map ${q}`);D.push({name:U.name,tiles:Uint32Array.from(U.data),parallax:{x:U.parallaxx??1,y:U.parallaxy??1},opacity:U.opacity,visible:U.visible})}else H.push({name:U.name,objects:U.objects.map(($)=>({name:$.name??"",type:$.type??"",x:$.x,y:$.y,width:$.width??0,height:$.height??0,rotation:$.rotation??0,properties:F($.properties)}))});return P({width:J,height:Z,tileWidth:Y,tileHeight:X,layers:D,tilesets:B,tileMetadata:W,objectLayers:H})}function P(Q){let{width:V,height:J,tileWidth:Z,tileHeight:Y,layers:X,tilesets:q,tileMetadata:B,objectLayers:W}=Q,D=(U,$,N)=>{if(U<0||$<0||U>=V||$>=J)return!1;let G=$*V+U;for(let j=0;j<X.length;j++){let k=(X[j].tiles[G]??0)&_;if(k===0)continue;let z=B.get(k);if(z&&z[N]===!0)return!0}return!1},H=(U)=>{return B.get(U)?.walkable===!0?1:0};return{width:V,height:J,tileWidth:Z,tileHeight:Y,layers:X,tilesets:q,tileMetadata:B,objectLayers:W,tileToWorld(U,$){return{x:(U+0.5)*Z,y:($+0.5)*Y}},worldToTile(U,$){return{tx:Math.floor(U/Z),ty:Math.floor($/Y)}},getTile(U,$,N){let G=X[U];if(!G)return 0;if($<0||N<0||$>=V||N>=J)return 0;return(G.tiles[N*V+$]??0)&_},isSolid:(U,$)=>D(U,$,"solid"),isOpaque:(U,$)=>D(U,$,"blocksSight"),isWalkable:(U,$)=>D(U,$,"walkable"),buildNavGrid(U,$){let N=X[U];if(!N)throw Error(`tilemap: buildNavGrid — no layer at index ${U}`);let G=new Uint8Array(V*J),j=$??H;for(let k=0;k<G.length;k++){let z=(N.tiles[k]??0)&_,R=j(z)|0;G[k]=R<0?0:R>255?255:R}return C({width:V,height:J,cellSize:Z,cells:G})},getObjectLayer(U){return W.find(($)=>$.name===U)?.objects??[]},getObjects(U){let $=[];for(let N of W)for(let G of N.objects)if(G.type===U)$.push(G);return $}}}function l(Q){let V=[];for(let J=0;J<Q.height;J++){let Z=-1;for(let Y=0;Y<=Q.width;Y++){let X=Y<Q.width&&Q.isSolid(Y,J);if(X&&Z===-1)Z=Y;else if(!X&&Z!==-1)V.push({tx:Z,ty:J,tw:Y-Z,th:1}),Z=-1}}return V}function JQ(Q={}){let{collisionLayer:V,collidesWith:J}=Q;return x("tilemap").withComponentTypes().withResourceTypes().withLabels().withGroups().install((Z)=>{let Y=new Map,X=new Map,q=(H)=>{let U=X.get(H);if(!U)return;for(let $ of U)Z.removeEntity($);X.delete(H)},B=(H,U)=>{if(!V)return;let $=[];for(let N of l(U)){let G=(N.tx+N.tw/2)*U.tileWidth,j=(N.ty+N.th/2)*U.tileHeight,k={tilemapCollider:{dataKey:H},aabbCollider:{width:N.tw*U.tileWidth,height:N.th*U.tileHeight},collisionLayer:{layer:V,collidesWith:J??[]},localTransform:{x:G,y:j,rotation:0,scaleX:1,scaleY:1},worldTransform:{x:G,y:j,rotation:0,scaleX:1,scaleY:1}},z=Z.spawn(k);$.push(z.id)}if($.length>0)X.set(H,$)},W=(H,U)=>{return q(H),Y.set(H,U),B(H,U),U},D={entries:Y,registerRuntime(H,U){return W(H,n(U))},async registerAsset(H,U,$){let N=await Z.loadAsset(U);return W(H,p(N,$??{tilesetTextures:{}}))},get:(H)=>Y.get(H),has:(H)=>Y.has(H)};Z.addResource("tilemaps",D)})}function VQ(Q,V,J){return{tilemap:{dataKey:Q,layerIndex:V,tilesetKey:J?.tilesetKey,opacity:J?.opacity??1,parallax:J?.parallax??{x:1,y:1},cullingMode:J?.cullingMode??"viewport",cameraRef:J?.cameraRef,tintFn:J?.tintFn}}}export{p as parseTiledJSON,$Q as decodeGid,JQ as createTilemapPlugin,VQ as createTilemapLayer,n as createLoadedTilemap,_ as TILE_GID_MASK,y as TILE_FLIP_VERTICAL,g as TILE_FLIP_HORIZONTAL,c as TILE_FLIP_DIAGONAL};
2
+
3
+ //# debugId=1B20DC0974BC7FAF64756E2164756E21
4
+ //# sourceMappingURL=tilemap.js.map
@@ -0,0 +1,11 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/plugins/ai/pathfinding.ts", "../src/plugins/rendering/tilemap.ts"],
4
+ "sourcesContent": [
5
+ "/**\n * Pathfinding Plugin for ECSpresso\n *\n * A* pathfinding on a weighted grid. Produces waypoint lists consumed by the\n * steering plugin — the pathfinding system writes the `path` component and\n * sets `moveTarget` to the first waypoint; the waypoint advancement handler\n * listens for `arriveAtTarget` and advances to the next waypoint.\n *\n * Exports the pure `findPath(grid, start, goal, options?)` function for\n * turn-based / non-realtime consumers that don't need the component dance.\n */\n\nimport { definePlugin, type BasePluginOptions } from 'ecspresso';\nimport type { WorldConfigFrom } from 'ecspresso';\nimport type { Vector2D } from '../../utils/math';\nimport type { TransformWorldConfig } from '../spatial/transform';\nimport type { SteeringWorldConfig } from '../physics/steering';\n\n// ==================== Topology / Grid Types ====================\n\n/** Flat-indexed cell position in a `NavGrid`. Transparent alias, not branded. */\nexport type CellIndex = number;\n\n/**\n * Grid topology. v1 ships `square4`; other values are accepted at construction\n * but throw at `findPath` time.\n */\nexport type NavGridTopology = 'square4' | 'square8' | 'hex-pointy' | 'hex-flat';\n\n/**\n * Weighted navigation grid. Row-major storage (`idx = row * width + col`).\n * Cell value `0` = impassable, `1`–`255` = traversal cost into that cell.\n */\nexport interface NavGrid {\n\treadonly topology: NavGridTopology;\n\treadonly width: number;\n\treadonly height: number;\n\treadonly cellSize: number;\n\treadonly originX: number;\n\treadonly originY: number;\n\treadonly cells: Uint8Array;\n\tworldToCell(wx: number, wy: number): CellIndex;\n\tcellToWorld(idx: CellIndex): Vector2D;\n\tcellFromXY(x: number, y: number): CellIndex;\n\tcellToXY(idx: CellIndex): { x: number; y: number };\n}\n\n/** Options accepted by `createNavGrid`. */\nexport interface CreateNavGridOptions {\n\ttopology?: NavGridTopology;\n\twidth: number;\n\theight: number;\n\tcellSize?: number;\n\toriginX?: number;\n\toriginY?: number;\n\tcells?: Uint8Array;\n\tdefaultCost?: number;\n}\n\n// ==================== Component Types ====================\n\n/** Signals the pathfinding system to compute a route to `target`. */\nexport interface PathRequest {\n\ttarget: Vector2D;\n}\n\n/** Active route; waypoints are in world-space, advanced by `currentIndex`. */\nexport interface Path {\n\twaypoints: Vector2D[];\n\tcurrentIndex: number;\n}\n\n/** Component types provided by the pathfinding plugin. */\nexport interface PathfindingComponentTypes {\n\tpathRequest: PathRequest;\n\tpath: Path;\n}\n\n// ==================== Event Types ====================\n\n/** Fired when A* produces a route. `path` is empty when start is already at the goal. */\nexport interface PathFoundEvent {\n\tentityId: number;\n\tpath: Vector2D[];\n}\n\n/** Fired when no path exists to the target. */\nexport interface PathBlockedEvent {\n\tentityId: number;\n}\n\n/** Event types provided by the pathfinding plugin. */\nexport interface PathfindingEventTypes {\n\tpathFound: PathFoundEvent;\n\tpathBlocked: PathBlockedEvent;\n}\n\n// ==================== Resource Types ====================\n\n/** Resource types provided by the pathfinding plugin. */\nexport interface PathfindingResourceTypes {\n\tnavGrid: NavGrid;\n}\n\n// ==================== WorldConfig ====================\n\n/** WorldConfig representing the pathfinding plugin's provided types. */\nexport type PathfindingWorldConfig = WorldConfigFrom<\n\tPathfindingComponentTypes,\n\tPathfindingEventTypes,\n\tPathfindingResourceTypes\n>;\n\n// ==================== Plugin Options ====================\n\nexport interface PathfindingPluginOptions<G extends string = 'ai'> extends BasePluginOptions<G> {\n\t/** The navigation grid. Construct via `createNavGrid`. */\n\tgrid: NavGrid;\n\t/** Max path requests processed per frame (default 4). */\n\tmaxRequestsPerFrame?: number;\n\t/** Default `maxNodesExpanded` passed to A* per request (default 10_000). */\n\tmaxNodesExpanded?: number;\n}\n\n// ==================== NavGrid Construction ====================\n\ninterface TopologyOps {\n\tneighbors(grid: NavGrid, idx: CellIndex, out: number[]): number;\n\tstepCost(grid: NavGrid, from: CellIndex, to: CellIndex): number;\n\theuristic(grid: NavGrid, a: CellIndex, b: CellIndex): number;\n}\n\nconst square4Ops: TopologyOps = {\n\tneighbors(grid, idx, out) {\n\t\tconst col = idx % grid.width;\n\t\tconst row = (idx - col) / grid.width;\n\t\tlet count = 0;\n\t\tif (col > 0) out[count++] = idx - 1;\n\t\tif (col < grid.width - 1) out[count++] = idx + 1;\n\t\tif (row > 0) out[count++] = idx - grid.width;\n\t\tif (row < grid.height - 1) out[count++] = idx + grid.width;\n\t\treturn count;\n\t},\n\tstepCost(grid, _from, to) {\n\t\treturn grid.cells[to] ?? 0;\n\t},\n\theuristic(grid, a, b) {\n\t\tconst ax = a % grid.width;\n\t\tconst ay = (a - ax) / grid.width;\n\t\tconst bx = b % grid.width;\n\t\tconst by = (b - bx) / grid.width;\n\t\treturn Math.abs(ax - bx) + Math.abs(ay - by);\n\t},\n};\n\nconst unimplementedOps = (topology: NavGridTopology): TopologyOps => {\n\tconst err = (): never => {\n\t\tthrow new Error(`pathfinding: topology '${topology}' is not implemented in v1`);\n\t};\n\treturn {\n\t\tneighbors: err,\n\t\tstepCost: err,\n\t\theuristic: err,\n\t};\n};\n\nconst topologyOps: Readonly<Record<NavGridTopology, TopologyOps>> = Object.freeze({\n\t'square4': square4Ops,\n\t'square8': unimplementedOps('square8'),\n\t'hex-pointy': unimplementedOps('hex-pointy'),\n\t'hex-flat': unimplementedOps('hex-flat'),\n});\n\n/**\n * Create a weighted navigation grid.\n *\n * @example\n * ```typescript\n * const grid = createNavGrid({ width: 32, height: 32, cellSize: 16 });\n * grid.cells[grid.cellFromXY(5, 5)] = 0; // block a cell\n * ```\n */\nexport function createNavGrid(options: CreateNavGridOptions): NavGrid {\n\tconst topology = options.topology ?? 'square4';\n\tconst cellSize = options.cellSize ?? 32;\n\tconst originX = options.originX ?? 0;\n\tconst originY = options.originY ?? 0;\n\tconst { width, height } = options;\n\tconst defaultCost = options.defaultCost ?? 1;\n\n\tif (!Number.isInteger(width) || width <= 0) {\n\t\tthrow new Error(`pathfinding: width must be a positive integer, got ${width}`);\n\t}\n\tif (!Number.isInteger(height) || height <= 0) {\n\t\tthrow new Error(`pathfinding: height must be a positive integer, got ${height}`);\n\t}\n\tif (cellSize <= 0) {\n\t\tthrow new Error(`pathfinding: cellSize must be > 0, got ${cellSize}`);\n\t}\n\tif (defaultCost < 0 || defaultCost > 255) {\n\t\tthrow new Error(`pathfinding: defaultCost must be in 0–255, got ${defaultCost}`);\n\t}\n\n\tconst expectedLen = width * height;\n\tconst cells = options.cells ?? new Uint8Array(expectedLen).fill(defaultCost);\n\tif (cells.length !== expectedLen) {\n\t\tthrow new Error(\n\t\t\t`pathfinding: cells length ${cells.length} does not match width*height ${expectedLen}`,\n\t\t);\n\t}\n\n\tconst invCellSize = 1 / cellSize;\n\n\tconst worldToCell = (wx: number, wy: number): CellIndex => {\n\t\tconst col = Math.floor((wx - originX) * invCellSize);\n\t\tconst row = Math.floor((wy - originY) * invCellSize);\n\t\tconst cCol = col < 0 ? 0 : col >= width ? width - 1 : col;\n\t\tconst cRow = row < 0 ? 0 : row >= height ? height - 1 : row;\n\t\treturn cRow * width + cCol;\n\t};\n\n\tconst cellToWorld = (idx: CellIndex): Vector2D => {\n\t\tconst col = idx % width;\n\t\tconst row = (idx - col) / width;\n\t\treturn {\n\t\t\tx: originX + (col + 0.5) * cellSize,\n\t\t\ty: originY + (row + 0.5) * cellSize,\n\t\t};\n\t};\n\n\tconst cellFromXY = (x: number, y: number): CellIndex => y * width + x;\n\n\tconst cellToXY = (idx: CellIndex): { x: number; y: number } => {\n\t\tconst x = idx % width;\n\t\treturn { x, y: (idx - x) / width };\n\t};\n\n\treturn {\n\t\ttopology, width, height, cellSize, originX, originY, cells,\n\t\tworldToCell, cellToWorld, cellFromXY, cellToXY,\n\t};\n}\n\n// ==================== Helper Functions ====================\n\n/**\n * Create a `pathRequest` component for spreading into `spawn()` / `addComponent()`.\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createTransform(0, 0),\n * ...createMoveSpeed(100),\n * ...createPathRequest({ x: 200, y: 300 }),\n * });\n * ```\n */\nexport function createPathRequest(target: Vector2D): Pick<PathfindingComponentTypes, 'pathRequest'> {\n\treturn { pathRequest: { target: { x: target.x, y: target.y } } };\n}\n\n// ==================== Pure A* API ====================\n\nexport interface FindPathOptions {\n\t/** Cap on A* node expansions; returns `null` if exceeded. Default 10_000. */\n\tmaxNodesExpanded?: number;\n\t/** Dynamic per-call obstacles layered on top of the static grid. */\n\tblockedCells?: Set<CellIndex>;\n\t/** Accept arrival within N cells of goal (topology-aware distance). Default 0. */\n\tgoalTolerance?: number;\n}\n\ninterface PathHeap {\n\tids: Int32Array;\n\tpriorities: Float32Array;\n\tsize: number;\n}\n\n// Why: parallel-typed-array heap keeps cells & priorities in cache without per-node allocations.\nfunction heapPush(heap: PathHeap, id: number, priority: number): void {\n\tlet i = heap.size;\n\theap.size = i + 1;\n\twhile (i > 0) {\n\t\tconst parent = (i - 1) >> 1;\n\t\tif ((heap.priorities[parent] ?? 0) <= priority) break;\n\t\theap.ids[i] = heap.ids[parent] ?? 0;\n\t\theap.priorities[i] = heap.priorities[parent] ?? 0;\n\t\ti = parent;\n\t}\n\theap.ids[i] = id;\n\theap.priorities[i] = priority;\n}\n\nfunction heapPop(heap: PathHeap): number {\n\tconst top = heap.ids[0] ?? -1;\n\tconst last = heap.size - 1;\n\theap.size = last;\n\tif (last <= 0) return top;\n\tconst movedId = heap.ids[last] ?? 0;\n\tconst movedPri = heap.priorities[last] ?? 0;\n\tlet i = 0;\n\tconst half = last >> 1;\n\twhile (i < half) {\n\t\tlet child = (i << 1) + 1;\n\t\tconst right = child + 1;\n\t\tif (right < last && (heap.priorities[right] ?? 0) < (heap.priorities[child] ?? 0)) child = right;\n\t\tif ((heap.priorities[child] ?? 0) >= movedPri) break;\n\t\theap.ids[i] = heap.ids[child] ?? 0;\n\t\theap.priorities[i] = heap.priorities[child] ?? 0;\n\t\ti = child;\n\t}\n\theap.ids[i] = movedId;\n\theap.priorities[i] = movedPri;\n\treturn top;\n}\n\nfunction reconstructPath(cameFrom: Int32Array, end: CellIndex): CellIndex[] {\n\t// Why: two-pass (count then fill) avoids unshift/reverse allocation.\n\tlet count = 1;\n\tlet cur = end;\n\twhile ((cameFrom[cur] ?? -1) !== -1) {\n\t\tcount++;\n\t\tcur = cameFrom[cur] ?? -1;\n\t}\n\tconst path = new Array<CellIndex>(count);\n\tcur = end;\n\tfor (let i = count - 1; i >= 0; i--) {\n\t\tpath[i] = cur;\n\t\tif (i > 0) cur = cameFrom[cur] ?? -1;\n\t}\n\treturn path;\n}\n\n/**\n * Compute a path through `grid` from `start` to `goal`.\n *\n * Returns a list of cell indices starting with `start` and ending at a cell\n * within `goalTolerance` of `goal`, or `null` if no such path exists within\n * `maxNodesExpanded` expansions.\n *\n * `start` is always treated as passable (even if its grid cell is 0 or the\n * cell is in `blockedCells`) — actors physics-pushed onto a wall still get a\n * valid origin.\n */\nexport function findPath(\n\tgrid: NavGrid,\n\tstart: CellIndex,\n\tgoal: CellIndex,\n\toptions?: FindPathOptions,\n): CellIndex[] | null {\n\tconst n = grid.cells.length;\n\tif (start < 0 || start >= n) return null;\n\tif (goal < 0 || goal >= n) return null;\n\n\tconst maxNodesExpanded = options?.maxNodesExpanded ?? 10_000;\n\tconst blockedCells = options?.blockedCells;\n\tconst goalTolerance = options?.goalTolerance ?? 0;\n\tconst ops = topologyOps[grid.topology];\n\n\t// Per-call allocations: ~n bytes × 5 (gScore, cameFrom, closed, heap ids, heap priorities).\n\t// For a 100×100 grid that's ~120 KB per search. Acceptable for v1 game-grid scales.\n\t// Deferred optimization: closure-scoped reusable pool keyed by `n`, reset per call.\n\tconst gScore = new Float32Array(n);\n\tgScore.fill(Number.POSITIVE_INFINITY);\n\tconst cameFrom = new Int32Array(n);\n\tcameFrom.fill(-1);\n\tconst closed = new Uint8Array(n);\n\tconst heap: PathHeap = {\n\t\tids: new Int32Array(n),\n\t\tpriorities: new Float32Array(n),\n\t\tsize: 0,\n\t};\n\tconst neighborBuf: number[] = [];\n\n\tgScore[start] = 0;\n\theapPush(heap, start, ops.heuristic(grid, start, goal));\n\n\tlet expanded = 0;\n\twhile (heap.size > 0) {\n\t\tif (expanded >= maxNodesExpanded) return null;\n\t\tconst current = heapPop(heap);\n\t\tif (closed[current]) continue;\n\t\tclosed[current] = 1;\n\t\texpanded++;\n\n\t\tif (ops.heuristic(grid, current, goal) <= goalTolerance) {\n\t\t\treturn reconstructPath(cameFrom, current);\n\t\t}\n\n\t\tneighborBuf.length = 0;\n\t\tconst count = ops.neighbors(grid, current, neighborBuf);\n\t\tfor (let k = 0; k < count; k++) {\n\t\t\tconst next = neighborBuf[k] ?? -1;\n\t\t\tif (next < 0 || closed[next]) continue;\n\t\t\tconst cellCost = grid.cells[next] ?? 0;\n\t\t\tif (cellCost === 0) continue;\n\t\t\tif (blockedCells && blockedCells.has(next)) continue;\n\n\t\t\tconst tentative = (gScore[current] ?? Number.POSITIVE_INFINITY) + ops.stepCost(grid, current, next);\n\t\t\tif (tentative < (gScore[next] ?? Number.POSITIVE_INFINITY)) {\n\t\t\t\tgScore[next] = tentative;\n\t\t\t\tcameFrom[next] = current;\n\t\t\t\theapPush(heap, next, tentative + ops.heuristic(grid, next, goal));\n\t\t\t}\n\t\t}\n\t}\n\treturn null;\n}\n\n// ==================== Plugin Factory ====================\n\n/**\n * Create a pathfinding plugin for ECSpresso.\n *\n * Requires the transform and steering plugins to be installed (entities need\n * `worldTransform` for start-cell detection and `moveTarget`/`moveSpeed` for\n * waypoint traversal).\n *\n * @example\n * ```typescript\n * const grid = createNavGrid({ width: 32, height: 32, cellSize: 16 });\n * const ecs = ECSpresso.create()\n * .withPlugin(createTransformPlugin())\n * .withPlugin(createSteeringPlugin())\n * .withPlugin(createPathfindingPlugin({ grid }))\n * .build();\n *\n * ecs.spawn({\n * ...createTransform(0, 0),\n * ...createMoveSpeed(100),\n * ...createPathRequest({ x: 500, y: 300 }),\n * });\n * ```\n */\nexport function createPathfindingPlugin<G extends string = 'ai'>(\n\toptions: PathfindingPluginOptions<G>,\n) {\n\tconst {\n\t\tgrid,\n\t\tsystemGroup = 'ai' as G,\n\t\tpriority = 150,\n\t\tphase = 'update',\n\t\tmaxRequestsPerFrame = 4,\n\t\tmaxNodesExpanded = 10_000,\n\t} = options;\n\n\treturn definePlugin('pathfinding')\n\t\t.withComponentTypes<PathfindingComponentTypes>()\n\t\t.withEventTypes<PathfindingEventTypes>()\n\t\t.withResourceTypes<PathfindingResourceTypes>()\n\t\t.withLabels<'pathfinding-request' | 'pathfinding-waypoint-advance'>()\n\t\t.withGroups<G>()\n\t\t.requires<TransformWorldConfig & SteeringWorldConfig>()\n\t\t.install((world) => {\n\t\t\tworld.addResource('navGrid', grid);\n\n\t\t\tworld\n\t\t\t\t.addSystem('pathfinding-request')\n\t\t\t\t.setPriority(priority)\n\t\t\t\t.inPhase(phase)\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.addQuery('requests', {\n\t\t\t\t\twith: ['pathRequest', 'worldTransform'],\n\t\t\t\t})\n\t\t\t\t.setProcess(({ queries, ecs }) => {\n\t\t\t\t\tconst navGrid = ecs.getResource('navGrid');\n\t\t\t\t\tlet processed = 0;\n\t\t\t\t\tfor (const entity of queries.requests) {\n\t\t\t\t\t\tif (processed >= maxRequestsPerFrame) break;\n\t\t\t\t\t\tprocessed++;\n\t\t\t\t\t\tconst { pathRequest, worldTransform } = entity.components;\n\t\t\t\t\t\tconst startIdx = navGrid.worldToCell(worldTransform.x, worldTransform.y);\n\t\t\t\t\t\tconst goalIdx = navGrid.worldToCell(pathRequest.target.x, pathRequest.target.y);\n\t\t\t\t\t\tconst result = findPath(navGrid, startIdx, goalIdx, { maxNodesExpanded });\n\t\t\t\t\t\tecs.commands.removeComponent(entity.id, 'pathRequest');\n\n\t\t\t\t\t\tif (result === null) {\n\t\t\t\t\t\t\tecs.eventBus.publish('pathBlocked', { entityId: entity.id });\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst waypoints = result.slice(1).map(idx => navGrid.cellToWorld(idx));\n\t\t\t\t\t\tecs.eventBus.publish('pathFound', { entityId: entity.id, path: waypoints });\n\t\t\t\t\t\tif (waypoints.length === 0) continue;\n\n\t\t\t\t\t\tconst existingPath = ecs.getComponent(entity.id, 'path');\n\t\t\t\t\t\tif (existingPath) {\n\t\t\t\t\t\t\texistingPath.waypoints = waypoints;\n\t\t\t\t\t\t\texistingPath.currentIndex = 0;\n\t\t\t\t\t\t\tecs.markChanged(entity.id, 'path');\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tecs.addComponent(entity.id, 'path', { waypoints, currentIndex: 0 });\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst first = waypoints[0];\n\t\t\t\t\t\tif (!first) continue;\n\t\t\t\t\t\tconst existingMT = ecs.getComponent(entity.id, 'moveTarget');\n\t\t\t\t\t\tif (existingMT) {\n\t\t\t\t\t\t\texistingMT.x = first.x;\n\t\t\t\t\t\t\texistingMT.y = first.y;\n\t\t\t\t\t\t\tecs.markChanged(entity.id, 'moveTarget');\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tecs.addComponent(entity.id, 'moveTarget', { x: first.x, y: first.y });\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\tworld\n\t\t\t\t.addSystem('pathfinding-waypoint-advance')\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.setEventHandlers({\n\t\t\t\t\tarriveAtTarget({ data, ecs }) {\n\t\t\t\t\t\tconst path = ecs.getComponent(data.entityId, 'path');\n\t\t\t\t\t\tif (!path) return;\n\t\t\t\t\t\tconst next = path.currentIndex + 1;\n\t\t\t\t\t\tif (next >= path.waypoints.length) {\n\t\t\t\t\t\t\tecs.commands.removeComponent(data.entityId, 'path');\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpath.currentIndex = next;\n\t\t\t\t\t\tecs.markChanged(data.entityId, 'path');\n\t\t\t\t\t\tconst wp = path.waypoints[next];\n\t\t\t\t\t\tif (!wp) return;\n\t\t\t\t\t\t// Why: use command buffer so the add is queued AFTER steering's\n\t\t\t\t\t\t// queued `removeComponent('moveTarget')` in the same phase.\n\t\t\t\t\t\tecs.commands.addComponent(data.entityId, 'moveTarget', { x: wp.x, y: wp.y });\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t});\n}\n",
6
+ "/**\n * Tilemap plugin for ECSpresso.\n *\n * Two ingestion paths share a common `LoadedTilemap` shape:\n * - `registerAsset` — load a Tiled `.tmj` file via the asset manager\n * - `registerRuntime` — pass a pre-built tile-id array (procedural)\n *\n * Query methods (`isSolid`, `isOpaque`, `isWalkable`) read from `tileMetadata`\n * regardless of source.\n */\n\nimport { definePlugin, type BasePluginOptions } from 'ecspresso';\nimport type ECSpresso from 'ecspresso';\nimport type { WorldConfigFrom, EmptyConfig } from '../../type-utils';\nimport { createNavGrid, type NavGrid } from '../ai/pathfinding';\nimport type { Vector2D } from '../../utils/math';\nimport type { LocalTransform, WorldTransform } from '../spatial/transform';\nimport type { AABBCollider, CollisionLayer } from '../physics/collision';\n\nexport const TILE_FLIP_HORIZONTAL = 0x80000000;\nexport const TILE_FLIP_VERTICAL = 0x40000000;\nexport const TILE_FLIP_DIAGONAL = 0x20000000;\nexport const TILE_GID_MASK = 0x1fffffff;\n\nexport interface DecodedGid {\n\tid: number;\n\tflipH: boolean;\n\tflipV: boolean;\n\tflipD: boolean;\n}\n\nexport function decodeGid(gid: number): DecodedGid {\n\treturn {\n\t\tid: (gid & TILE_GID_MASK) >>> 0,\n\t\tflipH: (gid & TILE_FLIP_HORIZONTAL) !== 0,\n\t\tflipV: (gid & TILE_FLIP_VERTICAL) !== 0,\n\t\tflipD: (gid & TILE_FLIP_DIAGONAL) !== 0,\n\t};\n}\n\n/** The three tile flag keys the query API understands. Custom keys flow through unchanged. */\nexport type TileFlag = 'solid' | 'blocksSight' | 'walkable';\n\n/** Tile metadata. Known flag keys drive query methods; arbitrary custom keys are preserved. */\nexport interface TileMetadata {\n\tsolid?: boolean;\n\tblocksSight?: boolean;\n\twalkable?: boolean;\n\t[key: string]: unknown;\n}\n\nexport interface ObjectDef {\n\tname: string;\n\ttype: string;\n\tx: number;\n\ty: number;\n\twidth: number;\n\theight: number;\n\trotation: number;\n\tproperties: Record<string, string | number | boolean>;\n}\n\nexport interface RuntimeTileset {\n\ttextureKey: string;\n\tcolumns: number;\n\ttileWidth: number;\n\ttileHeight: number;\n\tfirstgid?: number;\n}\n\nexport interface RuntimeLayer {\n\tname: string;\n\ttiles: Uint32Array | Uint8Array | readonly number[];\n\tparallax?: Vector2D;\n\topacity?: number;\n\tvisible?: boolean;\n}\n\nexport interface TilemapRuntimeData {\n\twidth: number;\n\theight: number;\n\ttileSize: number;\n\tlayers: readonly RuntimeLayer[];\n\ttilesets: readonly RuntimeTileset[];\n\ttileMetadata?: Record<number, TileMetadata>;\n\tobjectLayers?: readonly { name: string; objects: readonly ObjectDef[] }[];\n}\n\nexport interface LoadedLayer {\n\tname: string;\n\ttiles: Uint32Array;\n\tparallax: Vector2D;\n\topacity: number;\n\tvisible: boolean;\n}\n\nexport interface LoadedTileset {\n\ttextureKey: string;\n\tcolumns: number;\n\ttileWidth: number;\n\ttileHeight: number;\n\tfirstgid: number;\n}\n\nexport interface LoadedObjectLayer {\n\tname: string;\n\tobjects: readonly ObjectDef[];\n}\n\nexport interface LoadedTilemap {\n\treadonly width: number;\n\treadonly height: number;\n\treadonly tileWidth: number;\n\treadonly tileHeight: number;\n\treadonly layers: readonly LoadedLayer[];\n\treadonly tilesets: readonly LoadedTileset[];\n\treadonly tileMetadata: ReadonlyMap<number, TileMetadata>;\n\treadonly objectLayers: readonly LoadedObjectLayer[];\n\n\ttileToWorld(tx: number, ty: number): Vector2D;\n\tworldToTile(wx: number, wy: number): { tx: number; ty: number };\n\tgetTile(layerIndex: number, tx: number, ty: number): number;\n\tisSolid(tx: number, ty: number): boolean;\n\tisOpaque(tx: number, ty: number): boolean;\n\tisWalkable(tx: number, ty: number): boolean;\n\tbuildNavGrid(layerIndex: number, costFn?: (tileId: number) => number): NavGrid;\n\tgetObjectLayer(name: string): readonly ObjectDef[];\n\tgetObjects(type: string): readonly ObjectDef[];\n}\n\n/** Subset of a Tiled `.tmj` (JSON) document we consume in v1. */\nexport interface TiledMap {\n\twidth: number;\n\theight: number;\n\ttilewidth: number;\n\ttileheight: number;\n\ttilesets: readonly TiledTileset[];\n\tlayers: readonly TiledLayer[];\n}\n\nexport interface TiledTileset {\n\tfirstgid: number;\n\tcolumns: number;\n\ttilewidth: number;\n\ttileheight: number;\n\timage: string;\n\timagewidth: number;\n\timageheight: number;\n\ttiles?: readonly TiledTileDef[];\n}\n\nexport interface TiledTileDef {\n\tid: number;\n\tproperties?: readonly TiledProperty[];\n\tanimation?: readonly { tileid: number; duration: number }[];\n}\n\nexport interface TiledProperty {\n\tname: string;\n\ttype: 'bool' | 'int' | 'float' | 'string' | 'color' | 'file' | 'object';\n\tvalue: string | number | boolean;\n}\n\nexport type TiledLayer = TiledTileLayer | TiledObjectLayer;\n\nexport interface TiledTileLayer {\n\ttype: 'tilelayer';\n\tname: string;\n\twidth: number;\n\theight: number;\n\tdata: readonly number[];\n\topacity: number;\n\tvisible: boolean;\n\tparallaxx?: number;\n\tparallaxy?: number;\n}\n\nexport interface TiledObjectLayer {\n\ttype: 'objectgroup';\n\tname: string;\n\tobjects: readonly TiledObject[];\n}\n\nexport interface TiledObject {\n\tid: number;\n\tname?: string;\n\ttype?: string;\n\tx: number;\n\ty: number;\n\twidth?: number;\n\theight?: number;\n\trotation?: number;\n\tproperties?: readonly TiledProperty[];\n}\n\nexport interface ParseTiledOptions {\n\ttilesetTextures: Record<string, string>;\n}\n\nexport type TilemapCullingMode = 'viewport' | 'none';\n\nexport interface TilemapLayerComponent {\n\tdataKey: string;\n\ttilesetKey?: string;\n\tlayerIndex: number;\n\topacity: number;\n\tparallax: Vector2D;\n\tcullingMode: TilemapCullingMode;\n\tcameraRef?: number;\n\ttintFn?: (tx: number, ty: number) => number | null;\n}\n\nexport interface TilemapColliderTag {\n\tdataKey: string;\n}\n\nexport interface TilemapComponentTypes {\n\ttilemap: TilemapLayerComponent;\n\ttilemapCollider: TilemapColliderTag;\n}\n\nexport interface TilemapRegistry {\n\tregisterRuntime(dataKey: string, data: TilemapRuntimeData): LoadedTilemap;\n\tregisterAsset(dataKey: string, assetKey: string, options?: ParseTiledOptions): Promise<LoadedTilemap>;\n\tget(dataKey: string): LoadedTilemap | undefined;\n\thas(dataKey: string): boolean;\n\treadonly entries: ReadonlyMap<string, LoadedTilemap>;\n}\n\nexport interface TilemapResourceTypes {\n\ttilemaps: TilemapRegistry;\n}\n\nexport type TilemapWorldConfig = WorldConfigFrom<TilemapComponentTypes, EmptyConfig['events'], TilemapResourceTypes>;\n\nexport interface TilemapPluginOptions<G extends string = 'rendering'> extends BasePluginOptions<G> {\n\t/** Optional collision layer name. When set, solid tiles auto-spawn `aabbCollider` strips. */\n\tcollisionLayer?: string;\n\t/** Layers the auto-generated tile bodies collide with. */\n\tcollidesWith?: readonly string[];\n}\n\nfunction toTilesArray(input: RuntimeLayer['tiles']): Uint32Array {\n\treturn Uint32Array.from(input);\n}\n\nfunction tiledPropsToRecord(props?: readonly TiledProperty[]): Record<string, string | number | boolean> {\n\tif (!props) return {};\n\tconst out: Record<string, string | number | boolean> = {};\n\tfor (const p of props) out[p.name] = p.value;\n\treturn out;\n}\n\nexport function createLoadedTilemap(data: TilemapRuntimeData): LoadedTilemap {\n\tconst { width, height, tileSize, layers, tilesets } = data;\n\n\tif (!Number.isFinite(width) || width <= 0) {\n\t\tthrow new Error(`tilemap: width must be > 0, got ${width}`);\n\t}\n\tif (!Number.isFinite(height) || height <= 0) {\n\t\tthrow new Error(`tilemap: height must be > 0, got ${height}`);\n\t}\n\tif (!Number.isFinite(tileSize) || tileSize <= 0) {\n\t\tthrow new Error(`tilemap: tileSize must be > 0, got ${tileSize}`);\n\t}\n\tif (tilesets.length === 0) {\n\t\tthrow new Error('tilemap: at least one tileset is required');\n\t}\n\n\tconst expectedLen = width * height;\n\n\tconst loadedLayers: LoadedLayer[] = layers.map((l) => {\n\t\tconst tiles = toTilesArray(l.tiles);\n\t\tif (tiles.length !== expectedLen) {\n\t\t\tthrow new Error(\n\t\t\t\t`tilemap: layer \"${l.name}\" tile count ${tiles.length} does not match width*height ${expectedLen}`,\n\t\t\t);\n\t\t}\n\t\treturn {\n\t\t\tname: l.name,\n\t\t\ttiles,\n\t\t\tparallax: l.parallax ? { x: l.parallax.x, y: l.parallax.y } : { x: 1, y: 1 },\n\t\t\topacity: l.opacity ?? 1,\n\t\t\tvisible: l.visible ?? true,\n\t\t};\n\t});\n\n\tconst loadedTilesets: LoadedTileset[] = tilesets.map((t, i) => {\n\t\tif (t.firstgid === undefined && i > 0) {\n\t\t\tthrow new Error(`tilemap: runtime tileset at index ${i} (\"${t.textureKey}\") must specify an explicit firstgid`);\n\t\t}\n\t\treturn {\n\t\t\ttextureKey: t.textureKey,\n\t\t\tcolumns: t.columns,\n\t\t\ttileWidth: t.tileWidth,\n\t\t\ttileHeight: t.tileHeight,\n\t\t\tfirstgid: t.firstgid ?? 1,\n\t\t};\n\t});\n\n\tconst metadataMap = new Map<number, TileMetadata>();\n\tif (data.tileMetadata) {\n\t\tfor (const [k, v] of Object.entries(data.tileMetadata)) {\n\t\t\tmetadataMap.set(Number(k), { ...v });\n\t\t}\n\t}\n\n\tconst objectLayers: LoadedObjectLayer[] = (data.objectLayers ?? []).map(l => ({\n\t\tname: l.name,\n\t\tobjects: l.objects.map(o => ({ ...o, properties: { ...o.properties } })),\n\t}));\n\n\treturn buildLoadedTilemap({\n\t\twidth,\n\t\theight,\n\t\ttileWidth: tileSize,\n\t\ttileHeight: tileSize,\n\t\tlayers: loadedLayers,\n\t\ttilesets: loadedTilesets,\n\t\ttileMetadata: metadataMap,\n\t\tobjectLayers,\n\t});\n}\n\nexport function parseTiledJSON(map: TiledMap, options: ParseTiledOptions): LoadedTilemap {\n\tconst { width, height, tilewidth, tileheight } = map;\n\tconst expectedLen = width * height;\n\n\tconst loadedTilesets: LoadedTileset[] = map.tilesets.map((t) => {\n\t\tconst textureKey = options.tilesetTextures[t.image];\n\t\tif (!textureKey) {\n\t\t\tthrow new Error(`tilemap: no texture key registered for tileset image \"${t.image}\"`);\n\t\t}\n\t\treturn {\n\t\t\ttextureKey,\n\t\t\tcolumns: t.columns,\n\t\t\ttileWidth: t.tilewidth,\n\t\t\ttileHeight: t.tileheight,\n\t\t\tfirstgid: t.firstgid,\n\t\t};\n\t});\n\n\tconst metadataMap = new Map<number, TileMetadata>();\n\tfor (const ts of map.tilesets) {\n\t\tif (!ts.tiles) continue;\n\t\tfor (const td of ts.tiles) {\n\t\t\tmetadataMap.set(ts.firstgid + td.id, tiledPropsToRecord(td.properties) as TileMetadata);\n\t\t}\n\t}\n\n\tconst loadedLayers: LoadedLayer[] = [];\n\tconst loadedObjectLayers: LoadedObjectLayer[] = [];\n\tfor (const l of map.layers) {\n\t\tif (l.type === 'tilelayer') {\n\t\t\tif (l.data.length !== expectedLen) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`tilemap: layer \"${l.name}\" data length ${l.data.length} does not match map ${expectedLen}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tloadedLayers.push({\n\t\t\t\tname: l.name,\n\t\t\t\ttiles: Uint32Array.from(l.data),\n\t\t\t\tparallax: { x: l.parallaxx ?? 1, y: l.parallaxy ?? 1 },\n\t\t\t\topacity: l.opacity,\n\t\t\t\tvisible: l.visible,\n\t\t\t});\n\t\t} else {\n\t\t\tloadedObjectLayers.push({\n\t\t\t\tname: l.name,\n\t\t\t\tobjects: l.objects.map(o => ({\n\t\t\t\t\tname: o.name ?? '',\n\t\t\t\t\ttype: o.type ?? '',\n\t\t\t\t\tx: o.x,\n\t\t\t\t\ty: o.y,\n\t\t\t\t\twidth: o.width ?? 0,\n\t\t\t\t\theight: o.height ?? 0,\n\t\t\t\t\trotation: o.rotation ?? 0,\n\t\t\t\t\tproperties: tiledPropsToRecord(o.properties),\n\t\t\t\t})),\n\t\t\t});\n\t\t}\n\t}\n\n\treturn buildLoadedTilemap({\n\t\twidth,\n\t\theight,\n\t\ttileWidth: tilewidth,\n\t\ttileHeight: tileheight,\n\t\tlayers: loadedLayers,\n\t\ttilesets: loadedTilesets,\n\t\ttileMetadata: metadataMap,\n\t\tobjectLayers: loadedObjectLayers,\n\t});\n}\n\ninterface LoadedTilemapState {\n\twidth: number;\n\theight: number;\n\ttileWidth: number;\n\ttileHeight: number;\n\tlayers: LoadedLayer[];\n\ttilesets: LoadedTileset[];\n\ttileMetadata: Map<number, TileMetadata>;\n\tobjectLayers: LoadedObjectLayer[];\n}\n\nfunction buildLoadedTilemap(state: LoadedTilemapState): LoadedTilemap {\n\tconst { width, height, tileWidth, tileHeight, layers, tilesets, tileMetadata, objectLayers } = state;\n\n\t// Why: hot path. Inlining the bounds + cellIndex math here avoids\n\t// per-layer recomputation in the loop below (called by FOV / pathfinding consumers).\n\tconst flagAtAnyLayer = (tx: number, ty: number, key: TileFlag): boolean => {\n\t\tif (tx < 0 || ty < 0 || tx >= width || ty >= height) return false;\n\t\tconst idx = ty * width + tx;\n\t\tfor (let i = 0; i < layers.length; i++) {\n\t\t\tconst gid = (layers[i]!.tiles[idx] ?? 0) & TILE_GID_MASK;\n\t\t\tif (gid === 0) continue;\n\t\t\tconst meta = tileMetadata.get(gid);\n\t\t\tif (meta && meta[key] === true) return true;\n\t\t}\n\t\treturn false;\n\t};\n\n\tconst defaultCostFromMetadata = (gid: number): number => {\n\t\tconst meta = tileMetadata.get(gid);\n\t\treturn meta?.walkable === true ? 1 : 0;\n\t};\n\n\treturn {\n\t\twidth,\n\t\theight,\n\t\ttileWidth,\n\t\ttileHeight,\n\t\tlayers,\n\t\ttilesets,\n\t\ttileMetadata,\n\t\tobjectLayers,\n\n\t\ttileToWorld(tx, ty) {\n\t\t\treturn { x: (tx + 0.5) * tileWidth, y: (ty + 0.5) * tileHeight };\n\t\t},\n\n\t\tworldToTile(wx, wy) {\n\t\t\treturn { tx: Math.floor(wx / tileWidth), ty: Math.floor(wy / tileHeight) };\n\t\t},\n\n\t\tgetTile(layerIndex, tx, ty) {\n\t\t\tconst layer = layers[layerIndex];\n\t\t\tif (!layer) return 0;\n\t\t\tif (tx < 0 || ty < 0 || tx >= width || ty >= height) return 0;\n\t\t\treturn (layer.tiles[ty * width + tx] ?? 0) & TILE_GID_MASK;\n\t\t},\n\n\t\tisSolid: (tx, ty) => flagAtAnyLayer(tx, ty, 'solid'),\n\t\tisOpaque: (tx, ty) => flagAtAnyLayer(tx, ty, 'blocksSight'),\n\t\tisWalkable: (tx, ty) => flagAtAnyLayer(tx, ty, 'walkable'),\n\n\t\tbuildNavGrid(layerIndex, costFn) {\n\t\t\tconst layer = layers[layerIndex];\n\t\t\tif (!layer) {\n\t\t\t\tthrow new Error(`tilemap: buildNavGrid — no layer at index ${layerIndex}`);\n\t\t\t}\n\t\t\tconst cells = new Uint8Array(width * height);\n\t\t\tconst cost = costFn ?? defaultCostFromMetadata;\n\t\t\tfor (let i = 0; i < cells.length; i++) {\n\t\t\t\tconst gid = (layer.tiles[i] ?? 0) & TILE_GID_MASK;\n\t\t\t\tconst c = cost(gid) | 0;\n\t\t\t\tcells[i] = c < 0 ? 0 : c > 255 ? 255 : c;\n\t\t\t}\n\t\t\treturn createNavGrid({ width, height, cellSize: tileWidth, cells });\n\t\t},\n\n\t\tgetObjectLayer(name) {\n\t\t\treturn objectLayers.find(l => l.name === name)?.objects ?? [];\n\t\t},\n\n\t\tgetObjects(type) {\n\t\t\tconst out: ObjectDef[] = [];\n\t\t\tfor (const layer of objectLayers) {\n\t\t\t\tfor (const o of layer.objects) {\n\t\t\t\t\tif (o.type === type) out.push(o);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn out;\n\t\t},\n\t};\n}\n\ninterface CollisionStrip {\n\ttx: number;\n\tty: number;\n\ttw: number;\n\tth: number;\n}\n\n/** Greedy row-first scan: each row produces N horizontal strips. No vertical merge in v1. */\nfunction buildCollisionStrips(map: LoadedTilemap): CollisionStrip[] {\n\tconst strips: CollisionStrip[] = [];\n\tfor (let ty = 0; ty < map.height; ty++) {\n\t\tlet runStart = -1;\n\t\tfor (let tx = 0; tx <= map.width; tx++) {\n\t\t\tconst solid = tx < map.width && map.isSolid(tx, ty);\n\t\t\tif (solid && runStart === -1) {\n\t\t\t\trunStart = tx;\n\t\t\t} else if (!solid && runStart !== -1) {\n\t\t\t\tstrips.push({ tx: runStart, ty, tw: tx - runStart, th: 1 });\n\t\t\t\trunStart = -1;\n\t\t\t}\n\t\t}\n\t}\n\treturn strips;\n}\n\n// Component shape for the optional collision-strip spawn. The plugin doesn't\n// declare a hard requirement on the collision/transform plugins so users who\n// don't enable `collisionLayer` aren't forced to install them; the cast at the\n// spawn site is the bridge. When `collisionLayer` is set the user has installed\n// both plugins by definition (otherwise the layer name would be meaningless).\ninterface CollisionSpawnShape {\n\ttilemapCollider: TilemapColliderTag;\n\taabbCollider: AABBCollider;\n\tcollisionLayer: CollisionLayer<string>;\n\tlocalTransform: LocalTransform;\n\tworldTransform: WorldTransform;\n}\n\ntype TilemapWorld = ECSpresso<TilemapWorldConfig>;\n\ntype TilemapLabels = never;\n\nexport function createTilemapPlugin<G extends string = 'rendering'>(\n\toptions: TilemapPluginOptions<G> = {},\n) {\n\tconst { collisionLayer, collidesWith } = options;\n\n\treturn definePlugin('tilemap')\n\t\t.withComponentTypes<TilemapComponentTypes>()\n\t\t.withResourceTypes<TilemapResourceTypes>()\n\t\t.withLabels<TilemapLabels>()\n\t\t.withGroups<G>()\n\t\t.install((world: TilemapWorld) => {\n\t\t\tconst entries = new Map<string, LoadedTilemap>();\n\t\t\tconst colliderEntitiesByKey = new Map<string, number[]>();\n\n\t\t\tconst despawnCollidersFor = (dataKey: string): void => {\n\t\t\t\tconst ids = colliderEntitiesByKey.get(dataKey);\n\t\t\t\tif (!ids) return;\n\t\t\t\tfor (const id of ids) world.removeEntity(id);\n\t\t\t\tcolliderEntitiesByKey.delete(dataKey);\n\t\t\t};\n\n\t\t\tconst spawnCollisionStripsFor = (dataKey: string, lt: LoadedTilemap): void => {\n\t\t\t\tif (!collisionLayer) return;\n\t\t\t\tconst ids: number[] = [];\n\t\t\t\tfor (const s of buildCollisionStrips(lt)) {\n\t\t\t\t\tconst cx = (s.tx + s.tw / 2) * lt.tileWidth;\n\t\t\t\t\tconst cy = (s.ty + s.th / 2) * lt.tileHeight;\n\t\t\t\t\tconst components: CollisionSpawnShape = {\n\t\t\t\t\t\ttilemapCollider: { dataKey },\n\t\t\t\t\t\taabbCollider: { width: s.tw * lt.tileWidth, height: s.th * lt.tileHeight },\n\t\t\t\t\t\tcollisionLayer: { layer: collisionLayer, collidesWith: collidesWith ?? [] },\n\t\t\t\t\t\tlocalTransform: { x: cx, y: cy, rotation: 0, scaleX: 1, scaleY: 1 },\n\t\t\t\t\t\tworldTransform: { x: cx, y: cy, rotation: 0, scaleX: 1, scaleY: 1 },\n\t\t\t\t\t};\n\t\t\t\t\tconst entity = (world as unknown as ECSpresso<WorldConfigFrom<CollisionSpawnShape>>).spawn(components);\n\t\t\t\t\tids.push(entity.id);\n\t\t\t\t}\n\t\t\t\tif (ids.length > 0) colliderEntitiesByKey.set(dataKey, ids);\n\t\t\t};\n\n\t\t\tconst ingest = (dataKey: string, lt: LoadedTilemap): LoadedTilemap => {\n\t\t\t\tdespawnCollidersFor(dataKey);\n\t\t\t\tentries.set(dataKey, lt);\n\t\t\t\tspawnCollisionStripsFor(dataKey, lt);\n\t\t\t\treturn lt;\n\t\t\t};\n\n\t\t\tconst registry: TilemapRegistry = {\n\t\t\t\tentries,\n\t\t\t\tregisterRuntime(dataKey, data) {\n\t\t\t\t\treturn ingest(dataKey, createLoadedTilemap(data));\n\t\t\t\t},\n\t\t\t\tasync registerAsset(dataKey, assetKey, parseOptions) {\n\t\t\t\t\tconst raw = await (world as unknown as ECSpresso<WorldConfigFrom<EmptyConfig['components'], EmptyConfig['events'], EmptyConfig['resources'], { [k: string]: TiledMap }>>).loadAsset(assetKey) as TiledMap;\n\t\t\t\t\treturn ingest(dataKey, parseTiledJSON(raw, parseOptions ?? { tilesetTextures: {} }));\n\t\t\t\t},\n\t\t\t\tget: (dataKey) => entries.get(dataKey),\n\t\t\t\thas: (dataKey) => entries.has(dataKey),\n\t\t\t};\n\n\t\t\tworld.addResource('tilemaps', registry);\n\t\t});\n}\n\n/**\n * Create a `tilemap` layer component for spreading into `spawn()`.\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createTilemapLayer('dungeon', 0),\n * ...createLocalTransform(0, 0),\n * });\n * ```\n */\nexport function createTilemapLayer(\n\tdataKey: string,\n\tlayerIndex: number,\n\toptions?: {\n\t\ttilesetKey?: string;\n\t\topacity?: number;\n\t\tparallax?: Vector2D;\n\t\tcullingMode?: TilemapCullingMode;\n\t\tcameraRef?: number;\n\t\ttintFn?: (tx: number, ty: number) => number | null;\n\t},\n): Pick<TilemapComponentTypes, 'tilemap'> {\n\treturn {\n\t\ttilemap: {\n\t\t\tdataKey,\n\t\t\tlayerIndex,\n\t\t\ttilesetKey: options?.tilesetKey,\n\t\t\topacity: options?.opacity ?? 1,\n\t\t\tparallax: options?.parallax ?? { x: 1, y: 1 },\n\t\t\tcullingMode: options?.cullingMode ?? 'viewport',\n\t\t\tcameraRef: options?.cameraRef,\n\t\t\ttintFn: options?.tintFn,\n\t\t},\n\t};\n}\n"
7
+ ],
8
+ "mappings": "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",
9
+ "debugId": "1B20DC0974BC7FAF64756E2164756E21",
10
+ "names": []
11
+ }
@@ -1,4 +1,4 @@
1
- var V=((j)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(j,{get:(z,x)=>(typeof require<"u"?require:z)[x]}):j)(function(j){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+j+'" is not supported')});import{definePlugin as R}from"ecspresso";function S(j,z){return{coroutine:{generator:j,onComplete:z?.onComplete}}}function*Y(j){if(j<=0)return;let z=0;while(z<j){let x=yield;z+=x}}function*Z(j){for(let z=0;z<j;z++)yield}function*_(j){while(!j())yield}function*$(...j){if(j.length===0)return;let z=j.map((x)=>{return x.next(0),{gen:x,done:!1}});while(z.some((x)=>!x.done)){let x=yield;for(let D of z){if(D.done)continue;if(D.gen.next(x).done)D.done=!0}}}function*A(...j){if(j.length===0)return;let z=j.map((x)=>{return x.next(0),{gen:x,done:!1}});try{while(!0){let x=yield;for(let D of z){if(D.done)continue;if(D.gen.next(x).done){D.done=!0;for(let K of z)if(!K.done)K.gen.return(),K.done=!0;return}}}}finally{for(let x of z)if(!x.done)x.gen.return(),x.done=!0}}function*U(j,z,x){let D=!1,H=j.subscribe(z,(K)=>{if(!x||x(K))D=!0});try{while(!D)yield}finally{H()}}function C(j,z){let x=j.getComponent(z,"coroutine");if(!x)return!1;return x.generator.return(),j.commands.removeComponent(z,"coroutine"),!0}function E(j){return{createCoroutine:S,waitForEvent:U}}function F(j){let{systemGroup:z="coroutines",priority:x=0,phase:D="update"}=j??{},H=new Set;return R("coroutines").withComponentTypes().withLabels().withGroups().install((K)=>{K.registerDispose("coroutine",({value:L,entityId:M})=>{L.generator.return(),H.delete(M)}),K.addSystem("coroutine-update").setPriority(x).inPhase(D).inGroup(z).addQuery("coroutines",{with:["coroutine"]}).setOnEntityEnter("coroutines",({entity:L})=>{L.components.coroutine.generator.next(0)}).setProcess(({queries:L,dt:M,ecs:N})=>{for(let J of L.coroutines){if(H.has(J.id)){H.delete(J.id);continue}let O=J.components.coroutine;try{if(O.generator.next(M).done)H.add(J.id),O.onComplete?.({entityId:J.id}),N.commands.removeComponent(J.id,"coroutine")}catch(Q){console.warn(`Coroutine error on entity ${J.id}:`,Q),H.add(J.id),N.commands.removeComponent(J.id,"coroutine")}}})})}export{_ as waitUntil,Y as waitSeconds,Z as waitFrames,U as waitForEvent,A as race,$ as parallel,F as createCoroutinePlugin,E as createCoroutineHelpers,S as createCoroutine,C as cancelCoroutine};
1
+ var R=Object.defineProperty;var S=(j)=>j;function U(j,x){this[j]=S.bind(null,x)}var Y=(j,x)=>{for(var z in x)R(j,z,{get:x[z],enumerable:!0,configurable:!0,set:U.bind(x,z)})};var Z=(j,x)=>()=>(j&&(x=j(j=0)),x);var _=((j)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(j,{get:(x,z)=>(typeof require<"u"?require:x)[z]}):j)(function(j){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+j+'" is not supported')});import{definePlugin as V}from"ecspresso";function W(j,x){return{coroutine:{generator:j,onComplete:x?.onComplete}}}function*C(j){if(j<=0)return;let x=0;while(x<j){let z=yield;x+=z}}function*E(j){for(let x=0;x<j;x++)yield}function*F(j){while(!j())yield}function*G(...j){if(j.length===0)return;let x=j.map((z)=>{return z.next(0),{gen:z,done:!1}});while(x.some((z)=>!z.done)){let z=yield;for(let D of x){if(D.done)continue;if(D.gen.next(z).done)D.done=!0}}}function*P(...j){if(j.length===0)return;let x=j.map((z)=>{return z.next(0),{gen:z,done:!1}});try{while(!0){let z=yield;for(let D of x){if(D.done)continue;if(D.gen.next(z).done){D.done=!0;for(let K of x)if(!K.done)K.gen.return(),K.done=!0;return}}}}finally{for(let z of x)if(!z.done)z.gen.return(),z.done=!0}}function*X(j,x,z){let D=!1,H=j.subscribe(x,(K)=>{if(!z||z(K))D=!0});try{while(!D)yield}finally{H()}}function k(j,x){let z=j.getComponent(x,"coroutine");if(!z)return!1;return z.generator.return(),j.commands.removeComponent(x,"coroutine"),!0}function q(j){return{createCoroutine:W,waitForEvent:X}}function B(j){let{systemGroup:x="coroutines",priority:z=0,phase:D="update"}=j??{},H=new Set;return V("coroutines").withComponentTypes().withLabels().withGroups().install((K)=>{K.registerDispose("coroutine",({value:L,entityId:M})=>{L.generator.return(),H.delete(M)}),K.addSystem("coroutine-update").setPriority(z).inPhase(D).inGroup(x).addQuery("coroutines",{with:["coroutine"]}).setOnEntityEnter("coroutines",({entity:L})=>{L.components.coroutine.generator.next(0)}).setProcess(({queries:L,dt:M,ecs:N})=>{for(let J of L.coroutines){if(H.has(J.id)){H.delete(J.id);continue}let O=J.components.coroutine;try{if(O.generator.next(M).done)H.add(J.id),O.onComplete?.({entityId:J.id}),N.commands.removeComponent(J.id,"coroutine")}catch(Q){console.warn(`Coroutine error on entity ${J.id}:`,Q),H.add(J.id),N.commands.removeComponent(J.id,"coroutine")}}})})}export{F as waitUntil,C as waitSeconds,E as waitFrames,X as waitForEvent,P as race,G as parallel,B as createCoroutinePlugin,q as createCoroutineHelpers,W as createCoroutine,k as cancelCoroutine};
2
2
 
3
- //# debugId=DF25F3CC4BAA535D64756E2164756E21
3
+ //# debugId=8EE943BD950C16EB64756E2164756E21
4
4
  //# sourceMappingURL=coroutine.js.map
@@ -4,7 +4,7 @@
4
4
  "sourcesContent": [
5
5
  "/**\n * Coroutine Plugin for ECSpresso\n *\n * ES6 generator-based coroutines for multi-step, frame-spanning scripted sequences.\n * A `coroutine` component holds a live generator. A system ticks all generators each\n * frame via `.next(dt)`. Helper generators (`waitSeconds`, `waitFrames`, `waitUntil`,\n * `waitForEvent`, `parallel`, `race`) compose via `yield*`.\n */\n\nimport { definePlugin, type BasePluginOptions } from 'ecspresso';\nimport type { EventsOfWorld, AnyECSpresso } from 'ecspresso';\n\n// ==================== Generator Protocol ====================\n\n/**\n * Yields void, returns void, receives deltaTime (number) via `.next(dt)`.\n * First `.next(dt)` initializes the generator (runs to first yield, dt discarded per JS spec).\n * Subsequent `.next(dt)` resume from yield with dt as the yield expression value.\n */\nexport type CoroutineGenerator = Generator<void, void, number>;\n\n// ==================== Event Types ====================\n\nexport interface CoroutineEventData {\n\tentityId: number;\n}\n\n\n// ==================== Component Types ====================\n\nexport interface CoroutineState {\n\tgenerator: CoroutineGenerator;\n\tonComplete?: (data: CoroutineEventData) => void;\n}\n\nexport interface CoroutineComponentTypes {\n\tcoroutine: CoroutineState;\n}\n\n// ==================== Plugin Options ====================\n\nexport interface CoroutinePluginOptions<G extends string = 'coroutines'> extends BasePluginOptions<G> {}\n\n// ==================== Component Factory ====================\n\nexport interface CoroutineOptions {\n\tonComplete?: (data: CoroutineEventData) => void;\n}\n\n/**\n * Create a coroutine component for spawning or adding to an entity.\n *\n * @param generator - The generator function to drive\n * @param options - Optional configuration (onComplete event)\n * @returns Component object suitable for spreading into spawn()\n */\nexport function createCoroutine(\n\tgenerator: CoroutineGenerator,\n\toptions?: CoroutineOptions,\n): Pick<CoroutineComponentTypes, 'coroutine'> {\n\treturn {\n\t\tcoroutine: {\n\t\t\tgenerator,\n\t\t\tonComplete: options?.onComplete,\n\t\t},\n\t};\n}\n\n// ==================== Helper Generators (standalone) ====================\n\n/**\n * Wait for a specified number of seconds. Accumulates dt until elapsed >= seconds.\n * If seconds <= 0, returns immediately.\n */\nexport function* waitSeconds(seconds: number): CoroutineGenerator {\n\tif (seconds <= 0) return;\n\tlet elapsed = 0;\n\twhile (elapsed < seconds) {\n\t\tconst dt: number = yield;\n\t\telapsed += dt;\n\t}\n}\n\n/**\n * Wait for a specified number of frames. Yields `frames` times.\n * If frames <= 0, returns immediately.\n */\nexport function* waitFrames(frames: number): CoroutineGenerator {\n\tfor (let i = 0; i < frames; i++) {\n\t\tyield;\n\t}\n}\n\n/**\n * Wait until a predicate returns true. Yields each frame until predicate is satisfied.\n * User closes over ecs if needed for state checks.\n */\nexport function* waitUntil(predicate: () => boolean): CoroutineGenerator {\n\twhile (!predicate()) {\n\t\tyield;\n\t}\n}\n\n/**\n * Run multiple coroutines in parallel. Completes when all finish.\n * Initializes all sub-generators, ticks all each frame.\n * Empty array = immediate return.\n */\nexport function* parallel(...coroutines: CoroutineGenerator[]): CoroutineGenerator {\n\tif (coroutines.length === 0) return;\n\n\t// Initialize all generators\n\tconst active = coroutines.map(gen => {\n\t\tgen.next(0);\n\t\treturn { gen, done: false };\n\t});\n\n\twhile (active.some(entry => !entry.done)) {\n\t\tconst dt: number = yield;\n\t\tfor (const entry of active) {\n\t\t\tif (entry.done) continue;\n\t\t\tconst result = entry.gen.next(dt);\n\t\t\tif (result.done) {\n\t\t\t\tentry.done = true;\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * Run multiple coroutines, completing when the first one finishes.\n * Calls `.return()` on remaining generators (triggers finally blocks).\n * Empty array = immediate return.\n */\nexport function* race(...coroutines: CoroutineGenerator[]): CoroutineGenerator {\n\tif (coroutines.length === 0) return;\n\n\t// Initialize all generators\n\tconst entries = coroutines.map(gen => {\n\t\tgen.next(0);\n\t\treturn { gen, done: false };\n\t});\n\n\ttry {\n\t\twhile (true) {\n\t\t\tconst dt: number = yield;\n\t\t\tfor (const entry of entries) {\n\t\t\t\tif (entry.done) continue;\n\t\t\t\tconst result = entry.gen.next(dt);\n\t\t\t\tif (result.done) {\n\t\t\t\t\tentry.done = true;\n\t\t\t\t\t// Cancel all others\n\t\t\t\t\tfor (const other of entries) {\n\t\t\t\t\t\tif (!other.done) {\n\t\t\t\t\t\t\tother.gen.return();\n\t\t\t\t\t\t\tother.done = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} finally {\n\t\t// Clean up all on external cancellation\n\t\tfor (const entry of entries) {\n\t\t\tif (!entry.done) {\n\t\t\t\tentry.gen.return();\n\t\t\t\tentry.done = true;\n\t\t\t}\n\t\t}\n\t}\n}\n\n// ==================== Helper Generator (ECS-dependent) ====================\n\n/**\n * Wait until a matching event fires on the event bus.\n * Subscribes via eventBus.subscribe, yields until event received, unsubscribes in finally block.\n *\n * @param eventBus - Object with subscribe method (typically ecs.eventBus)\n * @param eventType - Event type name to listen for\n * @param filter - Optional predicate to filter events\n */\nexport function* waitForEvent<ET extends Record<string, any>, E extends keyof ET & string>(\n\teventBus: { subscribe(type: E, cb: (data: ET[E]) => void): () => void },\n\teventType: E,\n\tfilter?: (data: ET[E]) => boolean,\n): CoroutineGenerator {\n\tlet received = false;\n\tconst unsubscribe = eventBus.subscribe(eventType, (data: ET[E]) => {\n\t\tif (!filter || filter(data)) {\n\t\t\treceived = true;\n\t\t}\n\t});\n\ttry {\n\t\twhile (!received) {\n\t\t\tyield;\n\t\t}\n\t} finally {\n\t\tunsubscribe();\n\t}\n}\n\n// ==================== Cancellation ====================\n\n/**\n * Structural interface for ECS methods used by cancelCoroutine.\n */\nexport interface CoroutineWorld {\n\tgetComponent(entityId: number, componentName: string): unknown | undefined;\n\tcommands: {\n\t\tremoveComponent(entityId: number, componentName: string): void;\n\t};\n}\n\n/**\n * Cancel a running coroutine on an entity. Calls generator.return() (triggers finally blocks)\n * and queues component removal.\n *\n * @returns true if the entity had a coroutine that was cancelled, false otherwise\n */\nexport function cancelCoroutine(ecs: CoroutineWorld, entityId: number): boolean {\n\tconst state = ecs.getComponent(entityId, 'coroutine') as CoroutineState | undefined;\n\tif (!state) return false;\n\tstate.generator.return();\n\tecs.commands.removeComponent(entityId, 'coroutine');\n\treturn true;\n}\n\n// ==================== Typed Helpers (replaces Kit Pattern) ====================\n\n/**\n * Type-safe coroutine helpers that validate event names against a world's event types.\n * Use `createCoroutineHelpers<typeof ecs>()` to get compile-time validation.\n */\nexport interface CoroutineHelpers<W extends AnyECSpresso> {\n\tcreateCoroutine: (\n\t\tgenerator: CoroutineGenerator,\n\t\toptions?: { onComplete?: (data: CoroutineEventData) => void },\n\t) => Pick<CoroutineComponentTypes, 'coroutine'>;\n\twaitForEvent: <E extends keyof EventsOfWorld<W> & string>(\n\t\teventBus: { subscribe(type: E, cb: (data: EventsOfWorld<W>[E]) => void): () => void },\n\t\teventType: E,\n\t\tfilter?: (data: EventsOfWorld<W>[E]) => boolean,\n\t) => CoroutineGenerator;\n}\n\n/**\n * Create typed coroutine helpers that validate event names at compile time.\n *\n * @example\n * ```typescript\n * const { createCoroutine, waitForEvent } = createCoroutineHelpers<typeof ecs>();\n * ecs.spawn({ ...createCoroutine(myGen(), { onComplete: (data) => console.log(data.entityId) }) });\n * ```\n */\nexport function createCoroutineHelpers<W extends AnyECSpresso>(_world?: W): CoroutineHelpers<W> {\n\treturn {\n\t\tcreateCoroutine: createCoroutine as CoroutineHelpers<W>['createCoroutine'],\n\t\twaitForEvent: waitForEvent as CoroutineHelpers<W>['waitForEvent'],\n\t};\n}\n\n// ==================== Plugin Factory ====================\n\n/**\n * Create a coroutine plugin for ECSpresso.\n *\n * This plugin provides:\n * - Coroutine system that ticks all generator-based coroutines each frame\n * - Automatic cleanup via dispose callback (triggers generator finally blocks)\n * - `onComplete` callback invocation\n * - Component removal on completion\n */\nexport function createCoroutinePlugin<G extends string = 'coroutines'>(\n\toptions?: CoroutinePluginOptions<G>,\n) {\n\tconst {\n\t\tsystemGroup = 'coroutines',\n\t\tpriority = 0,\n\t\tphase = 'update',\n\t} = options ?? {};\n\n\t// Tracks entities whose coroutine completed this frame to prevent re-ticking\n\t// before the command buffer removes the component.\n\tconst finished = new Set<number>();\n\n\treturn definePlugin('coroutines')\n\t\t.withComponentTypes<CoroutineComponentTypes>()\n\t\t.withLabels<'coroutine-update'>()\n\t\t.withGroups<G>()\n\t\t.install((world) => {\n\t\t\tworld.registerDispose('coroutine', ({ value, entityId }) => {\n\t\t\t\tvalue.generator.return();\n\t\t\t\tfinished.delete(entityId);\n\t\t\t});\n\n\t\t\tworld\n\t\t\t\t.addSystem('coroutine-update')\n\t\t\t\t.setPriority(priority)\n\t\t\t\t.inPhase(phase)\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.addQuery('coroutines', {\n\t\t\t\t\twith: ['coroutine'],\n\t\t\t\t})\n\t\t\t\t.setOnEntityEnter('coroutines', ({ entity }) => {\n\t\t\t\t\tentity.components.coroutine.generator.next(0);\n\t\t\t\t})\n\t\t\t\t.setProcess(({ queries, dt, ecs }) => {\n\t\t\t\t\tfor (const entity of queries.coroutines) {\n\t\t\t\t\t\t// Already completed — skip until command buffer removes the component\n\t\t\t\t\t\tif (finished.has(entity.id)) {\n\t\t\t\t\t\t\tfinished.delete(entity.id);\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst state = entity.components.coroutine;\n\n\t\t\t\t\t\t// Tick the generator\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst result = state.generator.next(dt);\n\t\t\t\t\t\t\tif (result.done) {\n\t\t\t\t\t\t\t\tfinished.add(entity.id);\n\t\t\t\t\t\t\t\tstate.onComplete?.({ entityId: entity.id });\n\t\t\t\t\t\t\t\tecs.commands.removeComponent(entity.id, 'coroutine');\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tconsole.warn(`Coroutine error on entity ${entity.id}:`, error);\n\t\t\t\t\t\t\tfinished.add(entity.id);\n\t\t\t\t\t\t\tecs.commands.removeComponent(entity.id, 'coroutine');\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t});\n}\n"
6
6
  ],
7
- "mappings": "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",
8
- "debugId": "DF25F3CC4BAA535D64756E2164756E21",
7
+ "mappings": "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",
8
+ "debugId": "8EE943BD950C16EB64756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -1,4 +1,4 @@
1
- var Y=((b)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(b,{get:(j,z)=>(typeof require<"u"?require:j)[z]}):b)(function(b){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+b+'" is not supported')});import{definePlugin as W}from"ecspresso";function X(b,j){return Object.freeze({id:b,initial:j.initial,states:Object.freeze(j.states)})}function $(b,j){let z=j?.initial??b.initial;return{stateMachine:{definition:b,current:z,previous:null,stateTime:0}}}function L(b,j,z,A){let H=z.definition.states,D=H[z.current],B=H[A];if(!B)return!1;return D?.onExit?.({ecs:b,entityId:j}),z.previous=z.current,z.current=A,z.stateTime=0,B.onEnter?.({ecs:b,entityId:j}),b.markChanged(j,"stateMachine"),b.eventBus.publish("stateTransition",{entityId:j,from:z.previous,to:z.current,definitionId:z.definition.id}),!0}function E(b,j,z){let A=b.getComponent(j,"stateMachine");if(!A)return!1;return L(b,j,A,z)}function G(b,j,z){let A=b.getComponent(j,"stateMachine");if(!A)return!1;let D=A.definition.states[A.current];if(!D?.on)return!1;let B=D.on[z];if(B===void 0)return!1;if(typeof B==="string")return L(b,j,A,B);if(!B.guard({ecs:b,entityId:j}))return!1;return L(b,j,A,B.target)}function M(b,j){return b.getComponent(j,"stateMachine")?.current}function P(b){return{defineStateMachine:X}}function q(b){let{systemGroup:j="stateMachine",priority:z=0,phase:A="update"}=b??{};return W("stateMachine").withComponentTypes().withEventTypes().withLabels().withGroups().install((H)=>{H.addSystem("state-machine-update").setPriority(z).inPhase(A).inGroup(j).addQuery("machines",{with:["stateMachine"]}).setOnEntityEnter("machines",({entity:D,ecs:B})=>{let K=D.components.stateMachine,Q=K.definition.states,F=B;Q[K.current]?.onEnter?.({ecs:F,entityId:D.id})}).setProcess(({queries:D,dt:B,ecs:K})=>{let F={ecs:K,entityId:0,dt:0};for(let O of D.machines){let J=O.components.stateMachine,R=J.definition.states;F.entityId=O.id,F.dt=B,J.stateTime+=B,R[J.current]?.onUpdate?.(F);let U=R[J.current];if(U?.transitions){for(let V of U.transitions)if(V.guard(F)){L(F.ecs,O.id,J,V.target);break}}}})})}export{E as transitionTo,G as sendEvent,M as getStateMachineState,X as defineStateMachine,q as createStateMachinePlugin,P as createStateMachineHelpers,$ as createStateMachine};
1
+ var W=Object.defineProperty;var X=(b)=>b;function Y(b,j){this[b]=X.bind(null,j)}var $=(b,j)=>{for(var z in j)W(b,z,{get:j[z],enumerable:!0,configurable:!0,set:Y.bind(j,z)})};var E=(b,j)=>()=>(b&&(j=b(b=0)),j);var G=((b)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(b,{get:(j,z)=>(typeof require<"u"?require:j)[z]}):b)(function(b){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+b+'" is not supported')});import{definePlugin as Z}from"ecspresso";function _(b,j){return Object.freeze({id:b,initial:j.initial,states:Object.freeze(j.states)})}function q(b,j){let z=j?.initial??b.initial;return{stateMachine:{definition:b,current:z,previous:null,stateTime:0}}}function L(b,j,z,A){let H=z.definition.states,D=H[z.current],B=H[A];if(!B)return!1;return D?.onExit?.({ecs:b,entityId:j}),z.previous=z.current,z.current=A,z.stateTime=0,B.onEnter?.({ecs:b,entityId:j}),b.markChanged(j,"stateMachine"),b.eventBus.publish("stateTransition",{entityId:j,from:z.previous,to:z.current,definitionId:z.definition.id}),!0}function N(b,j,z){let A=b.getComponent(j,"stateMachine");if(!A)return!1;return L(b,j,A,z)}function v(b,j,z){let A=b.getComponent(j,"stateMachine");if(!A)return!1;let D=A.definition.states[A.current];if(!D?.on)return!1;let B=D.on[z];if(B===void 0)return!1;if(typeof B==="string")return L(b,j,A,B);if(!B.guard({ecs:b,entityId:j}))return!1;return L(b,j,A,B.target)}function w(b,j){return b.getComponent(j,"stateMachine")?.current}function T(b){return{defineStateMachine:_}}function k(b){let{systemGroup:j="stateMachine",priority:z=0,phase:A="update"}=b??{};return Z("stateMachine").withComponentTypes().withEventTypes().withLabels().withGroups().install((H)=>{H.addSystem("state-machine-update").setPriority(z).inPhase(A).inGroup(j).addQuery("machines",{with:["stateMachine"]}).setOnEntityEnter("machines",({entity:D,ecs:B})=>{let K=D.components.stateMachine,Q=K.definition.states,F=B;Q[K.current]?.onEnter?.({ecs:F,entityId:D.id})}).setProcess(({queries:D,dt:B,ecs:K})=>{let F={ecs:K,entityId:0,dt:0};for(let O of D.machines){let J=O.components.stateMachine,R=J.definition.states;F.entityId=O.id,F.dt=B,J.stateTime+=B,R[J.current]?.onUpdate?.(F);let U=R[J.current];if(U?.transitions){for(let V of U.transitions)if(V.guard(F)){L(F.ecs,O.id,J,V.target);break}}}})})}export{N as transitionTo,v as sendEvent,w as getStateMachineState,_ as defineStateMachine,k as createStateMachinePlugin,T as createStateMachineHelpers,q as createStateMachine};
2
2
 
3
- //# debugId=6ADF9D5235D78F5264756E2164756E21
3
+ //# debugId=BF20B5C1E19C9EB064756E2164756E21
4
4
  //# sourceMappingURL=state-machine.js.map
@@ -4,7 +4,7 @@
4
4
  "sourcesContent": [
5
5
  "/**\n * State Machine Plugin for ECSpresso\n *\n * Provides ECS-native finite state machines with guard-based transitions,\n * event-driven transitions, and lifecycle hooks (onEnter, onExit, onUpdate).\n *\n * Each entity gets a `stateMachine` component referencing a shared definition.\n * One system processes all state machine entities each tick.\n */\n\nimport { definePlugin, type BasePluginOptions } from 'ecspresso';\nimport type { BaseWorld } from 'ecspresso';\n\n/** BaseWorld narrowed to state-machine components for typed access in helpers. */\ntype StateMachineWorld = BaseWorld<StateMachineComponentTypes>;\n\n// ==================== State Config ====================\n\n/**\n * Configuration for a single state in a state machine definition.\n *\n * @template S - Union of state name strings\n * @template W - World interface type for hooks/guards (default: StateMachineWorld)\n */\nexport interface StateConfig<S extends string, W extends BaseWorld<StateMachineComponentTypes> = StateMachineWorld> {\n\t/** Called when entering this state */\n\tonEnter?(ctx: { ecs: W; entityId: number }): void;\n\t/** Called when exiting this state */\n\tonExit?(ctx: { ecs: W; entityId: number }): void;\n\t/** Called each tick while in this state */\n\tonUpdate?(ctx: { ecs: W; entityId: number; dt: number }): void;\n\t/** Guard-based transitions evaluated each tick. First passing guard wins. */\n\ttransitions?: ReadonlyArray<{\n\t\ttarget: S;\n\t\tguard(ctx: { ecs: W; entityId: number }): boolean;\n\t}>;\n\t/** Event-based transition map: eventName → target state or guarded transition */\n\ton?: Record<string, S | { target: S; guard(ctx: { ecs: W; entityId: number }): boolean }>;\n}\n\n// ==================== State Machine Definition ====================\n\n/**\n * Immutable definition of a state machine. Shared across entities.\n *\n * @template S - Union of state name strings\n */\nexport interface StateMachineDefinition<S extends string> {\n\treadonly id: string;\n\treadonly initial: S;\n\treadonly states: { readonly [K in S]: StateConfig<S> };\n}\n\n// ==================== Component ====================\n\n/**\n * Runtime state machine component stored on each entity.\n *\n * @template S - Union of state name strings (default: string)\n */\nexport interface StateMachine<S extends string = string> {\n\treadonly definition: StateMachineDefinition<string>;\n\tcurrent: S;\n\tprevious: S | null;\n\tstateTime: number;\n}\n\n/**\n * Component types provided by the state machine plugin.\n *\n * @template S - Union of state name strings (default: string)\n */\nexport interface StateMachineComponentTypes<S extends string = string> {\n\tstateMachine: StateMachine<S>;\n}\n\n// ==================== Event Types ====================\n\n/**\n * Event published on every state transition.\n *\n * @template S - Union of state name strings (default: string)\n */\nexport interface StateTransitionEvent<S extends string = string> {\n\tentityId: number;\n\tfrom: S;\n\tto: S;\n\tdefinitionId: string;\n}\n\n/**\n * Event types provided by the state machine plugin.\n *\n * @template S - Union of state name strings (default: string)\n */\nexport interface StateMachineEventTypes<S extends string = string> {\n\tstateTransition: StateTransitionEvent<S>;\n}\n\n/**\n * Extract the state name union from a StateMachineDefinition.\n *\n * @example\n * ```typescript\n * const enemyFSM = defineStateMachine('enemy', { initial: 'idle', states: { idle: {}, chase: {} } });\n * type EnemyStates = StatesOf<typeof enemyFSM>; // 'idle' | 'chase'\n * type AllStates = StatesOf<typeof enemyFSM> | StatesOf<typeof playerFSM>;\n * ```\n */\nexport type StatesOf<D> = D extends StateMachineDefinition<infer S> ? S : never;\n\n// ==================== Plugin Options ====================\n\n/**\n * Configuration options for the state machine plugin.\n */\nexport interface StateMachinePluginOptions<G extends string = 'stateMachine'> extends BasePluginOptions<G> {}\n\n// ==================== Helper Functions ====================\n\n/**\n * Define a state machine with type-safe state names.\n *\n * @template S - Union of state name strings, inferred from `states` keys\n * @param id - Unique identifier for this definition\n * @param config - Initial state and state configurations\n * @returns A frozen StateMachineDefinition\n *\n * @example\n * ```typescript\n * const enemyFSM = defineStateMachine('enemy', {\n * initial: 'idle',\n * states: {\n * idle: {\n * onEnter: ({ ecs, entityId }) => { ... },\n * transitions: [{ target: 'chase', guard: ({ ecs, entityId }) => playerNearby(ecs, entityId) }],\n * },\n * chase: {\n * onUpdate: ({ ecs, entityId, dt }) => { ... },\n * on: { playerLost: 'idle' },\n * },\n * },\n * });\n * ```\n */\nexport function defineStateMachine<S extends string>(\n\tid: string,\n\tconfig: { initial: NoInfer<S>; states: Record<S, StateConfig<NoInfer<S>>> },\n): StateMachineDefinition<S> {\n\treturn Object.freeze({\n\t\tid,\n\t\tinitial: config.initial,\n\t\tstates: Object.freeze(config.states),\n\t}) as StateMachineDefinition<S>;\n}\n\n/**\n * Create a stateMachine component from a definition.\n *\n * @param definition - The state machine definition to use\n * @param options - Optional overrides (e.g., initial state)\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createStateMachine(enemyFSM),\n * position: { x: 100, y: 200 },\n * });\n * ```\n */\nexport function createStateMachine<S extends string>(\n\tdefinition: StateMachineDefinition<S>,\n\toptions?: { initial?: S },\n): Pick<StateMachineComponentTypes<S>, 'stateMachine'> {\n\tconst initial = options?.initial ?? definition.initial;\n\treturn {\n\t\tstateMachine: {\n\t\t\tdefinition,\n\t\t\tcurrent: initial,\n\t\t\tprevious: null,\n\t\t\tstateTime: 0,\n\t\t},\n\t};\n}\n\n// ==================== Internal: Shared Transition Logic ====================\n\n/**\n * Perform a state transition: onExit → update fields → onEnter → markChanged → publish event.\n * Returns true if the target state exists, false otherwise.\n */\nfunction performTransition(\n\tecs: StateMachineWorld,\n\tentityId: number,\n\tsm: StateMachine,\n\ttargetState: string,\n): boolean {\n\tconst states = sm.definition.states as Record<string, StateConfig<string>>;\n\tconst currentConfig = states[sm.current];\n\tconst targetConfig = states[targetState];\n\n\tif (!targetConfig) return false;\n\n\tcurrentConfig?.onExit?.({ ecs, entityId });\n\n\tsm.previous = sm.current;\n\tsm.current = targetState;\n\tsm.stateTime = 0;\n\n\ttargetConfig.onEnter?.({ ecs, entityId });\n\n\tecs.markChanged(entityId, 'stateMachine');\n\tecs.eventBus.publish('stateTransition', {\n\t\tentityId,\n\t\tfrom: sm.previous,\n\t\tto: sm.current,\n\t\tdefinitionId: sm.definition.id,\n\t} satisfies StateTransitionEvent);\n\n\treturn true;\n}\n\n// ==================== Utility Functions ====================\n\n/**\n * Directly transition an entity's state machine to a target state.\n * Fires onExit, onEnter hooks and publishes stateTransition event.\n *\n * @param ecs - ECS instance (structural typing)\n * @param entityId - Entity to transition\n * @param targetState - State to transition to\n * @returns true if transition succeeded, false if entity has no stateMachine or target state doesn't exist\n */\nexport function transitionTo(\n\tecs: StateMachineWorld,\n\tentityId: number,\n\ttargetState: string,\n): boolean {\n\tconst sm = ecs.getComponent(entityId, 'stateMachine');\n\tif (!sm) return false;\n\treturn performTransition(ecs, entityId, sm, targetState);\n}\n\n/**\n * Send a named event to an entity's state machine.\n * Checks the current state's `on` handlers for a matching event.\n *\n * @param ecs - ECS instance (structural typing)\n * @param entityId - Entity to send event to\n * @param eventName - Event name to match against `on` handlers\n * @returns true if a transition occurred, false otherwise\n */\nexport function sendEvent(\n\tecs: StateMachineWorld,\n\tentityId: number,\n\teventName: string,\n): boolean {\n\tconst sm = ecs.getComponent(entityId, 'stateMachine');\n\tif (!sm) return false;\n\n\tconst states = sm.definition.states as Record<string, StateConfig<string>>;\n\tconst currentConfig = states[sm.current];\n\tif (!currentConfig?.on) return false;\n\n\tconst handler = currentConfig.on[eventName];\n\tif (handler === undefined) return false;\n\n\tif (typeof handler === 'string') {\n\t\treturn performTransition(ecs, entityId, sm, handler);\n\t}\n\n\tif (!handler.guard({ ecs, entityId })) return false;\n\treturn performTransition(ecs, entityId, sm, handler.target);\n}\n\n/**\n * Get the current state of an entity's state machine.\n *\n * @param ecs - ECS instance (structural typing)\n * @param entityId - Entity to query\n * @returns The current state string, or undefined if entity has no stateMachine\n */\nexport function getStateMachineState(\n\tecs: StateMachineWorld,\n\tentityId: number,\n): string | undefined {\n\tconst sm = ecs.getComponent(entityId, 'stateMachine');\n\treturn sm?.current;\n}\n\n// ==================== State Machine Helpers ====================\n\n/**\n * Typed helpers for the state machine plugin.\n * Creates helpers that validate hook parameters against the world type W.\n * Call after .build() using typeof ecs.\n */\nexport interface StateMachineHelpers<W extends BaseWorld<StateMachineComponentTypes>> {\n\tdefineStateMachine: <S extends string>(\n\t\tid: string,\n\t\tconfig: { initial: NoInfer<S>; states: Record<S, StateConfig<NoInfer<S>, W>> },\n\t) => StateMachineDefinition<S>;\n}\n\nexport function createStateMachineHelpers<W extends BaseWorld<StateMachineComponentTypes> = StateMachineWorld>(_world?: W): StateMachineHelpers<W> {\n\treturn {\n\t\tdefineStateMachine: defineStateMachine as StateMachineHelpers<W>['defineStateMachine'],\n\t};\n}\n\n// ==================== Plugin Factory ====================\n\n/**\n * Create a state machine plugin for ECSpresso.\n *\n * Provides:\n * - Lifecycle hooks (onEnter, onExit, onUpdate) per state\n * - Guard-based automatic transitions evaluated each tick\n * - Event-based transitions via `sendEvent()`\n * - Direct transitions via `transitionTo()`\n * - stateTransition events published on every transition\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso.create()\n * .withPlugin(createStateMachinePlugin())\n * .build();\n *\n * const fsm = defineStateMachine('enemy', {\n * initial: 'idle',\n * states: {\n * idle: {\n * transitions: [{ target: 'chase', guard: ({ ecs, entityId }) => playerNearby(ecs, entityId) }],\n * },\n * chase: {\n * onUpdate: ({ ecs, entityId, dt }) => moveTowardPlayer(ecs, entityId, dt),\n * on: { playerLost: 'idle' },\n * },\n * },\n * });\n *\n * ecs.spawn({\n * ...createStateMachine(fsm),\n * position: { x: 0, y: 0 },\n * });\n * ```\n */\nexport function createStateMachinePlugin<S extends string = string, G extends string = 'stateMachine'>(\n\toptions?: StateMachinePluginOptions<G>,\n) {\n\tconst {\n\t\tsystemGroup = 'stateMachine',\n\t\tpriority = 0,\n\t\tphase = 'update',\n\t} = options ?? {};\n\n\treturn definePlugin('stateMachine')\n\t\t.withComponentTypes<StateMachineComponentTypes<S>>()\n\t\t.withEventTypes<StateMachineEventTypes<S>>()\n\t\t.withLabels<'state-machine-update'>()\n\t\t.withGroups<G>()\n\t\t.install((world) => {\n\t\t\tworld\n\t\t\t\t.addSystem('state-machine-update')\n\t\t\t\t.setPriority(priority)\n\t\t\t\t.inPhase(phase)\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.addQuery('machines', {\n\t\t\t\t\twith: ['stateMachine'],\n\t\t\t\t})\n\t\t\t\t.setOnEntityEnter('machines', ({ entity, ecs }) => {\n\t\t\t\t\tconst sm = entity.components.stateMachine;\n\t\t\t\t\tconst states = sm.definition.states as Record<string, StateConfig<string>>;\n\t\t\t\t\tconst world: StateMachineWorld = ecs;\n\t\t\t\t\tstates[sm.current]?.onEnter?.({ ecs: world, entityId: entity.id });\n\t\t\t\t})\n\t\t\t\t.setProcess(({ queries, dt, ecs }) => {\n\t\t\t\t\t// Pre-allocated context reused across entities to avoid per-entity-per-frame allocations\n\t\t\t\t\tconst world: StateMachineWorld = ecs;\n\t\t\t\t\tconst hookCtx = { ecs: world, entityId: 0, dt: 0 };\n\n\t\t\t\t\tfor (const entity of queries.machines) {\n\t\t\t\t\t\tconst sm = entity.components.stateMachine;\n\t\t\t\t\t\tconst states = sm.definition.states as Record<string, StateConfig<string>>;\n\n\t\t\t\t\t\thookCtx.entityId = entity.id;\n\t\t\t\t\t\thookCtx.dt = dt;\n\n\t\t\t\t\t\t// Accumulate state time\n\t\t\t\t\t\tsm.stateTime += dt;\n\n\t\t\t\t\t\t// onUpdate hook\n\t\t\t\t\t\tstates[sm.current]?.onUpdate?.(hookCtx);\n\n\t\t\t\t\t\t// Evaluate guard transitions (first passing guard wins)\n\t\t\t\t\t\tconst currentConfig = states[sm.current];\n\t\t\t\t\t\tif (currentConfig?.transitions) {\n\t\t\t\t\t\t\tfor (const transition of currentConfig.transitions) {\n\t\t\t\t\t\t\t\tif (transition.guard(hookCtx)) {\n\t\t\t\t\t\t\t\t\tperformTransition(hookCtx.ecs, entity.id, sm, transition.target);\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t});\n}\n"
6
6
  ],
7
- "mappings": "2PAUA,uBAAS,kBAuIF,SAAS,CAAoC,CACnD,EACA,EAC4B,CAC5B,OAAO,OAAO,OAAO,CACpB,KACA,QAAS,EAAO,QAChB,OAAQ,OAAO,OAAO,EAAO,MAAM,CACpC,CAAC,EAkBK,SAAS,CAAoC,CACnD,EACA,EACsD,CACtD,IAAM,EAAU,GAAS,SAAW,EAAW,QAC/C,MAAO,CACN,aAAc,CACb,aACA,QAAS,EACT,SAAU,KACV,UAAW,CACZ,CACD,EASD,SAAS,CAAiB,CACzB,EACA,EACA,EACA,EACU,CACV,IAAM,EAAS,EAAG,WAAW,OACvB,EAAgB,EAAO,EAAG,SAC1B,EAAe,EAAO,GAE5B,GAAI,CAAC,EAAc,MAAO,GAkB1B,OAhBA,GAAe,SAAS,CAAE,MAAK,UAAS,CAAC,EAEzC,EAAG,SAAW,EAAG,QACjB,EAAG,QAAU,EACb,EAAG,UAAY,EAEf,EAAa,UAAU,CAAE,MAAK,UAAS,CAAC,EAExC,EAAI,YAAY,EAAU,cAAc,EACxC,EAAI,SAAS,QAAQ,kBAAmB,CACvC,WACA,KAAM,EAAG,SACT,GAAI,EAAG,QACP,aAAc,EAAG,WAAW,EAC7B,CAAgC,EAEzB,GAcD,SAAS,CAAY,CAC3B,EACA,EACA,EACU,CACV,IAAM,EAAK,EAAI,aAAa,EAAU,cAAc,EACpD,GAAI,CAAC,EAAI,MAAO,GAChB,OAAO,EAAkB,EAAK,EAAU,EAAI,CAAW,EAYjD,SAAS,CAAS,CACxB,EACA,EACA,EACU,CACV,IAAM,EAAK,EAAI,aAAa,EAAU,cAAc,EACpD,GAAI,CAAC,EAAI,MAAO,GAGhB,IAAM,EADS,EAAG,WAAW,OACA,EAAG,SAChC,GAAI,CAAC,GAAe,GAAI,MAAO,GAE/B,IAAM,EAAU,EAAc,GAAG,GACjC,GAAI,IAAY,OAAW,MAAO,GAElC,GAAI,OAAO,IAAY,SACtB,OAAO,EAAkB,EAAK,EAAU,EAAI,CAAO,EAGpD,GAAI,CAAC,EAAQ,MAAM,CAAE,MAAK,UAAS,CAAC,EAAG,MAAO,GAC9C,OAAO,EAAkB,EAAK,EAAU,EAAI,EAAQ,MAAM,EAUpD,SAAS,CAAoB,CACnC,EACA,EACqB,CAErB,OADW,EAAI,aAAa,EAAU,cAAc,GACzC,QAiBL,SAAS,CAA8F,CAAC,EAAoC,CAClJ,MAAO,CACN,mBAAoB,CACrB,EAwCM,SAAS,CAAsF,CACrG,EACC,CACD,IACC,cAAc,eACd,WAAW,EACX,QAAQ,UACL,GAAW,CAAC,EAEhB,OAAO,EAAa,cAAc,EAChC,mBAAkD,EAClD,eAA0C,EAC1C,WAAmC,EACnC,WAAc,EACd,QAAQ,CAAC,IAAU,CACnB,EACE,UAAU,sBAAsB,EAChC,YAAY,CAAQ,EACpB,QAAQ,CAAK,EACb,QAAQ,CAAW,EACnB,SAAS,WAAY,CACrB,KAAM,CAAC,cAAc,CACtB,CAAC,EACA,iBAAiB,WAAY,EAAG,SAAQ,SAAU,CAClD,IAAM,EAAK,EAAO,WAAW,aACvB,EAAS,EAAG,WAAW,OACvB,EAA2B,EACjC,EAAO,EAAG,UAAU,UAAU,CAAE,IAAK,EAAO,SAAU,EAAO,EAAG,CAAC,EACjE,EACA,WAAW,EAAG,UAAS,KAAI,SAAU,CAGrC,IAAM,EAAU,CAAE,IADe,EACH,SAAU,EAAG,GAAI,CAAE,EAEjD,QAAW,KAAU,EAAQ,SAAU,CACtC,IAAM,EAAK,EAAO,WAAW,aACvB,EAAS,EAAG,WAAW,OAE7B,EAAQ,SAAW,EAAO,GAC1B,EAAQ,GAAK,EAGb,EAAG,WAAa,EAGhB,EAAO,EAAG,UAAU,WAAW,CAAO,EAGtC,IAAM,EAAgB,EAAO,EAAG,SAChC,GAAI,GAAe,aAClB,QAAW,KAAc,EAAc,YACtC,GAAI,EAAW,MAAM,CAAO,EAAG,CAC9B,EAAkB,EAAQ,IAAK,EAAO,GAAI,EAAI,EAAW,MAAM,EAC/D,SAKJ,EACF",
8
- "debugId": "6ADF9D5235D78F5264756E2164756E21",
7
+ "mappings": "4cAUA,uBAAS,kBAuIF,SAAS,CAAoC,CACnD,EACA,EAC4B,CAC5B,OAAO,OAAO,OAAO,CACpB,KACA,QAAS,EAAO,QAChB,OAAQ,OAAO,OAAO,EAAO,MAAM,CACpC,CAAC,EAkBK,SAAS,CAAoC,CACnD,EACA,EACsD,CACtD,IAAM,EAAU,GAAS,SAAW,EAAW,QAC/C,MAAO,CACN,aAAc,CACb,aACA,QAAS,EACT,SAAU,KACV,UAAW,CACZ,CACD,EASD,SAAS,CAAiB,CACzB,EACA,EACA,EACA,EACU,CACV,IAAM,EAAS,EAAG,WAAW,OACvB,EAAgB,EAAO,EAAG,SAC1B,EAAe,EAAO,GAE5B,GAAI,CAAC,EAAc,MAAO,GAkB1B,OAhBA,GAAe,SAAS,CAAE,MAAK,UAAS,CAAC,EAEzC,EAAG,SAAW,EAAG,QACjB,EAAG,QAAU,EACb,EAAG,UAAY,EAEf,EAAa,UAAU,CAAE,MAAK,UAAS,CAAC,EAExC,EAAI,YAAY,EAAU,cAAc,EACxC,EAAI,SAAS,QAAQ,kBAAmB,CACvC,WACA,KAAM,EAAG,SACT,GAAI,EAAG,QACP,aAAc,EAAG,WAAW,EAC7B,CAAgC,EAEzB,GAcD,SAAS,CAAY,CAC3B,EACA,EACA,EACU,CACV,IAAM,EAAK,EAAI,aAAa,EAAU,cAAc,EACpD,GAAI,CAAC,EAAI,MAAO,GAChB,OAAO,EAAkB,EAAK,EAAU,EAAI,CAAW,EAYjD,SAAS,CAAS,CACxB,EACA,EACA,EACU,CACV,IAAM,EAAK,EAAI,aAAa,EAAU,cAAc,EACpD,GAAI,CAAC,EAAI,MAAO,GAGhB,IAAM,EADS,EAAG,WAAW,OACA,EAAG,SAChC,GAAI,CAAC,GAAe,GAAI,MAAO,GAE/B,IAAM,EAAU,EAAc,GAAG,GACjC,GAAI,IAAY,OAAW,MAAO,GAElC,GAAI,OAAO,IAAY,SACtB,OAAO,EAAkB,EAAK,EAAU,EAAI,CAAO,EAGpD,GAAI,CAAC,EAAQ,MAAM,CAAE,MAAK,UAAS,CAAC,EAAG,MAAO,GAC9C,OAAO,EAAkB,EAAK,EAAU,EAAI,EAAQ,MAAM,EAUpD,SAAS,CAAoB,CACnC,EACA,EACqB,CAErB,OADW,EAAI,aAAa,EAAU,cAAc,GACzC,QAiBL,SAAS,CAA8F,CAAC,EAAoC,CAClJ,MAAO,CACN,mBAAoB,CACrB,EAwCM,SAAS,CAAsF,CACrG,EACC,CACD,IACC,cAAc,eACd,WAAW,EACX,QAAQ,UACL,GAAW,CAAC,EAEhB,OAAO,EAAa,cAAc,EAChC,mBAAkD,EAClD,eAA0C,EAC1C,WAAmC,EACnC,WAAc,EACd,QAAQ,CAAC,IAAU,CACnB,EACE,UAAU,sBAAsB,EAChC,YAAY,CAAQ,EACpB,QAAQ,CAAK,EACb,QAAQ,CAAW,EACnB,SAAS,WAAY,CACrB,KAAM,CAAC,cAAc,CACtB,CAAC,EACA,iBAAiB,WAAY,EAAG,SAAQ,SAAU,CAClD,IAAM,EAAK,EAAO,WAAW,aACvB,EAAS,EAAG,WAAW,OACvB,EAA2B,EACjC,EAAO,EAAG,UAAU,UAAU,CAAE,IAAK,EAAO,SAAU,EAAO,EAAG,CAAC,EACjE,EACA,WAAW,EAAG,UAAS,KAAI,SAAU,CAGrC,IAAM,EAAU,CAAE,IADe,EACH,SAAU,EAAG,GAAI,CAAE,EAEjD,QAAW,KAAU,EAAQ,SAAU,CACtC,IAAM,EAAK,EAAO,WAAW,aACvB,EAAS,EAAG,WAAW,OAE7B,EAAQ,SAAW,EAAO,GAC1B,EAAQ,GAAK,EAGb,EAAG,WAAa,EAGhB,EAAO,EAAG,UAAU,WAAW,CAAO,EAGtC,IAAM,EAAgB,EAAO,EAAG,SAChC,GAAI,GAAe,aAClB,QAAW,KAAc,EAAc,YACtC,GAAI,EAAW,MAAM,CAAO,EAAG,CAC9B,EAAkB,EAAQ,IAAK,EAAO,GAAI,EAAI,EAAW,MAAM,EAC/D,SAKJ,EACF",
8
+ "debugId": "BF20B5C1E19C9EB064756E2164756E21",
9
9
  "names": []
10
10
  }