@jgengine/core 0.7.0 → 0.8.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/CHANGELOG.md +47 -0
- package/README.md +2 -0
- package/dist/ai/spawnDirector.d.ts +5 -0
- package/dist/ai/spawnDirector.js +13 -1
- package/dist/anim/easing.d.ts +14 -0
- package/dist/anim/easing.js +55 -0
- package/dist/behaviour/behaviour.d.ts +136 -0
- package/dist/behaviour/behaviour.js +388 -0
- package/dist/cards/cardPile.d.ts +1 -0
- package/dist/cards/cardPile.js +12 -3
- package/dist/combat/abilityKit.d.ts +5 -0
- package/dist/combat/abilityKit.js +10 -0
- package/dist/combat/effects.d.ts +1 -1
- package/dist/combat/effects.js +9 -6
- package/dist/combat/projectiles.d.ts +42 -5
- package/dist/combat/projectiles.js +98 -12
- package/dist/combat/telegraph.d.ts +5 -1
- package/dist/combat/telegraph.js +14 -0
- package/dist/commands/commandRegistry.d.ts +1 -1
- package/dist/commands/commandRegistry.js +2 -1
- package/dist/data/dataSource.d.ts +26 -0
- package/dist/data/dataSource.js +93 -0
- package/dist/data/devProxy.d.ts +11 -0
- package/dist/data/devProxy.js +38 -0
- package/dist/data/fetchJson.d.ts +20 -0
- package/dist/data/fetchJson.js +43 -0
- package/dist/data/jsonDataSource.d.ts +4 -0
- package/dist/data/jsonDataSource.js +6 -0
- package/dist/devtools/devtools.d.ts +157 -0
- package/dist/devtools/devtools.js +484 -0
- package/dist/devtools/transformTunables.d.ts +14 -0
- package/dist/devtools/transformTunables.js +38 -0
- package/dist/game/chat.d.ts +66 -0
- package/dist/game/chat.js +165 -0
- package/dist/game/defineGame.d.ts +8 -2
- package/dist/game/defineGame.js +2 -1
- package/dist/game/events.d.ts +25 -0
- package/dist/game/levelSequence.d.ts +44 -0
- package/dist/game/levelSequence.js +78 -0
- package/dist/game/playableGame.d.ts +237 -6
- package/dist/game/playableGame.js +6 -1
- package/dist/game/progression.d.ts +1 -0
- package/dist/game/progression.js +23 -2
- package/dist/game/race.d.ts +5 -0
- package/dist/game/race.js +20 -0
- package/dist/game/social.d.ts +42 -0
- package/dist/game/social.js +122 -0
- package/dist/game/spawnPoints.d.ts +19 -0
- package/dist/game/spawnPoints.js +23 -0
- package/dist/game/worldItem.d.ts +3 -0
- package/dist/game/worldItem.js +12 -6
- package/dist/input/actionBindings.d.ts +10 -0
- package/dist/input/actionBindings.js +14 -0
- package/dist/input/gestureSurface.d.ts +31 -0
- package/dist/input/gestureSurface.js +120 -0
- package/dist/input/pointer.d.ts +45 -0
- package/dist/input/pointer.js +48 -0
- package/dist/input/touchScheme.d.ts +83 -0
- package/dist/input/touchScheme.js +115 -0
- package/dist/item/affix.d.ts +1 -1
- package/dist/item/affix.js +1 -19
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +37 -1
- package/dist/movement/movementModel.d.ts +36 -3
- package/dist/movement/movementModel.js +81 -5
- package/dist/movement/voxelController.d.ts +61 -0
- package/dist/movement/voxelController.js +229 -0
- package/dist/multiplayer/chatContract.d.ts +42 -0
- package/dist/multiplayer/chatContract.js +57 -0
- package/dist/multiplayer/identity.d.ts +13 -0
- package/dist/multiplayer/identity.js +20 -0
- package/dist/multiplayer/poseSyncGate.d.ts +1 -0
- package/dist/multiplayer/poseSyncGate.js +13 -1
- package/dist/multiplayer/presenceModel.d.ts +3 -0
- package/dist/multiplayer/presenceModel.js +15 -1
- package/dist/multiplayer/voiceContract.d.ts +44 -0
- package/dist/multiplayer/voiceContract.js +108 -0
- package/dist/nav/navConstrain.d.ts +14 -0
- package/dist/nav/navConstrain.js +26 -0
- package/dist/nav/navFromEnvironment.d.ts +11 -0
- package/dist/nav/navFromEnvironment.js +16 -0
- package/dist/nav/navGrid.d.ts +10 -0
- package/dist/nav/navGrid.js +18 -1
- package/dist/nav/pathFollow.d.ts +4 -2
- package/dist/nav/pathFollow.js +5 -3
- package/dist/physics/ballisticSweep.d.ts +18 -0
- package/dist/physics/ballisticSweep.js +49 -0
- package/dist/physics/forceVolume.js +5 -5
- package/dist/physics/physicsWorld.d.ts +41 -2
- package/dist/physics/physicsWorld.js +232 -40
- package/dist/physics/spatialGrid.d.ts +1 -1
- package/dist/physics/spatialGrid.js +5 -0
- package/dist/physics/vehicleBody.d.ts +6 -0
- package/dist/physics/vehicleBody.js +16 -17
- package/dist/puzzle/cellGrid.d.ts +26 -0
- package/dist/puzzle/cellGrid.js +110 -0
- package/dist/puzzle/fallingPiece.d.ts +24 -0
- package/dist/puzzle/fallingPiece.js +51 -0
- package/dist/random/nameGen.d.ts +18 -0
- package/dist/random/nameGen.js +35 -0
- package/dist/random/rng.d.ts +2 -0
- package/dist/random/rng.js +23 -0
- package/dist/runtime/adapter.d.ts +35 -0
- package/dist/runtime/adapter.js +37 -1
- package/dist/runtime/cameraDirector.d.ts +10 -0
- package/dist/runtime/cameraDirector.js +25 -0
- package/dist/runtime/gameContext.d.ts +44 -2
- package/dist/runtime/gameContext.js +100 -4
- package/dist/runtime/hostPersistence.d.ts +21 -0
- package/dist/runtime/hostPersistence.js +50 -0
- package/dist/runtime/inputSnapshot.d.ts +7 -0
- package/dist/runtime/inputSnapshot.js +12 -0
- package/dist/runtime/motionIntents.d.ts +25 -0
- package/dist/runtime/motionIntents.js +25 -0
- package/dist/runtime/transport.d.ts +32 -0
- package/dist/scene/entityStore.d.ts +8 -0
- package/dist/scene/entityStore.js +25 -2
- package/dist/scene/movementSpeed.d.ts +35 -0
- package/dist/scene/movementSpeed.js +20 -0
- package/dist/scene/objectQuery.d.ts +18 -0
- package/dist/scene/objectQuery.js +108 -0
- package/dist/scene/objectStore.d.ts +11 -0
- package/dist/scene/objectStore.js +109 -4
- package/dist/scene/paintLayer.d.ts +15 -0
- package/dist/scene/paintLayer.js +53 -0
- package/dist/scene/spatial.d.ts +21 -0
- package/dist/scene/spatial.js +63 -1
- package/dist/sensor/concealment.d.ts +21 -0
- package/dist/sensor/concealment.js +63 -0
- package/dist/sensor/freezeMonitor.d.ts +17 -0
- package/dist/sensor/freezeMonitor.js +35 -0
- package/dist/session/roles.d.ts +6 -0
- package/dist/session/roles.js +32 -0
- package/dist/session/roundState.d.ts +41 -17
- package/dist/session/roundState.js +67 -38
- package/dist/tactics/fallingGrid.d.ts +62 -0
- package/dist/tactics/fallingGrid.js +158 -0
- package/dist/tactics/tacticalGrid.d.ts +11 -0
- package/dist/tactics/tacticalGrid.js +17 -0
- package/dist/time/simClock.d.ts +25 -1
- package/dist/time/simClock.js +28 -3
- package/dist/turn/intent.d.ts +14 -0
- package/dist/turn/intent.js +29 -0
- package/dist/turn/turnLoop.d.ts +2 -0
- package/dist/turn/turnLoop.js +8 -1
- package/dist/world/environmentSummary.d.ts +53 -0
- package/dist/world/environmentSummary.js +129 -0
- package/dist/world/features.d.ts +87 -6
- package/dist/world/features.js +52 -2
- package/dist/world/gridInstances.d.ts +13 -0
- package/dist/world/gridInstances.js +22 -0
- package/dist/world/placementController.d.ts +22 -1
- package/dist/world/placementController.js +56 -0
- package/dist/world/terrain.d.ts +23 -1
- package/dist/world/terrain.js +64 -1
- package/dist/world/voxelField.d.ts +54 -0
- package/dist/world/voxelField.js +241 -0
- package/llms.txt +4102 -0
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,53 @@ Agents building on the published SDK can also read this programmatically:
|
|
|
11
11
|
same data as typed values, so an updater can diff its installed version against
|
|
12
12
|
the latest and surface the migration steps.
|
|
13
13
|
|
|
14
|
+
## 0.8.0
|
|
15
|
+
|
|
16
|
+
Transport-agnostic multiplayer (socket.io, WebRTC p2p, LAN/Fly adapters) plus grid/voxel
|
|
17
|
+
and puzzle primitives, HUD-only presentation, cumulative leveling, and a round of
|
|
18
|
+
controller/camera/sensor additions. **Breaking:** the `gameui` component kit moved out of
|
|
19
|
+
`@jgengine/react` onto the shadcn registry.
|
|
20
|
+
|
|
21
|
+
### Migrate
|
|
22
|
+
|
|
23
|
+
- Bump every `@jgengine/*` dependency to `^0.8.0` (the eight packages version in lockstep).
|
|
24
|
+
- Additive only, except for `gameui` — every other 0.7.0 API is unchanged; opt into any of the below by importing it directly.
|
|
25
|
+
- **Breaking:** replace any `@jgengine/react/gameui` import with the equivalent registry component (`npx shadcn@latest add https://jgengine.com/r/<name>.json`), and swap `GameUiThemeProvider` for `--jg-*` CSS variables on a wrapper element. `GameIcon` and friends moved to `@jgengine/react/gameIcons`.
|
|
26
|
+
- `leveling({ thresholdMode: 'cumulative' })` is opt-in; the default `perLevel` behavior is unchanged.
|
|
27
|
+
- `defineGame.physics.gravity`/`jumpVelocity` are now read by the built-in kinematics controller every frame — if a game already set them expecting a no-op, jump/fall now actually reflects them; omit both to keep the previous defaults.
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- Cumulative leveling — `leveling({ thresholdMode: 'cumulative' })` tracks xp as a lifetime total that resolves upward across levels and clamps at the max-level threshold once capped (#12).
|
|
32
|
+
- Direction-aware pool depletion — `EffectSystem.canReceive(instanceId, effect, magnitude?)` takes an optional signed magnitude; negative checks the opposite direction and returns `pools-depleted` only when every stat in the receive order is already at max, so heals reach fully-depleted targets (#168).
|
|
33
|
+
- Puzzle primitives — `puzzle/cellGrid` (uniform-cell boards: line-clear, match-3 cascade, run detection) and `puzzle/fallingPiece` (rotation-state shapes, ghost drop, lock delay, classic gravity/level/score curves) for Tetris/match-3 games (#166).
|
|
34
|
+
- Voxel field — `world/voxelField` (`createVoxelField`, chunked block lattice, neighbors/exposedFaces, 3D DDA raycast, dirty-tracked `chunkVersion`) for voxel games and instanced renderers; assert on `field.summary()` the way environment worlds assert on `summarizeEnvironment` (#166).
|
|
35
|
+
- `defineGame` games may omit `assets` (an empty catalog is injected); `PlayableGame.presentation: 'hud'` mounts no 3D canvas/camera/pointer for board/card/menu games; an `environment()` world auto-renders as the shell's backdrop when `PlayableGame.environment` is unset (#166).
|
|
36
|
+
- Declared-action intent board — `turn/intent` `createIntentBoard` for one-turn-ahead intents (Slay-the-Spire style): declare/peek/all/consume/clear (#168).
|
|
37
|
+
- `turnLoop` lifecycle hooks — `config.onTurnStart`/`onTurnEnd` fire on every `advanceTurn()`; `ctx.game.turn.loop(id, config)` lazily creates/returns a notify-wrapped `TurnLoop` so every mutation (`advanceTurn`/`advancePhase`/`advanceRound`/`spend`/`gain`/`refill`/`setOrder`/...) auto-bumps `ctx.version()` with no manual wiring (#163/#168).
|
|
38
|
+
- `ctx.game.store` — a reactive per-game keyed store (`set`/`delete`/`get`/`has`/`subscribe`/`mapSnapshot`/`arraySnapshot`) plus `@jgengine/react`'s `useGameStore` selector hook, replacing hand-rolled module-level stores for ad-hoc reactive game state; `ctx.game.cards.pile(id, config?)` lazily creates/returns a notify-wrapped `CardPile` the same way; `createCardPile` gained an `onChange` hook for headless use; `CommandDefinition.apply` may return void for side-effect-only commands (#163).
|
|
39
|
+
- Camera — `sideScroll` rig (fixed lateral follow for 2.5D platformers/beat-'em-ups), a `none` rig (no camera mounted; pairs with `PlayableGame.presentation: 'hud'`), `rts.pan: false` (static backdrop camera: no pan/edge-scroll/rotate/zoom, still re-centers on the follow target), and the `observer` rig now defaults to the local player when `bind` is unset (#167).
|
|
40
|
+
- Sensors + session — `sensor/concealment` (`colorDistance`/`concealmentScore`/`createConcealmentSensor`), `sensor/freezeMonitor` (`createFreezeMonitor`), `session/roles` (`assignRoles`); `createRoundState`'s `RoundConfig.teams` accepts per-team roles and an optional `winCondition` that `evaluate()` checks each tick, and takes an optional `phaseOrder` for arbitrary named phase cycles (`concludeRound`/`evaluate` settle only while the current phase is neither the first nor the last entry) (#151).
|
|
41
|
+
- Appearance replication — presence rows carry an optional per-slot appearance channel (cosmetic ids, hex tints, model keys) alongside pose, riding the existing pose message with no protocol bump; wire `ctx.player.cosmetics.get(userId)` into the outgoing pose (#151).
|
|
42
|
+
- `ctx.input` — a per-frame held-action snapshot (`publish(held)`/`isDown(action)`/`held()`) without bumping `ctx.version()`; action bindings gained `repeatMs` (repeat-fire while held); every command resolved from a bound action now carries `aim`; `pointer.secondaryCommand` runs a command on right-click off the same raycast as move/ping (#164).
|
|
43
|
+
- Object spatial queries + entity patching — `ctx.scene.object.at`/`inBox`/`raycast`/`raycastAll` over unit-box objects; `ctx.scene.entity.update(id, patch)` for name/position/rotation/role/movement/behaviors/meta; per-instance `renderObject`/`objectStyles` overrides; `pointerService.worldHitCenter()` + pointer-lock center-ray aiming (#165).
|
|
44
|
+
- Controller movement config — `PlayerMovementConfig` (`mode`: free/axis/grid, `axis`, `cellSize`, `collideObjects`, `beforeCommit` pre-commit hook) for the shell-driven walk controller; `defineGame.physics.gravity`/`jumpVelocity` are honored by the built-in walk controller (distinct from the standalone `physics/physicsWorld` rigid-body sim); `ctx.player.motion.impulse`/`setVerticalVelocity`/`setY`/`takePending` (`MotionIntents`); `entity.spawnPoseOf`/`resetToSpawn`/`resetAllToSpawn` (#162).
|
|
45
|
+
- Model material/animation + paint — `ModelConfig.tint`/`metalness`/`roughness`/`animation` (GLTF clip playback, paused pose holds); `PointerHit.uv` + `pointerService.sampleSurface()` for material-aware picking; `ctx.scene.entity.paint` runtime paint layer, auto-rendered via a per-instance canvas texture with no per-game wiring; remote-player appearance tint recolors the shell's default capsule (#151).
|
|
46
|
+
- **Transport pipe seam** (`@jgengine/ws/pipe`) — `createWsBackend` runs over any bidirectional string channel, not just a raw `WebSocket`: `TransportPipe`/`TransportPipeHandlers`/`TransportPipeFactory`, with `webSocketPipe(url, webSocketFactory?)` as the default. `createWsBackend({ userId, url?, pipe? })` — `url` stays the common case, `pipe` opens the seam to socket.io, WebRTC, and in-process loopback below.
|
|
47
|
+
- **Browser-safe authoritative host** — `createGameHost` and `memoryPersistence` moved from `@jgengine/node` to `@jgengine/ws/host` (zero Node dependencies; `@jgengine/node` re-exports both unchanged from `@jgengine/node/host` / `@jgengine/node/persistence`, so existing imports keep working). `@jgengine/ws/hostRouter`'s `createHostRouter({ host, authenticate?, poseRules?, positionHistoryMs?, chatRateLimit?, chatHistoryLimit?, chatMaxBodyLength?, now? })` extracts the ws wire-protocol session logic out of `createGameWsServer` into a transport-agnostic `HostRouter` (`connect(transport) → { handleRaw, close }`, `rewind`, `close`); `@jgengine/node`'s `createGameWsServer` is now a thin binding of this router onto the `ws` npm package, same public API. `loopbackPipe(router): TransportPipeFactory` connects a `createWsBackend` straight into an in-process router.
|
|
48
|
+
- **Socket.IO transport** — `@jgengine/ws/socketIoPipe` (`SocketIoLikeSocket` structural type, `socketIoPipe(socket)`, `createSocketIoBackend({ socket, userId, … })`) and `@jgengine/node/socketIoServer` (`attachGameSocketIoServer({ io, host, …router options }): { rewind, close }`, structural `SocketIoLikeServer`/`SocketIoLikeServerSocket`) ride the existing ws JSON protocol over socket.io's `send`/`message` frames — no socket.io dependency in either package's types. New `socketIo({ topology?, url? })` adapter in `@jgengine/core/runtime/adapter`.
|
|
49
|
+
- **WebRTC peer-to-peer** (`@jgengine/ws/peer`) — one browser tab hosts, authoritatively, with no server process. `createPeerHost({ userId, host?, runtimes?, persistence?, tickMs?, router?, rtc? })` runs a `GameHost` + `HostRouter` in the host tab (`backend` is the host player's own loopback connection, `accept(offerCode) → Promise<answerCode>` per guest); `createPeerGuest({ userId, token?, rtc? })` offers/connects from the joining side. `encodePeerSignal`/`decodePeerSignal` turn SDP into copy-pasteable base64url codes for manual cross-machine signaling; `broadcastChannelSignaling(room)` automates it for same-origin multi-tab play, with `announcePeerHost`/`joinPeerSession` wiring host/guest to a `PeerSignaling` in one call. New `p2p({ topology?, room? })` adapter (topology defaults `"private"`).
|
|
50
|
+
- **LAN adapter + Fly sugar** — `lan({ topology?, port?, path? })` in `@jgengine/core/runtime/adapter` resolves through `@jgengine/shell/multiplayer`'s `resolveShellMultiplayer` to `ws(s)://<page hostname>:<port ?? 8080><path ?? /ws>` derived from `window.location`, so any browser on the LAN auto-connects to whichever machine served the page — no URL configuration. `fly({ app, topology?, path? })` is `ws` sugar for a Fly.io deploy: resolves to `wss://<app>.fly.dev<path ?? "/ws">`. `apps/dev`'s Vite server now listens on the network (`server: { host: true }`) and exposes `?p2p=host` / `?p2p=join` query params wired through the new `resolvePeerShellMultiplayer({ gameId, role, room?, userId?, feedActions? })`.
|
|
51
|
+
- `ws()` (`@jgengine/core/runtime/adapter`) gained an optional `url` field, carried through by `resolveShellMultiplayer` (`args.url ?? adapter.url ?? default`).
|
|
52
|
+
|
|
53
|
+
### Removed
|
|
54
|
+
|
|
55
|
+
- **The `gameui` component kit** (`@jgengine/react/gameui`) — the themed HUD kit (bars, slots, feedback, meters, panels, screens, reticles, icons) and its `GameUiThemeProvider`/`useGameUiTheme` theming have been removed. **Breaking** for anyone importing `@jgengine/react/gameui` (or its subpaths/barrel). The components now ship as installable shadcn registry items at `https://jgengine.com/r/<name>.json` (`npx shadcn@latest add https://jgengine.com/r/<name>.json`), styled with Tailwind + `--jg-*` CSS variables instead of a theme object. The icon catalog (`GameIcon`, `iconForAction`, `iconForItemId`, `isGameIconName`, `GameIconName`) moved to `@jgengine/react/gameIcons`. To theme, set the `--jg-*` variables on a wrapper element (the registry's `jg-theme` presets mirror the old `ember`/`synthwave`/`fieldkit` palettes) instead of wrapping in `GameUiThemeProvider`.
|
|
56
|
+
|
|
57
|
+
### Docs
|
|
58
|
+
|
|
59
|
+
- Added [CREDITS.md](CREDITS.md) crediting [achrefelouafi](https://github.com/achrefelouafi) for the MIT Three.js reference projects behind the procedural building, water, rain, and snow renderers, with links from the root, `@jgengine/core`, and `@jgengine/shell` READMEs.
|
|
60
|
+
|
|
14
61
|
## 0.7.0
|
|
15
62
|
|
|
16
63
|
The engine-gaps release — 22 system-level additions across turn/tactics, cards & boards,
|
package/README.md
CHANGED
|
@@ -8,4 +8,6 @@ Import modules by path:
|
|
|
8
8
|
import { createGameRuntime } from "@jgengine/core/runtime/gameRuntime";
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
The renderer-free world layer includes a Gerstner `waterSurface` (`world/water`) and a seeded `generateBuilding` kit (`world/buildings`), whose wave math and building component vocabulary were shaped from **[achrefelouafi](https://github.com/achrefelouafi)**'s MIT [OceanThreejs](https://github.com/achrefelouafi/OceanThreejs) and [BuildingGeneratorThreeJS](https://github.com/achrefelouafi/BuildingGeneratorThreeJS) — see [CREDITS.md](../../CREDITS.md).
|
|
12
|
+
|
|
11
13
|
Part of [JGengine](https://github.com/Noisemaker111/jgengine). AGPL-3.0-only.
|
|
@@ -21,6 +21,8 @@ export interface SpawnDirectorConfig {
|
|
|
21
21
|
maxSpawnsPerTick?: number;
|
|
22
22
|
loop?: boolean;
|
|
23
23
|
seed?: number;
|
|
24
|
+
spawnPoints?: readonly NavPoint[];
|
|
25
|
+
spawnPointBias?: number;
|
|
24
26
|
}
|
|
25
27
|
export interface SpawnDirectorState {
|
|
26
28
|
wave: number;
|
|
@@ -36,11 +38,14 @@ export interface SpawnDirectorState {
|
|
|
36
38
|
export interface DirectorContext {
|
|
37
39
|
alive: number;
|
|
38
40
|
players?: number;
|
|
41
|
+
playerPositions?: readonly NavPoint[];
|
|
39
42
|
}
|
|
40
43
|
export interface SpawnRequest {
|
|
41
44
|
entryId: string;
|
|
42
45
|
cost: number;
|
|
43
46
|
wave: number;
|
|
47
|
+
point?: NavPoint;
|
|
48
|
+
laneId?: number;
|
|
44
49
|
}
|
|
45
50
|
export interface DirectorStep {
|
|
46
51
|
state: SpawnDirectorState;
|
package/dist/ai/spawnDirector.js
CHANGED
|
@@ -106,13 +106,25 @@ export function advanceSpawnDirector(config, state, dt, ctx) {
|
|
|
106
106
|
let spawnedTotal = state.spawnedTotal;
|
|
107
107
|
let alive = ctx.alive;
|
|
108
108
|
const entries = config.waves[wave]?.entries ?? [];
|
|
109
|
+
const spawnPoints = config.spawnPoints;
|
|
110
|
+
const playerPositions = ctx.playerPositions ?? [];
|
|
109
111
|
while (spawns.length < maxSpawns && alive < maxAlive) {
|
|
110
112
|
const picked = pickEntry(entries, wave, budget, rng);
|
|
111
113
|
if (picked === null)
|
|
112
114
|
break;
|
|
113
115
|
rng = picked.rng;
|
|
114
116
|
budget -= picked.entry.cost;
|
|
115
|
-
|
|
117
|
+
const spawn = { entryId: picked.entry.id, cost: picked.entry.cost, wave };
|
|
118
|
+
if (spawnPoints !== undefined && spawnPoints.length > 0) {
|
|
119
|
+
const [roll, nextRng] = nextRandom(rng);
|
|
120
|
+
rng = nextRng;
|
|
121
|
+
const point = pickSpawnPoint(spawnPoints, playerPositions, { roll, bias: config.spawnPointBias });
|
|
122
|
+
if (point !== null) {
|
|
123
|
+
spawn.point = point;
|
|
124
|
+
spawn.laneId = spawnPoints.indexOf(point);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
spawns.push(spawn);
|
|
116
128
|
spawnedThisWave += 1;
|
|
117
129
|
spawnedTotal += 1;
|
|
118
130
|
alive += 1;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type Easing = (t: number) => number;
|
|
2
|
+
export declare function lerp(from: number, to: number, t: number): number;
|
|
3
|
+
export declare function clamp01(t: number): number;
|
|
4
|
+
export declare function smoothstep(t: number): number;
|
|
5
|
+
export declare function easeInQuad(t: number): number;
|
|
6
|
+
export declare function easeOutQuad(t: number): number;
|
|
7
|
+
export declare function easeInOutQuad(t: number): number;
|
|
8
|
+
export declare function easeInCubic(t: number): number;
|
|
9
|
+
export declare function easeOutCubic(t: number): number;
|
|
10
|
+
export declare function easeInOutCubic(t: number): number;
|
|
11
|
+
export declare function easeOutBack(t: number): number;
|
|
12
|
+
export declare function easeOutElastic(t: number): number;
|
|
13
|
+
export declare function tween(from: number, to: number, t: number, easing?: Easing): number;
|
|
14
|
+
export declare function timedProgress(startedAt: number, now: number, durationMs: number): number;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export function lerp(from, to, t) {
|
|
2
|
+
return from + (to - from) * t;
|
|
3
|
+
}
|
|
4
|
+
export function clamp01(t) {
|
|
5
|
+
return t < 0 ? 0 : t > 1 ? 1 : t;
|
|
6
|
+
}
|
|
7
|
+
export function smoothstep(t) {
|
|
8
|
+
const c = clamp01(t);
|
|
9
|
+
return c * c * (3 - 2 * c);
|
|
10
|
+
}
|
|
11
|
+
export function easeInQuad(t) {
|
|
12
|
+
const c = clamp01(t);
|
|
13
|
+
return c * c;
|
|
14
|
+
}
|
|
15
|
+
export function easeOutQuad(t) {
|
|
16
|
+
const c = clamp01(t);
|
|
17
|
+
return 1 - (1 - c) * (1 - c);
|
|
18
|
+
}
|
|
19
|
+
export function easeInOutQuad(t) {
|
|
20
|
+
const c = clamp01(t);
|
|
21
|
+
return c < 0.5 ? 2 * c * c : 1 - ((-2 * c + 2) ** 2) / 2;
|
|
22
|
+
}
|
|
23
|
+
export function easeInCubic(t) {
|
|
24
|
+
const c = clamp01(t);
|
|
25
|
+
return c * c * c;
|
|
26
|
+
}
|
|
27
|
+
export function easeOutCubic(t) {
|
|
28
|
+
const c = clamp01(t);
|
|
29
|
+
return 1 - (1 - c) ** 3;
|
|
30
|
+
}
|
|
31
|
+
export function easeInOutCubic(t) {
|
|
32
|
+
const c = clamp01(t);
|
|
33
|
+
return c < 0.5 ? 4 * c * c * c : 1 - ((-2 * c + 2) ** 3) / 2;
|
|
34
|
+
}
|
|
35
|
+
const BACK_OVERSHOOT = 1.70158;
|
|
36
|
+
export function easeOutBack(t) {
|
|
37
|
+
const c = clamp01(t);
|
|
38
|
+
const s = BACK_OVERSHOOT + 1;
|
|
39
|
+
return 1 + s * (c - 1) ** 3 + BACK_OVERSHOOT * (c - 1) ** 2;
|
|
40
|
+
}
|
|
41
|
+
const ELASTIC_PERIOD = (2 * Math.PI) / 3;
|
|
42
|
+
export function easeOutElastic(t) {
|
|
43
|
+
const c = clamp01(t);
|
|
44
|
+
if (c === 0 || c === 1)
|
|
45
|
+
return c;
|
|
46
|
+
return 2 ** (-10 * c) * Math.sin((c * 10 - 0.75) * ELASTIC_PERIOD) + 1;
|
|
47
|
+
}
|
|
48
|
+
export function tween(from, to, t, easing = smoothstep) {
|
|
49
|
+
return lerp(from, to, easing(clamp01(t)));
|
|
50
|
+
}
|
|
51
|
+
export function timedProgress(startedAt, now, durationMs) {
|
|
52
|
+
if (durationMs <= 0)
|
|
53
|
+
return now >= startedAt ? 1 : 0;
|
|
54
|
+
return clamp01((now - startedAt) / durationMs);
|
|
55
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unity-style behaviour lifecycle over a headless id-keyed node tree, ported from
|
|
3
|
+
* vladkrutenyuk/three-start (MIT) and adapted to run without three.js: nodes are plain
|
|
4
|
+
* string ids (use entity instance ids to pair with `entityStore`), and the shell binds
|
|
5
|
+
* render-side hooks separately.
|
|
6
|
+
*
|
|
7
|
+
* Lifecycle per behaviour: `onAwake` (once, first activation) → `onEnable` (every
|
|
8
|
+
* inactive→active transition) → `onStart` (once, after the first `onEnable`) →
|
|
9
|
+
* `onUpdate` (per `world.update` while active) → `onDisable` (every active→inactive
|
|
10
|
+
* transition) → `onDestroy` (once, always — even if never activated).
|
|
11
|
+
*
|
|
12
|
+
* `world.start()` bootstraps in passes: modules awake → modules start → modules join
|
|
13
|
+
* update dispatch → ALL behaviours on effectively-active nodes awake (even disabled
|
|
14
|
+
* ones) → enable/start/subscribe. Behaviours attached during the module pass are
|
|
15
|
+
* deferred to the behaviour passes, so module `onUpdate` always dispatches before any
|
|
16
|
+
* behaviour `onUpdate`.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Declaration-merging registry games and adapters augment for typed access to
|
|
20
|
+
* world modules:
|
|
21
|
+
*
|
|
22
|
+
* ```ts
|
|
23
|
+
* declare module "@jgengine/core/behaviour/behaviour" {
|
|
24
|
+
* interface JGEngineRegister {
|
|
25
|
+
* modules: { physics: PhysicsModule; director: DirectorModule };
|
|
26
|
+
* }
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export interface JGEngineRegister {
|
|
31
|
+
}
|
|
32
|
+
export type RegisterField<TKey extends string, TFallback = unknown> = TKey extends keyof JGEngineRegister ? JGEngineRegister[TKey] : TFallback;
|
|
33
|
+
/** Resolves to the augmented module map when `JGEngineRegister` declares one. */
|
|
34
|
+
export type BehaviourModules = RegisterField<"modules", Record<string, BehaviourModule>>;
|
|
35
|
+
interface UpdateSubscriber {
|
|
36
|
+
onUpdate(dt: number): void;
|
|
37
|
+
}
|
|
38
|
+
interface BehaviourWorldInternal extends BehaviourWorld {
|
|
39
|
+
_subscribeUpdate(subscriber: UpdateSubscriber): void;
|
|
40
|
+
_unsubscribeUpdate(subscriber: UpdateSubscriber): void;
|
|
41
|
+
_isEffectivelyActive(nodeId: string): boolean;
|
|
42
|
+
_detachBehaviour(behaviour: Behaviour): void;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Subclass and override the lifecycle hooks. A behaviour only joins the per-frame
|
|
46
|
+
* update dispatch if it actually overrides `onUpdate` (prototype-identity check at
|
|
47
|
+
* each activation), so hook-only behaviours cost nothing per frame.
|
|
48
|
+
*/
|
|
49
|
+
export declare class Behaviour {
|
|
50
|
+
/** @internal */ _world: BehaviourWorldInternal | null;
|
|
51
|
+
/** @internal */ _nodeId: string | null;
|
|
52
|
+
/** @internal */ _awoken: boolean;
|
|
53
|
+
/** @internal */ _startedOnce: boolean;
|
|
54
|
+
/** @internal */ _isActive: boolean;
|
|
55
|
+
private _enabled;
|
|
56
|
+
private _destroyed;
|
|
57
|
+
/** The node this behaviour is attached to; available from `onAwake` onward, never in the constructor. */
|
|
58
|
+
get nodeId(): string;
|
|
59
|
+
get world(): BehaviourWorld;
|
|
60
|
+
get modules(): BehaviourModules;
|
|
61
|
+
/** Own enabled flag; effective activity also requires the node's hierarchy to be active. */
|
|
62
|
+
get enabled(): boolean;
|
|
63
|
+
/** Whether the lifecycle is currently in the active span (`onEnable` fired, `onDisable` not yet). */
|
|
64
|
+
get isActive(): boolean;
|
|
65
|
+
onAwake(): void;
|
|
66
|
+
onEnable(): void;
|
|
67
|
+
onStart(): void;
|
|
68
|
+
onUpdate(_dt: number): void;
|
|
69
|
+
onDisable(): void;
|
|
70
|
+
onDestroy(): void;
|
|
71
|
+
enable(): void;
|
|
72
|
+
disable(): void;
|
|
73
|
+
/** Flips the flag only when the node is hierarchy-inactive or the world hasn't started; lifecycle catches up on the next activation. */
|
|
74
|
+
setEnabled(enabled: boolean): void;
|
|
75
|
+
/** Detaches from the node and fires `onDisable` (if active) then `onDestroy`. */
|
|
76
|
+
destroy(): void;
|
|
77
|
+
/** @internal */
|
|
78
|
+
_activate(): void;
|
|
79
|
+
/** @internal */
|
|
80
|
+
_deactivate(): void;
|
|
81
|
+
/** @internal */
|
|
82
|
+
_destroy(): void;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* A world-lifetime service with typed sibling access via `this.modules`. Modules awake
|
|
86
|
+
* and start before any behaviour during `world.start()`, subscribe to update dispatch
|
|
87
|
+
* first (their `onUpdate` fires before every behaviour's), and have no disable/destroy —
|
|
88
|
+
* they live as long as the world.
|
|
89
|
+
*/
|
|
90
|
+
export declare class BehaviourModule {
|
|
91
|
+
/** @internal */ _world: BehaviourWorldInternal | null;
|
|
92
|
+
get world(): BehaviourWorld;
|
|
93
|
+
get modules(): BehaviourModules;
|
|
94
|
+
onAwake(): void;
|
|
95
|
+
onStart(): void;
|
|
96
|
+
onUpdate(_dt: number): void;
|
|
97
|
+
/** @internal */
|
|
98
|
+
_subscribe(): void;
|
|
99
|
+
}
|
|
100
|
+
export interface BehaviourWorld {
|
|
101
|
+
/** Creates the node if missing (parented under `parentId` when given, else a root); reparents an existing node when `parentId` differs. */
|
|
102
|
+
add(id: string, parentId?: string): void;
|
|
103
|
+
has(id: string): boolean;
|
|
104
|
+
parentOf(id: string): string | null;
|
|
105
|
+
childrenOf(id: string): readonly string[];
|
|
106
|
+
/** Moves `id` (and subtree) under `parentId` (`null` = root), firing enable/disable cascades when effective activity changes. Throws on cycles. */
|
|
107
|
+
setParent(id: string, parentId: string | null): void;
|
|
108
|
+
/** Attaches and immediately activates when the world has started, the node is effectively active, and the behaviour is enabled (deferred during bootstrap). */
|
|
109
|
+
attach<T extends Behaviour>(nodeId: string, behaviour: T): T;
|
|
110
|
+
behavioursOf(nodeId: string): readonly Behaviour[];
|
|
111
|
+
/**
|
|
112
|
+
* Sets the node's own flag and cascades activation/deactivation through the subtree,
|
|
113
|
+
* pruning at descendants whose own flag is false — so reactivating a parent restores
|
|
114
|
+
* exactly the per-child stored flags. A flip under an inactive ancestor changes only
|
|
115
|
+
* the flag.
|
|
116
|
+
*/
|
|
117
|
+
setActive(id: string, active: boolean): void;
|
|
118
|
+
/** The node's own flag; unknown ids count active. */
|
|
119
|
+
isActiveSelf(id: string): boolean;
|
|
120
|
+
/** Own flag AND every ancestor's; unknown ids count active. */
|
|
121
|
+
isActive(id: string): boolean;
|
|
122
|
+
/** Registers modules by key. Throws on duplicate keys or after `start()`. */
|
|
123
|
+
addModules(modules: Partial<BehaviourModules>): void;
|
|
124
|
+
readonly modules: BehaviourModules;
|
|
125
|
+
/** Bootstraps: modules awake/start/subscribe, then all behaviours on effectively-active nodes awake, then enable+start+subscribe. Idempotent. */
|
|
126
|
+
start(): void;
|
|
127
|
+
started(): boolean;
|
|
128
|
+
/** Dispatches `onUpdate(dt)` to modules then behaviours, over a snapshot — subscribers added mid-dispatch first fire next update. */
|
|
129
|
+
update(dt: number): void;
|
|
130
|
+
/** Destroys every behaviour in the subtree (parent-first) and removes the nodes. */
|
|
131
|
+
destroyNode(id: string): boolean;
|
|
132
|
+
/** Destroys all nodes and behaviours and clears update dispatch. */
|
|
133
|
+
destroy(): void;
|
|
134
|
+
}
|
|
135
|
+
export declare function createBehaviourWorld(): BehaviourWorld;
|
|
136
|
+
export {};
|