@sadhaka/loom-engine 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +344 -0
- package/dist/animation/animation-clip.d.ts +12 -0
- package/dist/animation/animation-clip.d.ts.map +1 -0
- package/dist/animation/animation-clip.js +85 -0
- package/dist/animation/animation-clip.js.map +1 -0
- package/dist/animation/animation-state-pool.d.ts +25 -0
- package/dist/animation/animation-state-pool.d.ts.map +1 -0
- package/dist/animation/animation-state-pool.js +113 -0
- package/dist/animation/animation-state-pool.js.map +1 -0
- package/dist/asset/sprite-sheet-loader.d.ts +41 -0
- package/dist/asset/sprite-sheet-loader.d.ts.map +1 -0
- package/dist/asset/sprite-sheet-loader.js +313 -0
- package/dist/asset/sprite-sheet-loader.js.map +1 -0
- package/dist/audio/audio-bus.d.ts +43 -0
- package/dist/audio/audio-bus.d.ts.map +1 -0
- package/dist/audio/audio-bus.js +258 -0
- package/dist/audio/audio-bus.js.map +1 -0
- package/dist/combat/mob-catalog.d.ts +29 -0
- package/dist/combat/mob-catalog.d.ts.map +1 -0
- package/dist/combat/mob-catalog.js +104 -0
- package/dist/combat/mob-catalog.js.map +1 -0
- package/dist/components/health.d.ts +28 -0
- package/dist/components/health.d.ts.map +1 -0
- package/dist/components/health.js +150 -0
- package/dist/components/health.js.map +1 -0
- package/dist/components/interactable.d.ts +30 -0
- package/dist/components/interactable.d.ts.map +1 -0
- package/dist/components/interactable.js +94 -0
- package/dist/components/interactable.js.map +1 -0
- package/dist/components/particle-emitter.d.ts +62 -0
- package/dist/components/particle-emitter.d.ts.map +1 -0
- package/dist/components/particle-emitter.js +193 -0
- package/dist/components/particle-emitter.js.map +1 -0
- package/dist/components/pursue.d.ts +23 -0
- package/dist/components/pursue.d.ts.map +1 -0
- package/dist/components/pursue.js +96 -0
- package/dist/components/pursue.js.map +1 -0
- package/dist/components/ranged-attack.d.ts +44 -0
- package/dist/components/ranged-attack.d.ts.map +1 -0
- package/dist/components/ranged-attack.js +120 -0
- package/dist/components/ranged-attack.js.map +1 -0
- package/dist/components/sprite.d.ts +27 -0
- package/dist/components/sprite.d.ts.map +1 -0
- package/dist/components/sprite.js +122 -0
- package/dist/components/sprite.js.map +1 -0
- package/dist/components/transform.d.ts +30 -0
- package/dist/components/transform.d.ts.map +1 -0
- package/dist/components/transform.js +150 -0
- package/dist/components/transform.js.map +1 -0
- package/dist/director/director-bridge.d.ts +22 -0
- package/dist/director/director-bridge.d.ts.map +1 -0
- package/dist/director/director-bridge.js +23 -0
- package/dist/director/director-bridge.js.map +1 -0
- package/dist/director/director-encounter-system.d.ts +21 -0
- package/dist/director/director-encounter-system.d.ts.map +1 -0
- package/dist/director/director-encounter-system.js +128 -0
- package/dist/director/director-encounter-system.js.map +1 -0
- package/dist/director/director-system.d.ts +19 -0
- package/dist/director/director-system.d.ts.map +1 -0
- package/dist/director/director-system.js +179 -0
- package/dist/director/director-system.js.map +1 -0
- package/dist/director/event-envelope.d.ts +144 -0
- package/dist/director/event-envelope.d.ts.map +1 -0
- package/dist/director/event-envelope.js +108 -0
- package/dist/director/event-envelope.js.map +1 -0
- package/dist/director/knot-context-resource.d.ts +25 -0
- package/dist/director/knot-context-resource.d.ts.map +1 -0
- package/dist/director/knot-context-resource.js +152 -0
- package/dist/director/knot-context-resource.js.map +1 -0
- package/dist/director/mock-director-bridge.d.ts +18 -0
- package/dist/director/mock-director-bridge.d.ts.map +1 -0
- package/dist/director/mock-director-bridge.js +75 -0
- package/dist/director/mock-director-bridge.js.map +1 -0
- package/dist/director/snapshot-recovery.d.ts +37 -0
- package/dist/director/snapshot-recovery.d.ts.map +1 -0
- package/dist/director/snapshot-recovery.js +180 -0
- package/dist/director/snapshot-recovery.js.map +1 -0
- package/dist/director/sse-director-bridge.d.ts +42 -0
- package/dist/director/sse-director-bridge.d.ts.map +1 -0
- package/dist/director/sse-director-bridge.js +280 -0
- package/dist/director/sse-director-bridge.js.map +1 -0
- package/dist/engine.d.ts +25 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +166 -0
- package/dist/engine.js.map +1 -0
- package/dist/entity.d.ts +17 -0
- package/dist/entity.d.ts.map +1 -0
- package/dist/entity.js +77 -0
- package/dist/entity.js.map +1 -0
- package/dist/index.d.ts +85 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +64 -0
- package/dist/index.js.map +1 -0
- package/dist/input/input-manager.d.ts +91 -0
- package/dist/input/input-manager.d.ts.map +1 -0
- package/dist/input/input-manager.js +349 -0
- package/dist/input/input-manager.js.map +1 -0
- package/dist/input/tap-to-walk.d.ts +27 -0
- package/dist/input/tap-to-walk.d.ts.map +1 -0
- package/dist/input/tap-to-walk.js +118 -0
- package/dist/input/tap-to-walk.js.map +1 -0
- package/dist/input/virtual-dpad.d.ts +34 -0
- package/dist/input/virtual-dpad.d.ts.map +1 -0
- package/dist/input/virtual-dpad.js +267 -0
- package/dist/input/virtual-dpad.js.map +1 -0
- package/dist/renderer/camera.d.ts +26 -0
- package/dist/renderer/camera.d.ts.map +1 -0
- package/dist/renderer/camera.js +39 -0
- package/dist/renderer/camera.js.map +1 -0
- package/dist/renderer/canvas2d-device.d.ts +26 -0
- package/dist/renderer/canvas2d-device.d.ts.map +1 -0
- package/dist/renderer/canvas2d-device.js +252 -0
- package/dist/renderer/canvas2d-device.js.map +1 -0
- package/dist/renderer/graphics-device.d.ts +36 -0
- package/dist/renderer/graphics-device.d.ts.map +1 -0
- package/dist/renderer/graphics-device.js +12 -0
- package/dist/renderer/graphics-device.js.map +1 -0
- package/dist/renderer/iso-projection.d.ts +11 -0
- package/dist/renderer/iso-projection.d.ts.map +1 -0
- package/dist/renderer/iso-projection.js +59 -0
- package/dist/renderer/iso-projection.js.map +1 -0
- package/dist/resources.d.ts +28 -0
- package/dist/resources.d.ts.map +1 -0
- package/dist/resources.js +53 -0
- package/dist/resources.js.map +1 -0
- package/dist/system.d.ts +14 -0
- package/dist/system.d.ts.map +1 -0
- package/dist/system.js +25 -0
- package/dist/system.js.map +1 -0
- package/dist/systems/animation-system.d.ts +8 -0
- package/dist/systems/animation-system.d.ts.map +1 -0
- package/dist/systems/animation-system.js +77 -0
- package/dist/systems/animation-system.js.map +1 -0
- package/dist/systems/attack-system.d.ts +17 -0
- package/dist/systems/attack-system.d.ts.map +1 -0
- package/dist/systems/attack-system.js +94 -0
- package/dist/systems/attack-system.js.map +1 -0
- package/dist/systems/damage-system.d.ts +18 -0
- package/dist/systems/damage-system.d.ts.map +1 -0
- package/dist/systems/damage-system.js +77 -0
- package/dist/systems/damage-system.js.map +1 -0
- package/dist/systems/input-system.d.ts +7 -0
- package/dist/systems/input-system.d.ts.map +1 -0
- package/dist/systems/input-system.js +27 -0
- package/dist/systems/input-system.js.map +1 -0
- package/dist/systems/interaction-system.d.ts +23 -0
- package/dist/systems/interaction-system.d.ts.map +1 -0
- package/dist/systems/interaction-system.js +120 -0
- package/dist/systems/interaction-system.js.map +1 -0
- package/dist/systems/particle-emitter-system.d.ts +8 -0
- package/dist/systems/particle-emitter-system.d.ts.map +1 -0
- package/dist/systems/particle-emitter-system.js +161 -0
- package/dist/systems/particle-emitter-system.js.map +1 -0
- package/dist/systems/particle-render-system.d.ts +7 -0
- package/dist/systems/particle-render-system.d.ts.map +1 -0
- package/dist/systems/particle-render-system.js +53 -0
- package/dist/systems/particle-render-system.js.map +1 -0
- package/dist/systems/particle-simulation-system.d.ts +8 -0
- package/dist/systems/particle-simulation-system.d.ts.map +1 -0
- package/dist/systems/particle-simulation-system.js +45 -0
- package/dist/systems/particle-simulation-system.js.map +1 -0
- package/dist/systems/projectile-render-system.d.ts +7 -0
- package/dist/systems/projectile-render-system.d.ts.map +1 -0
- package/dist/systems/projectile-render-system.js +35 -0
- package/dist/systems/projectile-render-system.js.map +1 -0
- package/dist/systems/projectile-system.d.ts +7 -0
- package/dist/systems/projectile-system.d.ts.map +1 -0
- package/dist/systems/projectile-system.js +114 -0
- package/dist/systems/projectile-system.js.map +1 -0
- package/dist/systems/pursue-system.d.ts +7 -0
- package/dist/systems/pursue-system.d.ts.map +1 -0
- package/dist/systems/pursue-system.js +83 -0
- package/dist/systems/pursue-system.js.map +1 -0
- package/dist/systems/ranged-attack-system.d.ts +7 -0
- package/dist/systems/ranged-attack-system.d.ts.map +1 -0
- package/dist/systems/ranged-attack-system.js +98 -0
- package/dist/systems/ranged-attack-system.js.map +1 -0
- package/dist/systems/sprite-render-system.d.ts +9 -0
- package/dist/systems/sprite-render-system.d.ts.map +1 -0
- package/dist/systems/sprite-render-system.js +108 -0
- package/dist/systems/sprite-render-system.js.map +1 -0
- package/dist/systems/veil-budget-system.d.ts +7 -0
- package/dist/systems/veil-budget-system.d.ts.map +1 -0
- package/dist/systems/veil-budget-system.js +37 -0
- package/dist/systems/veil-budget-system.js.map +1 -0
- package/dist/util/color.d.ts +19 -0
- package/dist/util/color.d.ts.map +1 -0
- package/dist/util/color.js +45 -0
- package/dist/util/color.js.map +1 -0
- package/dist/util/math.d.ts +26 -0
- package/dist/util/math.d.ts.map +1 -0
- package/dist/util/math.js +47 -0
- package/dist/util/math.js.map +1 -0
- package/dist/util/typed-arrays.d.ts +7 -0
- package/dist/util/typed-arrays.d.ts.map +1 -0
- package/dist/util/typed-arrays.js +42 -0
- package/dist/util/typed-arrays.js.map +1 -0
- package/dist/vfx/particle-pool.d.ts +61 -0
- package/dist/vfx/particle-pool.d.ts.map +1 -0
- package/dist/vfx/particle-pool.js +204 -0
- package/dist/vfx/particle-pool.js.map +1 -0
- package/dist/vfx/projectile-pool.d.ts +56 -0
- package/dist/vfx/projectile-pool.d.ts.map +1 -0
- package/dist/vfx/projectile-pool.js +157 -0
- package/dist/vfx/projectile-pool.js.map +1 -0
- package/dist/world.d.ts +23 -0
- package/dist/world.d.ts.map +1 -0
- package/dist/world.js +101 -0
- package/dist/world.js.map +1 -0
- package/dist/zone/zone-catalog.d.ts +17 -0
- package/dist/zone/zone-catalog.d.ts.map +1 -0
- package/dist/zone/zone-catalog.js +116 -0
- package/dist/zone/zone-catalog.js.map +1 -0
- package/dist/zone/zone-state.d.ts +18 -0
- package/dist/zone/zone-state.d.ts.map +1 -0
- package/dist/zone/zone-state.js +52 -0
- package/dist/zone/zone-state.js.map +1 -0
- package/package.json +56 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// InputSystem - bridges DOM input accumulator into a frame-coherent
|
|
2
|
+
// resource. Runs as the FIRST registered system in PHASE_INPUT, so
|
|
3
|
+
// every subsequent phase reads the same snapshot.
|
|
4
|
+
//
|
|
5
|
+
// The system does two things each tick:
|
|
6
|
+
// 1. Calls inputManager.beginFrame() so per-frame transient state
|
|
7
|
+
// (pressed-this-frame / released-this-frame / wheel delta /
|
|
8
|
+
// touchesStarted / touchesEnded) snapshots from the accumulator
|
|
9
|
+
// 2. Writes the resulting InputSnapshot into the world's resource
|
|
10
|
+
// registry under RESOURCE_INPUT
|
|
11
|
+
//
|
|
12
|
+
// Other systems (camera pan, click-to-spawn, etc.) read from
|
|
13
|
+
// world.resources.get<InputSnapshot>(RESOURCE_INPUT). The snapshot is
|
|
14
|
+
// replaced each tick so consumers should not hold a reference across
|
|
15
|
+
// frames.
|
|
16
|
+
import { RESOURCE_INPUT_MANAGER, RESOURCE_INPUT, } from '../input/input-manager.js';
|
|
17
|
+
export class InputSystem {
|
|
18
|
+
name = 'input';
|
|
19
|
+
update(world, _dt) {
|
|
20
|
+
const manager = world.resources.get(RESOURCE_INPUT_MANAGER);
|
|
21
|
+
if (!manager)
|
|
22
|
+
return;
|
|
23
|
+
manager.beginFrame();
|
|
24
|
+
world.resources.set(RESOURCE_INPUT, manager.snapshot());
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=input-system.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input-system.js","sourceRoot":"","sources":["../../src/systems/input-system.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,mEAAmE;AACnE,kDAAkD;AAClD,EAAE;AACF,wCAAwC;AACxC,oEAAoE;AACpE,iEAAiE;AACjE,qEAAqE;AACrE,oEAAoE;AACpE,qCAAqC;AACrC,EAAE;AACF,6DAA6D;AAC7D,sEAAsE;AACtE,qEAAqE;AACrE,UAAU;AAIV,OAAO,EAEL,sBAAsB,EACtB,cAAc,GACf,MAAM,2BAA2B,CAAC;AAEnC,MAAM,OAAO,WAAW;IACb,IAAI,GAAW,OAAO,CAAC;IAEhC,MAAM,CAAC,KAAY,EAAE,GAAW;QAC9B,MAAM,OAAO,GAAG,KAAK,CAAC,SAAS,CAAC,GAAG,CAAe,sBAAsB,CAAC,CAAC;QAC1E,IAAI,CAAC,OAAO;YAAE,OAAO;QACrB,OAAO,CAAC,UAAU,EAAE,CAAC;QACrB,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,cAAc,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC1D,CAAC;CACF"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { System } from '../system.js';
|
|
2
|
+
import type { World } from '../world.js';
|
|
3
|
+
import { type InteractableKind } from '../components/interactable.js';
|
|
4
|
+
import { type EntityId } from '../entity.js';
|
|
5
|
+
export interface LastInteractionResource {
|
|
6
|
+
entityIndex: number;
|
|
7
|
+
atFrame: number;
|
|
8
|
+
kind: InteractableKind;
|
|
9
|
+
payload: string;
|
|
10
|
+
prompt: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function createLastInteraction(): LastInteractionResource;
|
|
13
|
+
export declare const RESOURCE_LAST_INTERACTION = "last_interaction";
|
|
14
|
+
export interface InteractionSystemOptions {
|
|
15
|
+
player: EntityId;
|
|
16
|
+
}
|
|
17
|
+
export declare class InteractionSystem implements System {
|
|
18
|
+
private opts;
|
|
19
|
+
readonly name: string;
|
|
20
|
+
constructor(opts: InteractionSystemOptions);
|
|
21
|
+
update(world: World, _dt: number): void;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=interaction-system.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interaction-system.d.ts","sourceRoot":"","sources":["../../src/systems/interaction-system.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAGzC,OAAO,EAIL,KAAK,gBAAgB,EACtB,MAAM,+BAA+B,CAAC;AASvC,OAAO,EAAE,KAAK,QAAQ,EAA2B,MAAM,cAAc,CAAC;AAItE,MAAM,WAAW,uBAAuB;IAGtC,WAAW,EAAE,MAAM,CAAC;IAGpB,OAAO,EAAE,MAAM,CAAC;IAEhB,IAAI,EAAE,gBAAgB,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,qBAAqB,IAAI,uBAAuB,CAQ/D;AAED,eAAO,MAAM,yBAAyB,qBAAqB,CAAC;AAE5D,MAAM,WAAW,wBAAwB;IAGvC,MAAM,EAAE,QAAQ,CAAC;CAClB;AAED,qBAAa,iBAAkB,YAAW,MAAM;IAGlC,OAAO,CAAC,IAAI;IAFxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAiB;gBAElB,IAAI,EAAE,wBAAwB;IAElD,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;CAgFxC"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// InteractionSystem - detects player interactions with Interactable
|
|
2
|
+
// entities (NPCs, portals, lore stones).
|
|
3
|
+
//
|
|
4
|
+
// Two trigger paths:
|
|
5
|
+
// 1. Click on or near an interactable entity (within its radius)
|
|
6
|
+
// 2. Press 'KeyE' or 'Enter' while in range of any interactable
|
|
7
|
+
// (per CLAUDE.md hotkey lock list: E + Enter open NPC dialog)
|
|
8
|
+
//
|
|
9
|
+
// Output: writes the most recently triggered interaction to the
|
|
10
|
+
// LastInteractionResource. Gameplay code reads this to show dialog,
|
|
11
|
+
// trigger zone transitions, etc. Cleared after one tick of being
|
|
12
|
+
// read so each interaction fires exactly once.
|
|
13
|
+
import { POOL_TRANSFORM } from '../world.js';
|
|
14
|
+
import { POOL_INTERACTABLE, INTERACTABLE_FLAG_ACTIVE, } from '../components/interactable.js';
|
|
15
|
+
import { RESOURCE_INPUT, } from '../input/input-manager.js';
|
|
16
|
+
import { RESOURCE_CAMERA } from '../resources.js';
|
|
17
|
+
import { isoToTile } from '../renderer/iso-projection.js';
|
|
18
|
+
import { vec2 } from '../util/math.js';
|
|
19
|
+
import { makeEntity, entityIndex } from '../entity.js';
|
|
20
|
+
const SCRATCH_TILE = vec2(0, 0);
|
|
21
|
+
export function createLastInteraction() {
|
|
22
|
+
return {
|
|
23
|
+
entityIndex: -1,
|
|
24
|
+
atFrame: -1,
|
|
25
|
+
kind: 'npc',
|
|
26
|
+
payload: '',
|
|
27
|
+
prompt: '',
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export const RESOURCE_LAST_INTERACTION = 'last_interaction';
|
|
31
|
+
export class InteractionSystem {
|
|
32
|
+
opts;
|
|
33
|
+
name = 'interaction';
|
|
34
|
+
constructor(opts) {
|
|
35
|
+
this.opts = opts;
|
|
36
|
+
}
|
|
37
|
+
update(world, _dt) {
|
|
38
|
+
const input = world.resources.get(RESOURCE_INPUT);
|
|
39
|
+
if (!input)
|
|
40
|
+
return;
|
|
41
|
+
const camera = world.resources.get(RESOURCE_CAMERA);
|
|
42
|
+
const transforms = world.getPool(POOL_TRANSFORM);
|
|
43
|
+
const interactables = world.getPool(POOL_INTERACTABLE);
|
|
44
|
+
const last = world.resources.get(RESOURCE_LAST_INTERACTION);
|
|
45
|
+
if (!camera || !transforms || !interactables || !last)
|
|
46
|
+
return;
|
|
47
|
+
const playerIdx = entityIndex(this.opts.player);
|
|
48
|
+
const px = transforms.x[playerIdx] ?? 0;
|
|
49
|
+
const py = transforms.y[playerIdx] ?? 0;
|
|
50
|
+
// Trigger paths:
|
|
51
|
+
// - Left click anywhere -> find interactable nearest the click
|
|
52
|
+
// point that is within its own radius
|
|
53
|
+
// - 'KeyE' or 'Enter' pressedThisFrame -> find nearest
|
|
54
|
+
// interactable to the player that is within its own radius
|
|
55
|
+
const leftClicked = (input.pointerPressedThisFrame & 1) !== 0;
|
|
56
|
+
const eKey = input.keysPressedThisFrame.has('KeyE') || input.keysPressedThisFrame.has('Enter');
|
|
57
|
+
if (!leftClicked && !eKey)
|
|
58
|
+
return;
|
|
59
|
+
let probeX;
|
|
60
|
+
let probeY;
|
|
61
|
+
if (leftClicked) {
|
|
62
|
+
// Convert click pixel coords -> world iso -> tile.
|
|
63
|
+
const worldIsoX = (input.pointer.x - camera.viewportWidth / 2) / camera.zoom + camera.centerX;
|
|
64
|
+
const worldIsoY = (input.pointer.y - camera.viewportHeight / 2) / camera.zoom + camera.centerY;
|
|
65
|
+
isoToTile(worldIsoX, worldIsoY, SCRATCH_TILE);
|
|
66
|
+
probeX = SCRATCH_TILE.x;
|
|
67
|
+
probeY = SCRATCH_TILE.y;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
// E / Enter: probe at player's position.
|
|
71
|
+
probeX = px;
|
|
72
|
+
probeY = py;
|
|
73
|
+
}
|
|
74
|
+
// Find the nearest interactable whose radius contains the probe.
|
|
75
|
+
const hwm = interactables.getHighWaterMark();
|
|
76
|
+
let bestIdx = -1;
|
|
77
|
+
let bestDist = Infinity;
|
|
78
|
+
for (let i = 1; i < hwm; i++) {
|
|
79
|
+
if (i === playerIdx)
|
|
80
|
+
continue;
|
|
81
|
+
const f = interactables.flags[i] ?? 0;
|
|
82
|
+
if ((f & INTERACTABLE_FLAG_ACTIVE) === 0)
|
|
83
|
+
continue;
|
|
84
|
+
const tx = transforms.x[i] ?? 0;
|
|
85
|
+
const ty = transforms.y[i] ?? 0;
|
|
86
|
+
const dx = tx - probeX;
|
|
87
|
+
const dy = ty - probeY;
|
|
88
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
89
|
+
const radius = interactables.radius[i] ?? 0;
|
|
90
|
+
if (dist > radius)
|
|
91
|
+
continue;
|
|
92
|
+
// For E/Enter: also require the player itself be within radius
|
|
93
|
+
// (so the player can't trigger an NPC across the map by pressing
|
|
94
|
+
// E while standing right next to a different one).
|
|
95
|
+
if (eKey) {
|
|
96
|
+
const playerDx = tx - px;
|
|
97
|
+
const playerDy = ty - py;
|
|
98
|
+
if (Math.sqrt(playerDx * playerDx + playerDy * playerDy) > radius)
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (dist < bestDist) {
|
|
102
|
+
bestDist = dist;
|
|
103
|
+
bestIdx = i;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (bestIdx < 0)
|
|
107
|
+
return;
|
|
108
|
+
last.entityIndex = bestIdx;
|
|
109
|
+
last.kind = interactables.kind[bestIdx] ?? 'npc';
|
|
110
|
+
last.payload = interactables.payload[bestIdx] ?? '';
|
|
111
|
+
last.prompt = interactables.prompt[bestIdx] ?? '';
|
|
112
|
+
// atFrame is read by the gameplay handler to detect "new" - we
|
|
113
|
+
// bump it to the entity index plus a tick salt so successive
|
|
114
|
+
// identical-target reads still register as new. Simpler: any
|
|
115
|
+
// monotonic increment works.
|
|
116
|
+
last.atFrame = (last.atFrame ?? 0) + 1;
|
|
117
|
+
void makeEntity; // imported but only used by callers via index
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=interaction-system.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interaction-system.js","sourceRoot":"","sources":["../../src/systems/interaction-system.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { System } from '../system.js';
|
|
2
|
+
import type { World } from '../world.js';
|
|
3
|
+
export declare const POOL_EMITTER = "emitter";
|
|
4
|
+
export declare class ParticleEmitterSystem implements System {
|
|
5
|
+
readonly name: string;
|
|
6
|
+
update(world: World, dt: number): void;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=particle-emitter-system.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"particle-emitter-system.d.ts","sourceRoot":"","sources":["../../src/systems/particle-emitter-system.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAOzC,eAAO,MAAM,YAAY,YAAY,CAAC;AAkEtC,qBAAa,qBAAsB,YAAW,MAAM;IAClD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAsB;IAE3C,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;CA0EvC"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// ParticleEmitterSystem - reads ECS entities that have BOTH a
|
|
2
|
+
// Transform AND a ParticleEmitter, computes how many particles to
|
|
3
|
+
// spawn this tick (continuous rate + pending burst), and pushes
|
|
4
|
+
// each spawn into the shared ParticlePool.
|
|
5
|
+
//
|
|
6
|
+
// Runs in PHASE_LOGIC. Simulation happens in PHASE_PHYSICS afterward,
|
|
7
|
+
// so a particle spawned this tick gets its first physics step in the
|
|
8
|
+
// same frame.
|
|
9
|
+
//
|
|
10
|
+
// Spawn math:
|
|
11
|
+
// - Continuous: emitter.rate * dt + emitter.spawnCarry, floor that
|
|
12
|
+
// for the count this tick, save the fractional part as new carry
|
|
13
|
+
// - Burst: emit min(burstRemaining, available-budget) all at once
|
|
14
|
+
// - Direction: sample a random direction inside the cone defined
|
|
15
|
+
// by (dirX, dirY, dirZ) and coneRadians half-angle
|
|
16
|
+
// - Speed: uniform random in [speedMin, speedMax]
|
|
17
|
+
//
|
|
18
|
+
// The pool's maxParticles cap is enforced inside ParticlePool.spawn;
|
|
19
|
+
// when it returns -1 we stop emitting from this entity for this tick
|
|
20
|
+
// and the missed spawns are NOT carried over (a budget under-fire is
|
|
21
|
+
// a render-budget signal, not a debt).
|
|
22
|
+
import { POOL_TRANSFORM } from '../world.js';
|
|
23
|
+
import { EMITTER_FLAG_ACTIVE } from '../components/particle-emitter.js';
|
|
24
|
+
import { POOL_PARTICLE } from './particle-simulation-system.js';
|
|
25
|
+
export const POOL_EMITTER = 'emitter';
|
|
26
|
+
// Sample a random unit direction inside a cone of half-angle
|
|
27
|
+
// `coneHalf` around the axis (ax, ay, az). For coneHalf = 0 the
|
|
28
|
+
// returned vector equals the axis. For coneHalf = PI it's a random
|
|
29
|
+
// direction on the full sphere.
|
|
30
|
+
//
|
|
31
|
+
// Algorithm: pick a random angle phi in [0, coneHalf], a random
|
|
32
|
+
// azimuth theta in [0, 2*PI], and rotate that into the axis-aligned
|
|
33
|
+
// frame. We use a small-angle approximation that's exact for our
|
|
34
|
+
// 2.5D usage (the axis is mostly axis-aligned in iso space).
|
|
35
|
+
function sampleConeDirection(ax, ay, az, coneHalf, out) {
|
|
36
|
+
if (coneHalf <= 0) {
|
|
37
|
+
out.x = ax;
|
|
38
|
+
out.y = ay;
|
|
39
|
+
out.z = az;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// Random angle from axis (cosine-weighted for uniform sphere
|
|
43
|
+
// coverage as cone widens).
|
|
44
|
+
const cosLimit = Math.cos(coneHalf);
|
|
45
|
+
const cosAngle = cosLimit + (1 - cosLimit) * Math.random();
|
|
46
|
+
const sinAngle = Math.sqrt(Math.max(0, 1 - cosAngle * cosAngle));
|
|
47
|
+
const azimuth = Math.random() * Math.PI * 2;
|
|
48
|
+
// Build a local frame around the axis.
|
|
49
|
+
const len = Math.sqrt(ax * ax + ay * ay + az * az) || 1;
|
|
50
|
+
const aux = ax / len;
|
|
51
|
+
const auy = ay / len;
|
|
52
|
+
const auz = az / len;
|
|
53
|
+
// Find a perpendicular to (aux, auy, auz). Use the smaller axis
|
|
54
|
+
// as the helper to avoid degenerate cross products.
|
|
55
|
+
let hx, hy, hz;
|
|
56
|
+
if (Math.abs(aux) < 0.9) {
|
|
57
|
+
hx = 1;
|
|
58
|
+
hy = 0;
|
|
59
|
+
hz = 0;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
hx = 0;
|
|
63
|
+
hy = 1;
|
|
64
|
+
hz = 0;
|
|
65
|
+
}
|
|
66
|
+
// First perpendicular = axis x helper
|
|
67
|
+
let p1x = auy * hz - auz * hy;
|
|
68
|
+
let p1y = auz * hx - aux * hz;
|
|
69
|
+
let p1z = aux * hy - auy * hx;
|
|
70
|
+
const p1Len = Math.sqrt(p1x * p1x + p1y * p1y + p1z * p1z) || 1;
|
|
71
|
+
p1x /= p1Len;
|
|
72
|
+
p1y /= p1Len;
|
|
73
|
+
p1z /= p1Len;
|
|
74
|
+
// Second perpendicular = axis x p1
|
|
75
|
+
const p2x = auy * p1z - auz * p1y;
|
|
76
|
+
const p2y = auz * p1x - aux * p1z;
|
|
77
|
+
const p2z = aux * p1y - auy * p1x;
|
|
78
|
+
const cosA = Math.cos(azimuth);
|
|
79
|
+
const sinA = Math.sin(azimuth);
|
|
80
|
+
// Combine: result = cosAngle*axis + sinAngle*(cosA*p1 + sinA*p2)
|
|
81
|
+
out.x = cosAngle * aux + sinAngle * (cosA * p1x + sinA * p2x);
|
|
82
|
+
out.y = cosAngle * auy + sinAngle * (cosA * p1y + sinA * p2y);
|
|
83
|
+
out.z = cosAngle * auz + sinAngle * (cosA * p1z + sinA * p2z);
|
|
84
|
+
}
|
|
85
|
+
const SCRATCH_DIR = { x: 0, y: 0, z: 0 };
|
|
86
|
+
export class ParticleEmitterSystem {
|
|
87
|
+
name = 'particle-emitter';
|
|
88
|
+
update(world, dt) {
|
|
89
|
+
const transforms = world.getPool(POOL_TRANSFORM);
|
|
90
|
+
const emitters = world.getPool(POOL_EMITTER);
|
|
91
|
+
const particles = world.getPool(POOL_PARTICLE);
|
|
92
|
+
if (!transforms || !emitters || !particles)
|
|
93
|
+
return;
|
|
94
|
+
const hwm = Math.min(transforms.getHighWaterMark(), emitters.getHighWaterMark());
|
|
95
|
+
for (let i = 1; i < hwm; i++) { // index 0 is NULL_ENTITY
|
|
96
|
+
const flags = emitters.flags[i] ?? 0;
|
|
97
|
+
if ((flags & EMITTER_FLAG_ACTIVE) === 0)
|
|
98
|
+
continue;
|
|
99
|
+
const x = transforms.x[i] ?? 0;
|
|
100
|
+
const y = transforms.y[i] ?? 0;
|
|
101
|
+
const z = transforms.z[i] ?? 0;
|
|
102
|
+
// Total spawns this tick = continuous fractional accumulator
|
|
103
|
+
// + pending burst.
|
|
104
|
+
const carry = (emitters.spawnCarry[i] ?? 0) + (emitters.rate[i] ?? 0) * dt;
|
|
105
|
+
const continuousCount = Math.floor(carry);
|
|
106
|
+
emitters.spawnCarry[i] = carry - continuousCount;
|
|
107
|
+
const burstCount = emitters.burstRemaining[i] ?? 0;
|
|
108
|
+
const total = continuousCount + burstCount;
|
|
109
|
+
if (total === 0)
|
|
110
|
+
continue;
|
|
111
|
+
let spawnedFromBurst = 0;
|
|
112
|
+
const additive = (flags & 0x02) !== 0;
|
|
113
|
+
const dirX = emitters.dirX[i] ?? 0;
|
|
114
|
+
const dirY = emitters.dirY[i] ?? -1;
|
|
115
|
+
const dirZ = emitters.dirZ[i] ?? 0;
|
|
116
|
+
const coneHalf = emitters.coneRadians[i] ?? 0;
|
|
117
|
+
const speedMin = emitters.speedMin[i] ?? 0;
|
|
118
|
+
const speedMax = emitters.speedMax[i] ?? speedMin;
|
|
119
|
+
const ax = emitters.ax[i] ?? 0;
|
|
120
|
+
const ay = emitters.ay[i] ?? 0;
|
|
121
|
+
const az = emitters.az[i] ?? 0;
|
|
122
|
+
const life = emitters.particleLife[i] ?? 1;
|
|
123
|
+
const startSize = emitters.startSize[i] ?? 4;
|
|
124
|
+
const endSize = emitters.endSize[i] ?? startSize;
|
|
125
|
+
for (let k = 0; k < total; k++) {
|
|
126
|
+
sampleConeDirection(dirX, dirY, dirZ, coneHalf, SCRATCH_DIR);
|
|
127
|
+
const speed = speedMin + Math.random() * (speedMax - speedMin);
|
|
128
|
+
const slot = particles.spawn({
|
|
129
|
+
x, y, z,
|
|
130
|
+
vx: SCRATCH_DIR.x * speed,
|
|
131
|
+
vy: SCRATCH_DIR.y * speed,
|
|
132
|
+
vz: SCRATCH_DIR.z * speed,
|
|
133
|
+
ax, ay, az,
|
|
134
|
+
life,
|
|
135
|
+
size: startSize,
|
|
136
|
+
endSize,
|
|
137
|
+
color: {
|
|
138
|
+
r: emitters.startR[i] ?? 1,
|
|
139
|
+
g: emitters.startG[i] ?? 1,
|
|
140
|
+
b: emitters.startB[i] ?? 1,
|
|
141
|
+
a: emitters.startA[i] ?? 1,
|
|
142
|
+
},
|
|
143
|
+
endColor: {
|
|
144
|
+
r: emitters.endR[i] ?? 1,
|
|
145
|
+
g: emitters.endG[i] ?? 1,
|
|
146
|
+
b: emitters.endB[i] ?? 1,
|
|
147
|
+
a: emitters.endA[i] ?? 0,
|
|
148
|
+
},
|
|
149
|
+
additive,
|
|
150
|
+
});
|
|
151
|
+
if (slot < 0)
|
|
152
|
+
break; // budget hit
|
|
153
|
+
if (k < burstCount)
|
|
154
|
+
spawnedFromBurst++;
|
|
155
|
+
}
|
|
156
|
+
// Reduce burstRemaining only by what we actually spawned.
|
|
157
|
+
emitters.burstRemaining[i] = burstCount - spawnedFromBurst;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=particle-emitter-system.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"particle-emitter-system.js","sourceRoot":"","sources":["../../src/systems/particle-emitter-system.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { System } from '../system.js';
|
|
2
|
+
import type { World } from '../world.js';
|
|
3
|
+
export declare class ParticleRenderSystem implements System {
|
|
4
|
+
readonly name: string;
|
|
5
|
+
update(world: World, _dt: number): void;
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=particle-render-system.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"particle-render-system.d.ts","sourceRoot":"","sources":["../../src/systems/particle-render-system.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAYzC,qBAAa,oBAAqB,YAAW,MAAM;IACjD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAqB;IAE1C,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;CAwCxC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// ParticleRenderSystem - iterates the ParticlePool and submits
|
|
2
|
+
// drawParticle calls to the device.
|
|
3
|
+
//
|
|
4
|
+
// Runs in PHASE_RENDER. Registration order matters: this system
|
|
5
|
+
// should be registered AFTER SpriteRenderSystem so particles draw
|
|
6
|
+
// on top of sprites (typical "VFX above" layering). Caller can
|
|
7
|
+
// reverse the order to put particles UNDER sprites for, say, ground-
|
|
8
|
+
// level smoke trails.
|
|
9
|
+
//
|
|
10
|
+
// Particle draw order is insertion order; the pool's free-list
|
|
11
|
+
// recycles slots so the order isn't strictly back-to-front. For
|
|
12
|
+
// additive-blended particles this doesn't matter; for alpha-blended
|
|
13
|
+
// the visual difference is rarely noticeable at typical particle
|
|
14
|
+
// sizes. Phase 4 ships without sort to keep the hot loop tight;
|
|
15
|
+
// Phase 5+ may add a sorted-render variant if profiling demands.
|
|
16
|
+
import { PARTICLE_FLAG_ALIVE, PARTICLE_FLAG_ADDITIVE } from '../vfx/particle-pool.js';
|
|
17
|
+
import { RESOURCE_DEVICE, RESOURCE_CAMERA, } from '../resources.js';
|
|
18
|
+
import { POOL_PARTICLE } from './particle-simulation-system.js';
|
|
19
|
+
const SCRATCH_COLOR = { r: 1, g: 1, b: 1, a: 1 };
|
|
20
|
+
export class ParticleRenderSystem {
|
|
21
|
+
name = 'particle-render';
|
|
22
|
+
update(world, _dt) {
|
|
23
|
+
const pool = world.getPool(POOL_PARTICLE);
|
|
24
|
+
const device = world.resources.get(RESOURCE_DEVICE);
|
|
25
|
+
const camera = world.resources.get(RESOURCE_CAMERA);
|
|
26
|
+
if (!pool || !device || !camera)
|
|
27
|
+
return;
|
|
28
|
+
if (pool.getLiveCount() === 0)
|
|
29
|
+
return;
|
|
30
|
+
device.setCamera(camera);
|
|
31
|
+
const hwm = pool.getHighWaterMark();
|
|
32
|
+
for (let i = 0; i < hwm; i++) {
|
|
33
|
+
const f = pool.flags[i] ?? 0;
|
|
34
|
+
if ((f & PARTICLE_FLAG_ALIVE) === 0)
|
|
35
|
+
continue;
|
|
36
|
+
const life = pool.life[i] ?? 0;
|
|
37
|
+
const maxLife = pool.maxLife[i] ?? 1;
|
|
38
|
+
// t = 0 at spawn, t = 1 at death.
|
|
39
|
+
const t = maxLife > 0 ? 1 - life / maxLife : 1;
|
|
40
|
+
const r = (pool.r0[i] ?? 1) + ((pool.r1[i] ?? 1) - (pool.r0[i] ?? 1)) * t;
|
|
41
|
+
const g = (pool.g0[i] ?? 1) + ((pool.g1[i] ?? 1) - (pool.g0[i] ?? 1)) * t;
|
|
42
|
+
const b = (pool.b0[i] ?? 1) + ((pool.b1[i] ?? 1) - (pool.b0[i] ?? 1)) * t;
|
|
43
|
+
const a = (pool.a0[i] ?? 1) + ((pool.a1[i] ?? 0) - (pool.a0[i] ?? 1)) * t;
|
|
44
|
+
const size = (pool.size[i] ?? 4) + ((pool.endSize[i] ?? 4) - (pool.size[i] ?? 4)) * t;
|
|
45
|
+
SCRATCH_COLOR.r = r;
|
|
46
|
+
SCRATCH_COLOR.g = g;
|
|
47
|
+
SCRATCH_COLOR.b = b;
|
|
48
|
+
SCRATCH_COLOR.a = a;
|
|
49
|
+
device.drawParticle(pool.x[i] ?? 0, pool.y[i] ?? 0, pool.z[i] ?? 0, size, SCRATCH_COLOR, (f & PARTICLE_FLAG_ADDITIVE) !== 0);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=particle-render-system.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"particle-render-system.js","sourceRoot":"","sources":["../../src/systems/particle-render-system.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,oCAAoC;AACpC,EAAE;AACF,gEAAgE;AAChE,kEAAkE;AAClE,+DAA+D;AAC/D,qEAAqE;AACrE,sBAAsB;AACtB,EAAE;AACF,+DAA+D;AAC/D,gEAAgE;AAChE,oEAAoE;AACpE,iEAAiE;AACjE,gEAAgE;AAChE,iEAAiE;AAIjE,OAAO,EAAgB,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACpG,OAAO,EACL,eAAe,EACf,eAAe,GAChB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAEhE,MAAM,aAAa,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;AAEjD,MAAM,OAAO,oBAAoB;IACtB,IAAI,GAAW,iBAAiB,CAAC;IAE1C,MAAM,CAAC,KAAY,EAAE,GAAW;QAC9B,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAe,aAAa,CAAC,CAAC;QACxD,MAAM,MAAM,GAAG,KAAK,CAAC,SAAS,CAAC,GAAG,CAAkB,eAAe,CAAC,CAAC;QACrE,MAAM,MAAM,GAAG,KAAK,CAAC,SAAS,CAAC,GAAG,CAAa,eAAe,CAAC,CAAC;QAChE,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM;YAAE,OAAO;QACxC,IAAI,IAAI,CAAC,YAAY,EAAE,KAAK,CAAC;YAAE,OAAO;QAEtC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QACzB,MAAM,GAAG,GAAG,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAEpC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;YAC7B,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YAC7B,IAAI,CAAC,CAAC,GAAG,mBAAmB,CAAC,KAAK,CAAC;gBAAE,SAAS;YAE9C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YAC/B,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACrC,kCAAkC;YAClC,MAAM,CAAC,GAAG,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;YAE/C,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YAC1E,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YAC1E,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YAC1E,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YAC1E,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YAEtF,aAAa,CAAC,CAAC,GAAG,CAAC,CAAC;YACpB,aAAa,CAAC,CAAC,GAAG,CAAC,CAAC;YACpB,aAAa,CAAC,CAAC,GAAG,CAAC,CAAC;YACpB,aAAa,CAAC,CAAC,GAAG,CAAC,CAAC;YAEpB,MAAM,CAAC,YAAY,CACjB,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EACd,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EACd,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EACd,IAAI,EACJ,aAAa,EACb,CAAC,CAAC,GAAG,sBAAsB,CAAC,KAAK,CAAC,CACnC,CAAC;QACJ,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { System } from '../system.js';
|
|
2
|
+
import type { World } from '../world.js';
|
|
3
|
+
export declare const POOL_PARTICLE = "particle";
|
|
4
|
+
export declare class ParticleSimulationSystem implements System {
|
|
5
|
+
readonly name: string;
|
|
6
|
+
update(world: World, dt: number): void;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=particle-simulation-system.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"particle-simulation-system.d.ts","sourceRoot":"","sources":["../../src/systems/particle-simulation-system.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAGzC,eAAO,MAAM,aAAa,aAAa,CAAC;AAExC,qBAAa,wBAAyB,YAAW,MAAM;IACrD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAyB;IAE9C,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;CA8BvC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// ParticleSimulationSystem - advances every live particle's state
|
|
2
|
+
// each tick. Position, velocity, life. Particles whose life drops
|
|
3
|
+
// below zero are killed and their slots returned to the free list.
|
|
4
|
+
//
|
|
5
|
+
// Runs in PHASE_PHYSICS, after the emitter system has spawned this
|
|
6
|
+
// frame's new particles (emitter runs in PHASE_LOGIC).
|
|
7
|
+
//
|
|
8
|
+
// Hot loop: walks [0, highWaterMark) of the pool, branchless on the
|
|
9
|
+
// alive flag (skip via continue), in-place mutation. No allocations
|
|
10
|
+
// per particle.
|
|
11
|
+
import { PARTICLE_FLAG_ALIVE } from '../vfx/particle-pool.js';
|
|
12
|
+
export const POOL_PARTICLE = 'particle';
|
|
13
|
+
export class ParticleSimulationSystem {
|
|
14
|
+
name = 'particle-simulation';
|
|
15
|
+
update(world, dt) {
|
|
16
|
+
const pool = world.getPool(POOL_PARTICLE);
|
|
17
|
+
if (!pool)
|
|
18
|
+
return;
|
|
19
|
+
const hwm = pool.getHighWaterMark();
|
|
20
|
+
if (hwm === 0)
|
|
21
|
+
return;
|
|
22
|
+
for (let i = 0; i < hwm; i++) {
|
|
23
|
+
const f = pool.flags[i] ?? 0;
|
|
24
|
+
if ((f & PARTICLE_FLAG_ALIVE) === 0)
|
|
25
|
+
continue;
|
|
26
|
+
const remaining = (pool.life[i] ?? 0) - dt;
|
|
27
|
+
if (remaining <= 0) {
|
|
28
|
+
pool.kill(i);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
pool.life[i] = remaining;
|
|
32
|
+
// Velocity integration with constant acceleration.
|
|
33
|
+
const vx = (pool.vx[i] ?? 0) + (pool.ax[i] ?? 0) * dt;
|
|
34
|
+
const vy = (pool.vy[i] ?? 0) + (pool.ay[i] ?? 0) * dt;
|
|
35
|
+
const vz = (pool.vz[i] ?? 0) + (pool.az[i] ?? 0) * dt;
|
|
36
|
+
pool.vx[i] = vx;
|
|
37
|
+
pool.vy[i] = vy;
|
|
38
|
+
pool.vz[i] = vz;
|
|
39
|
+
pool.x[i] = (pool.x[i] ?? 0) + vx * dt;
|
|
40
|
+
pool.y[i] = (pool.y[i] ?? 0) + vy * dt;
|
|
41
|
+
pool.z[i] = (pool.z[i] ?? 0) + vz * dt;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=particle-simulation-system.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"particle-simulation-system.js","sourceRoot":"","sources":["../../src/systems/particle-simulation-system.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,kEAAkE;AAClE,mEAAmE;AACnE,EAAE;AACF,mEAAmE;AACnE,uDAAuD;AACvD,EAAE;AACF,oEAAoE;AACpE,oEAAoE;AACpE,gBAAgB;AAIhB,OAAO,EAAgB,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAE5E,MAAM,CAAC,MAAM,aAAa,GAAG,UAAU,CAAC;AAExC,MAAM,OAAO,wBAAwB;IAC1B,IAAI,GAAW,qBAAqB,CAAC;IAE9C,MAAM,CAAC,KAAY,EAAE,EAAU;QAC7B,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAe,aAAa,CAAC,CAAC;QACxD,IAAI,CAAC,IAAI;YAAE,OAAO;QAClB,MAAM,GAAG,GAAG,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACpC,IAAI,GAAG,KAAK,CAAC;YAAE,OAAO;QAEtB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;YAC7B,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YAC7B,IAAI,CAAC,CAAC,GAAG,mBAAmB,CAAC,KAAK,CAAC;gBAAE,SAAS;YAE9C,MAAM,SAAS,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC;YAC3C,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;gBACnB,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBACb,SAAS;YACX,CAAC;YACD,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;YAEzB,mDAAmD;YACnD,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC;YACtD,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC;YACtD,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC;YACtD,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;YAChB,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;YAChB,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;YAEhB,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC;YACvC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC;YACvC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC;QACzC,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { System } from '../system.js';
|
|
2
|
+
import type { World } from '../world.js';
|
|
3
|
+
export declare class ProjectileRenderSystem implements System {
|
|
4
|
+
readonly name: string;
|
|
5
|
+
update(world: World, _dt: number): void;
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=projectile-render-system.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projectile-render-system.d.ts","sourceRoot":"","sources":["../../src/systems/projectile-render-system.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAWzC,qBAAa,sBAAuB,YAAW,MAAM;IACnD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAuB;IAE5C,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;CA0BxC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// ProjectileRenderSystem - draws live projectiles each frame.
|
|
2
|
+
//
|
|
3
|
+
// Reuses Canvas2DDevice.drawParticle for rendering since projectiles
|
|
4
|
+
// are visually similar to particles (small bright dots / lines). For
|
|
5
|
+
// a dedicated arrow/bolt sprite, future work can extend with a
|
|
6
|
+
// drawProjectile primitive that renders an oriented sprite. For v1
|
|
7
|
+
// of Phase 7 deeper, code-painted dots are sufficient.
|
|
8
|
+
import { POOL_PROJECTILE, PROJECTILE_FLAG_ALIVE } from '../vfx/projectile-pool.js';
|
|
9
|
+
import { RESOURCE_DEVICE, RESOURCE_CAMERA, } from '../resources.js';
|
|
10
|
+
const SCRATCH_COLOR = { r: 1, g: 1, b: 1, a: 1 };
|
|
11
|
+
export class ProjectileRenderSystem {
|
|
12
|
+
name = 'projectile-render';
|
|
13
|
+
update(world, _dt) {
|
|
14
|
+
const pool = world.getPool(POOL_PROJECTILE);
|
|
15
|
+
const device = world.resources.get(RESOURCE_DEVICE);
|
|
16
|
+
const camera = world.resources.get(RESOURCE_CAMERA);
|
|
17
|
+
if (!pool || !device || !camera)
|
|
18
|
+
return;
|
|
19
|
+
if (pool.getLiveCount() === 0)
|
|
20
|
+
return;
|
|
21
|
+
device.setCamera(camera);
|
|
22
|
+
const hwm = pool.getHighWaterMark();
|
|
23
|
+
for (let i = 0; i < hwm; i++) {
|
|
24
|
+
const f = pool.flags[i] ?? 0;
|
|
25
|
+
if ((f & PROJECTILE_FLAG_ALIVE) === 0)
|
|
26
|
+
continue;
|
|
27
|
+
SCRATCH_COLOR.r = pool.r[i] ?? 1;
|
|
28
|
+
SCRATCH_COLOR.g = pool.g[i] ?? 1;
|
|
29
|
+
SCRATCH_COLOR.b = pool.b[i] ?? 1;
|
|
30
|
+
SCRATCH_COLOR.a = pool.a[i] ?? 1;
|
|
31
|
+
device.drawParticle(pool.x[i] ?? 0, pool.y[i] ?? 0, pool.z[i] ?? 0, pool.size[i] ?? 4, SCRATCH_COLOR, true);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=projectile-render-system.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projectile-render-system.js","sourceRoot":"","sources":["../../src/systems/projectile-render-system.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,EAAE;AACF,qEAAqE;AACrE,qEAAqE;AACrE,+DAA+D;AAC/D,mEAAmE;AACnE,uDAAuD;AAIvD,OAAO,EAAkB,eAAe,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AACnG,OAAO,EACL,eAAe,EACf,eAAe,GAChB,MAAM,iBAAiB,CAAC;AAIzB,MAAM,aAAa,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;AAEjD,MAAM,OAAO,sBAAsB;IACxB,IAAI,GAAW,mBAAmB,CAAC;IAE5C,MAAM,CAAC,KAAY,EAAE,GAAW;QAC9B,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAiB,eAAe,CAAC,CAAC;QAC5D,MAAM,MAAM,GAAG,KAAK,CAAC,SAAS,CAAC,GAAG,CAAkB,eAAe,CAAC,CAAC;QACrE,MAAM,MAAM,GAAG,KAAK,CAAC,SAAS,CAAC,GAAG,CAAa,eAAe,CAAC,CAAC;QAChE,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM;YAAE,OAAO;QACxC,IAAI,IAAI,CAAC,YAAY,EAAE,KAAK,CAAC;YAAE,OAAO;QAEtC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QACzB,MAAM,GAAG,GAAG,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACpC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;YAC7B,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YAC7B,IAAI,CAAC,CAAC,GAAG,qBAAqB,CAAC,KAAK,CAAC;gBAAE,SAAS;YAChD,aAAa,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACjC,aAAa,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACjC,aAAa,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACjC,aAAa,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACjC,MAAM,CAAC,YAAY,CACjB,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EACd,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EACd,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EACd,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EACjB,aAAa,EACb,IAAI,CACL,CAAC;QACJ,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { System } from '../system.js';
|
|
2
|
+
import type { World } from '../world.js';
|
|
3
|
+
export declare class ProjectileSystem implements System {
|
|
4
|
+
readonly name: string;
|
|
5
|
+
update(world: World, dt: number): void;
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=projectile-system.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projectile-system.d.ts","sourceRoot":"","sources":["../../src/systems/projectile-system.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAOzC,qBAAa,gBAAiB,YAAW,MAAM;IAC7C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAgB;IAErC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;CA4FvC"}
|