@jgengine/assets 0.13.0 → 0.15.0-next.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 +529 -1
- package/dist/cli/paths.d.ts +26 -0
- package/dist/cli/paths.js +34 -0
- package/dist/cli/pull.js +18 -7
- package/dist/credits.d.ts +52 -0
- package/dist/credits.js +89 -0
- package/package.json +3 -3
- package/skills/ce-handoff/SKILL.md +149 -0
- package/skills/jgengine/SKILL.md +7 -3
- package/skills/jgengine/api.md +17 -7
- package/skills/jgengine/capabilities.md +12 -0
- package/skills/jgengine/recipes/minimal-game.md +7 -4
- package/skills/jgengine-assets/SKILL.md +1 -1
- package/skills/jgengine-assets/api.md +10 -0
- package/skills/jgengine-assets/capabilities.md +20 -0
- package/skills/jgengine-assets/references/video-to-3d-capture.md +69 -0
package/CHANGELOG.md
CHANGED
|
@@ -10,7 +10,535 @@ let consumers pick up the better stuff, not just to list what moved.
|
|
|
10
10
|
Agents building on the published SDK can also read this programmatically:
|
|
11
11
|
`import { VERSION, CHANGELOG } from "@jgengine/core/meta/changelog"` gives the
|
|
12
12
|
same data as typed values, so an updater can diff its installed version against
|
|
13
|
-
the latest and surface the migration steps.
|
|
13
|
+
the latest and surface the migration steps. Easier still: `npx jgengine upgrade`
|
|
14
|
+
run inside a game project diffs the installed `@jgengine/*` versions against the
|
|
15
|
+
latest release and prints every Migrate step and Adopt-worthy addition in
|
|
16
|
+
between (`--json` for structured output).
|
|
17
|
+
|
|
18
|
+
## [Unreleased]
|
|
19
|
+
|
|
20
|
+
<!--
|
|
21
|
+
Every PR that changes `packages/*/src` records its consumer-facing change here, so
|
|
22
|
+
the next release's notes are complete by construction. Add a bullet under the right
|
|
23
|
+
subhead below. `bun run check-changelog` fails a source PR whose `[Unreleased]` block
|
|
24
|
+
is untouched; pure refactors/tests bypass with `[skip changelog]` in a commit message.
|
|
25
|
+
At publish, rename this heading to the new version and mirror the entries into
|
|
26
|
+
`packages/core/src/meta/changelog.ts` (the typed `CHANGELOG` export).
|
|
27
|
+
-->
|
|
28
|
+
|
|
29
|
+
### Migrate
|
|
30
|
+
|
|
31
|
+
- **`jgengine create` scaffolds a textured world by default (#1550).** `--world` is now the default and `--no-world` is the opt-out (matching `--no-editor`); `--world` is still accepted. The default scaffold previously emitted untextured proxy geometry, which the AGENTS.md it ships declares a failing result. `create` now also pulls the starter asset packs into `public/models` after install, and never fails the scaffold if that pull cannot run (offline, blocked proxy, `--no-install`) — it prints `npx assets pull starter` in next steps instead, and the emitted AGENTS.md documents the same command. Skip the pull explicitly with `--no-assets`.
|
|
32
|
+
- **Start game code at `@jgengine/shell/gameKit` (#1541).** Prefer that surface for `defineGame`, `GameHost`, stores, systems, authored-scene helpers, and HUD building blocks. `@jgengine/core/authoring` remains for pure core helpers the kit does not re-export but is no longer a competing start-here path.
|
|
33
|
+
- **Every game must now declare its run-phase story — silence is a gate error (#1337).** The run-phase
|
|
34
|
+
contract used to be optional with a permissive default: a game that never published a phase read as
|
|
35
|
+
`"playing"`, so the shell painted the touch dock over title/menu/results screens (Vice Isle #1329).
|
|
36
|
+
`check-game-shape` now fails any game under `Games/*` that neither declares `lifecycle` in
|
|
37
|
+
`defineGame({...})` nor calls `setGamePhase(ctx, phase)` somewhere in `src/`. To satisfy it, a game
|
|
38
|
+
that runs live from boot with no menu/pause/end screens declares `defineGame({ lifecycle: "always-live" })`;
|
|
39
|
+
a game with run states declares a `LifecycleConfig` or calls `setGamePhase` at its
|
|
40
|
+
menu→playing→ended transitions. No runtime behavior changes for a game that already drove phases.
|
|
41
|
+
- **`generateCity` output is plot-first and no longer square by default.** Same options in, but the
|
|
42
|
+
layout changes: lots derive from block-frontage plots (varied sizes), the street net clips to a
|
|
43
|
+
seeded organic outline (`streets: { outline: 0 }` restores the legacy rectangle-filling footprint),
|
|
44
|
+
and the landmark/interior-fill passes are gone — `content.landmarks` now dials the share of grand
|
|
45
|
+
(block-scale) plots and `blockFill` dials frontage coverage/plot depth/park share.
|
|
46
|
+
`ResolvedCityLot` drops the `interior`/`park` flags (no street-less interior lots exist anymore)
|
|
47
|
+
and the `INTERIOR_LOTS_PER_BLOCK_CAP` export is removed; `GeneratedCity` gains `plots` + `parks`.
|
|
48
|
+
A city consumer that only reads `lots`/`lotContent` (center/rotation/footprint/pieces) needs no
|
|
49
|
+
code change.
|
|
50
|
+
- Walking-player object collision is now **on by default** (`PlayerMovementConfig.collideObjects`
|
|
51
|
+
defaults `true`): placed scene objects with blocking colliders stop, support, and are stood upon by
|
|
52
|
+
the player without opting in. A game that relies on walking through placed objects sets
|
|
53
|
+
`movement: { collideObjects: false }` (or gives those objects non-blocking colliders).
|
|
54
|
+
- Ground drops taller than `movement.stepHeight` (default 0.4) now fall under gravity instead of
|
|
55
|
+
snapping the feet down in one frame. Games that teleport-spawned players high above the ground will
|
|
56
|
+
see them fall to it.
|
|
57
|
+
- **Projectile tracers are now opt-in and never drawn for ballistic shots.** `presentationEffects.tracers`
|
|
58
|
+
defaults **off** (every other presentation channel still defaults on), so the shell no longer draws a
|
|
59
|
+
straight muzzle→impact line for a game that never asked for one — `fireProjectile` is a generic seam
|
|
60
|
+
(bullets, bolts, grenades, launchers), and a tracer only reads as a real shot for direct-fire weapons.
|
|
61
|
+
A game that wants bullet tracers now sets `presentationEffects: { tracers: true }`. Even with tracers on,
|
|
62
|
+
arced/exploding shots (anything ballistic — grenades, rocket/grenade launchers) never draw one, because a
|
|
63
|
+
straight line through an arc is a fake beam. The `projectile.settled` event and `ProjectileSettleReport`
|
|
64
|
+
gain a required `ballistic: boolean`; code that emits or consumes them directly must set/handle the field.
|
|
65
|
+
|
|
66
|
+
### Changed
|
|
67
|
+
|
|
68
|
+
- **Prerelease publishing (`0.15.0-next.0`).** Package versions now move together via `bun run version:prerelease` (`bun run check-version` verifies package versions, internal `@jgengine/*` ranges, and the `VERSION` mirror agree). The Publish workflow is `workflow_dispatch`-able from any ref and picks the npm dist-tag from the version, so prerelease builds land on `next` and never move `latest`. Install a prerelease with `npm i @jgengine/core@next`.
|
|
69
|
+
- **SDK remediation Phase 3a — AAA visual/sim ceiling items (#1545).** `ctx.rng` is a per-world seeded stream (loot tables default to it); post chains default to SMAA before tone-map; cinematic IBL is a procedural sky/ground(+sun) probe instead of RoomEnvironment gray box; directional lights support `cascades > 1` via three.js CSM; `stepFlock` no longer shares a module-global spatial grid across worlds.
|
|
70
|
+
- **SDK remediation Phase 1.2 + 2.3 — adoption ratchet + cheap seams (#1543).** `check-game-shape` now freezes module-level `export let` and local `Window`/`Bar`/`Chip`/`Slot` components via shrink-only `scripts/game-adoption-baseline.json` (new smells fail CI). Ships `grounded(ctx, x, z)` (`@jgengine/core/world/grounded`, also on `gameKit`), `DEFAULT_WALK_CODES` (WASD + Space on `@jgengine/shell/shellMovement` / `gameKit`), and a HudTheme-token default skin on `SettingsTrigger` (`SETTINGS_TRIGGER_CLASSNAME`) so bare triggers need no 8×8 className re-authoring.
|
|
71
|
+
- **SDK remediation Phase 0 — one start-here + correct UI guidance (#1541).** `@jgengine/shell/gameKit` is the sole happy-path game entrypoint; `@jgengine/core/authoring` no longer claims "Game code should begin here" (still a core helper barrel for pure exports the kit does not re-export). The whole-game recipe and scaffold `GameUI` header now match CLAUDE.md: compose shipped HUD building blocks (`StatBar`, `Hotbar`, `Coins`, …) and reskin via HudTheme — games own layout/terminology/art direction, not re-derivation. Studio Showcase and Tower Guard import kit-covered symbols from `gameKit`.
|
|
72
|
+
- **Kinematic vehicle drive feel** (#1515) — `createKinematicVehicle` gains a low-speed launch torque floor, softer reverse by default (`chassis.reverseForceScale`, default ~0.48 of engine force), handbrake rear-lock oversteer yaw, engine-braking that no longer fights powered reverse, and lighter ESC while handbraking. Existing chassis tunings pick this up with no migrate; override `reverseForceScale` only if you need the old full-force reverse.
|
|
73
|
+
|
|
74
|
+
### Added
|
|
75
|
+
|
|
76
|
+
- **`ModelMaterialOverride.rim` — a fresnel rim light on any entity/object model.** `{ color, strength, power }` adds a bright edge where the surface turns away from the viewer, patched into the material's own shader so it shadows, fogs, and tonemaps with the body rather than floating in front of it. This is the standard fix for an actor that shares a value band with its ground: no albedo choice separates a body from terrain lit by the same key, but an edge does. Omit for no rim (default).
|
|
77
|
+
|
|
78
|
+
### Fixed
|
|
79
|
+
|
|
80
|
+
- **`terrain({ detail: { material: { tint } } })` separates a ground texture's grain from its hue.** A detail material's colour map used to replace the terrain's authored colour outright, so the biome palette, macro sweeps, and slope-rock blend under it were painted over and a whole region flattened to the texture's single hue — despite the API documenting that it "blends with, never replaces". `tint` defaults to 1 (the existing behaviour, so no shipped game changes); drop it toward 0 to keep the authored colour and take only the map's light and shade.
|
|
81
|
+
- **`sky({ gradientExponent, cloudiness })` are now authorable — and `sky()` no longer silently drops them.** `gradientExponent` controls how fast the gradient reaches `zenithColor` off the horizon (default 0.65, unchanged): below 1 pulls the zenith hue down into the frame, which is what an eye-level camera in flat terrain actually needs — at the default the sky reads as one horizon-coloured wash however saturated the authored zenith is. `cloudiness` (default 0.35, unchanged) is the procedural cloud-band opacity.
|
|
82
|
+
- **A model that throws while rendering is now reported instead of vanishing (#1588).** `ModelFallbackBoundary` swapped in its fallback (usually nothing) and stayed silent: `probes.fallbacks` stayed empty, no warning was logged, and an entity simply disappeared. It now reports a new `renderError` fallback cause on every failed render and warns once with the model url and the caught error.
|
|
83
|
+
- **A failing or slow-loading piece of a part composition no longer erases the whole actor (#1588).** Parts, bone attachments, and material-map appliers each mount behind their own error/Suspense boundary, so one bad piece drops itself — the base mesh and healthy siblings still render.
|
|
84
|
+
- **`EntityModel` / `useModelAnimation` / `PartMotionRig` no longer require a running game (#1588).** They read the game context optionally, so `EntityPreview` renders a model in a character screen, inspector, or preview page instead of throwing `useGameContext must be used within <GameProvider>` and rendering nothing.
|
|
85
|
+
- **A model `animation` config naming clips the rig does not have now warns instead of silently posing wrong.** An explicit `ModelConfig.animation` always beats `"auto"`, and a missing state clip fell back to the GLB's *first* clip while missing one-shots were dropped — so a mistyped or wrong-pack clip name left an actor looping an arbitrary animation (on a KayKit rig, `1H_Melee_Attack_Chop`) with no death or hit reaction, and nothing in the logs. `useModelAnimation` now warns once per mount naming the missing clips plus the rig's real clip names, and points at `animation: "auto"`. No behavior change for a config whose clips all resolve.
|
|
86
|
+
- **City-scale player movement no longer freezes `pose` at ~3 fps (#1517).** Broadphase reach is split into capped horizontal vs vertical extents (tower height no longer inflates XZ/Y queries), `objectStore.inBox` bails to a linear object scan when the 1 m cell volume is huge, dense mesh-box colliders collapse to outer AABBs for walking, and `movement.frozen` skips the gather (seated drivers).
|
|
87
|
+
- **Generated intersections are compact carriageway unions, not plaza discs (#1511).**
|
|
88
|
+
Apron pull-back is the projected crossing half-width only (opposite through-arms are ignored; curb
|
|
89
|
+
return is no longer double-counted into the mouth distance). Exterior corners get small width-clamped
|
|
90
|
+
returns; T far sides stay straight through-curbs; degree-2 turns form L-unions via the outer edge
|
|
91
|
+
intersection. Sidewalk aprons offset along the local ring normal (not radially from the node), so
|
|
92
|
+
corners no longer become giant octagonal bands. `buildIntersectionMarkings` still joins degree-2
|
|
93
|
+
offsets and places stop lines at multi-arm mouths. Playground inspection hides buildings and does not
|
|
94
|
+
render cuboid traffic. Deterministic fixture: `bun run shoot --fixture StreetGeometryPreview`.
|
|
95
|
+
- **Managed capture no longer steals Windows focus or waits on a background supervisor.** Daemon start
|
|
96
|
+
launches hidden Chrome directly, while Git, taskkill, Vite, Chrome, and ffmpeg subprocesses all suppress
|
|
97
|
+
console windows. Website query state hydrates before Three.js boot, preserving fail-fast route capture.
|
|
98
|
+
Plot frontage is re-resolved after block-corner subdivision so buildings retain the correct street, and
|
|
99
|
+
`lots.variety` now controls the deterministic plot-tier mix.
|
|
100
|
+
- **`extractBlocks` warns (dev-mode) instead of silently collapsing on wandered / arc-filleted streets (#1502).** Proximity welding assumes near-aligned centerlines; wandered arc-filleted streets weld into a degenerate face set (a real case went 38 streets → 2 faces with no signal, so the block fabric looked adoptable but was empty). `extractBlocks` now emits a `console.warn` when the extracted face count is implausibly low for the street count (≥8 streets yielding fewer than street-count/4 faces), pointing callers at `extractGraphBlocks` — which traces faces from the exact street graph and is robust against the centerlines that defeat welding. No-ops in a production build; no runtime behavior change.
|
|
101
|
+
- **`assets pull` / `assets add` default output dir now lands where the dev server serves models** (`@jgengine/assets` CLI, #1339) — inside the monorepo a bare `pull`/`add` previously wrote to a cwd-relative `public/`, so running it under `packages/assets` (or any subdir) dropped GLBs into a folder no game serves. It now defaults to the served root `apps/dev/public` when that exists (falling back to the historical cwd-relative `public` for out-of-monorepo consumers), so pulled bytes land in `apps/dev/public/models/<pack>` where the runner reads them. `--dir` still overrides.
|
|
102
|
+
- **The missing-model diagnostic now names the served target dir, and a bare `assets reindex` no longer disagrees with `assets pull` (#1499).** The runtime 404 / HTML-fallback diagnostic (`classifyAssetResponse` in `@jgengine/core/scene/assetDiagnostics`) told you to run `assets pull <source>` but never named where the bytes must land; it now points at the dev server's served models dir (`apps/dev/public/models` in the monorepo) and states no manual copy is needed. Completing #1339, a bare `assets reindex` / `assets reindex-sprites` (no dir arg) now defaults to that same served root via `resolveDefaultReindexDir` instead of a cwd-relative `public/`, so it can never scan an empty dir while `pull` writes to the served one. Explicit dir args are unchanged.
|
|
103
|
+
|
|
104
|
+
### Added
|
|
105
|
+
|
|
106
|
+
- **An `rng` that falls back to `Math.random` now says so (#1582)** — twelve core primitives (`createMobBrain`, `applyStatus`, `rollCheck`, `autoTarget`, `rollCapture`, `createLootRegistry`, `createRunDraft`, `createResourceNode`, `worldItem`, `selectLane`, `assignRoles`, `scheduleDelivery`) took an injectable `rng` and defaulted it silently, so a caller who forgot to pass `ctx.rng` got a simulation that could not replay, lockstep, roll back, or be pinned by a test — with no signal. They now route through `resolveRng` (`@jgengine/core/random/resolveRng`), which warns once per call site in dev and stays silent in production. The fallback itself is unchanged, so no behavior moves; four sites whose `rng` was a defaulted parameter now take it as optional, which is source-compatible.
|
|
107
|
+
- **The two parts a game front-end could not reach for (#1577)** — `useMenuRouter(root)` (`@jgengine/react/menuRouter`) is the screen stack behind title → save select → settings → credits: `open`/`back`/`replace`/`reset`, `path`, and Escape-to-back on by default; `menuStackReducer` exposes the same transition as a pure function. For credits, `creditsForSourceIds(ids)` (`@jgengine/assets/credits`) turns the pack ids a game already declares to `buildCatalog` into licensed, attributed sections, `mergeCredits`/`groupCredits` (`@jgengine/core/game/credits`) fold and shape them, and `CreditsScreen` (`@jgengine/react/creditsScreen`) renders a `CreditsDocument` HudTheme-skinned. Attribution is now derived from what the game ships instead of hand-maintained.
|
|
108
|
+
- **`ResourcePool` and `ComboPoints` can hand their state back (#1575)** — both gain `snapshot()` / `restore(state)` over a plain-data `ResourcePoolSnapshot` / `ComboPointsSnapshot`, so a save file, a server host, or a test can carry them across a rebuild. `ResourcePool` also gains `retune({ max, regenPerSecond, decayPerSecond })`, so a buff or difficulty change no longer needs a fresh pool (regen/decay were frozen at construction). Additive: existing calls are unchanged.
|
|
109
|
+
- **`HudTheme` now covers the whole `--jg-*` vocabulary (#1573)** — new `surface`, `status`, `font`, and `rarity` groups plus `-deep`/glow palette companions, so `hudThemeVars(theme)` emits all 53 tokens the shipped components read instead of the 28 the bars/frames/slots needed. One theme spread on a HUD ancestor now skins the `@jgengine/react` primitives and the `registry/jgengine` blocks together; previously a game had to author two disjoint token sets. New `hudTheme({ ... })` fills a theme from per-group partials, and `deriveHudTheme({ accent, surface })` builds a coherent full theme (including frames, troughs, and slots) from a seed pair. `ember` / `synthwave` / `fieldkit` join `HUD_THEME_PRESETS` with their registry values unchanged. Additive: existing themes and every rendered surface are unaffected.
|
|
110
|
+
- **Runtime-mutable arcade car tuning + hops (#1550)** — `KinematicVehicle.retune(tuning)` swaps a vehicle's whole tuning block while keeping position, heading, velocity and gear state, for vehicles whose stats change during play (installed parts, damage, debuffs). New tuning fields `hopGravity` (enables `vehicle.hop(impulse)`, reported as `step.airOffset` / `step.airborne` and applied on top of the ground sample by `tickDrivableVehicle`), `coastDeceleration` (flat coast road-load, versus `rollingResistance`'s proportional decay), and `reverseForceScale` (reverse push on the arcade path, previously reachable only via a full `chassis` block). New per-tick modifier `brakeScale`. All additive: omit them and every existing vehicle behaves exactly as before.
|
|
111
|
+
- **`assets pull starter` (#1550)** — one meta-id that pulls every pack the starter catalog needs (`kaykit-adventurers`, `kaykit-dungeon`, `kaykit-furniture`, `kaykit-city-builder`, `quaternius-stylized-nature`) into `public/models`, so a game does not have to name five pack ids to make `asset:` references resolve.
|
|
112
|
+
- **`CastConfig.moveDeadzone` (#1550)** — per-tick movement at or below this is treated as the caster's noise floor and not accumulated toward `moveTolerance`. A 3D game's pose jitters every frame from terrain-following ground snap, and because `moveTolerance` accumulates, feeding those raw deltas in silently cancels casts the player never interrupted. Declare the noise floor once in `begin` instead of filtering at every `tick` call site. Defaults to `0`, so existing callers are unchanged.
|
|
113
|
+
- **`LootDrop` + `lootDropsOf(data)` (#1550)** — the granted-reward row is now a named type, and `lootDropsOf` safely reads the drops out of a `loot.granted` payload that arrived as `unknown` (a `FeedEntry.data`, a replicated event, a persisted row), returning `[]` for anything not shaped like a loot grant and discarding malformed rows. Replaces the `entry.data as { drops?: ... }` cast that four call sites — three games plus `ToastStack`'s own default renderer — were each writing by hand.
|
|
114
|
+
- **`createRegenShield({ pool })` — stat-backed shields (#1550).** A `RegenShieldPool` port (`current`/`max`/`set`) lets a regen shield read and write storage the game already owns — an entity stat, a replicated store — instead of keeping the value privately, which is what blocked adoption in games whose shields live in the stat system that damage, HUD and replication all read. A pool-backed shield also detects *external* decreases between ticks and restarts its regen grace period, replacing per-entity snapshot bookkeeping. Adds `noteDamage()` (restart the grace period without moving the value, for hits the shield cannot see) and per-tick `tick(dt, { regenPerSecond, regenDelayMs })` rate overrides for talent- or buff-derived rates. The grace period now advances on simulated `dt` rather than wall clock, so a paused game no longer regenerates.
|
|
115
|
+
- **`ctx.rng` + `createGameContext({ seed, rng })` (#1545)** — deterministic per-world `[0,1)` stream for sim paths; same seed → same sequence across worlds/hosts.
|
|
116
|
+
- **Post `aa: "smaa" | "msaa" | false` (#1545)** — SMAA default on post chains (before OutputPass) for alpha-tested edge crawl.
|
|
117
|
+
- **Procedural sky IBL + directional `cascades` CSM (#1545)** — `EnvironmentLighting` bakes sky/ground/sun into PMREM; `cascades > 1` on directional lighting mounts cascaded shadows.
|
|
118
|
+
- **`grounded(ctx, x, z)` (#1543)** — returns `[x, groundY, z]` from the live world surface; stop re-deriving `[x, ctx.world.groundHeightAt(x, z), z]`. Import from `@jgengine/core/world/grounded` or `@jgengine/shell/gameKit`.
|
|
119
|
+
- **`DEFAULT_WALK_CODES` (#1543)** — stock WASD + Space map for the shell walk controller (`@jgengine/shell/shellMovement`, re-exported from `gameKit`).
|
|
120
|
+
- **`SETTINGS_TRIGGER_CLASSNAME` + default `SettingsTrigger` skin (#1543)** — HudTheme-token 8×8 chrome button; omit `className` for the stock look.
|
|
121
|
+
- **City generation now enforces plot clearance and planar streets (#1454).** Building plots clear
|
|
122
|
+
one another and street corridors, building massing fits its plot, landmark footprints avoid roads
|
|
123
|
+
and overlap, and generated street chords and branches cannot cross without a junction. Interior
|
|
124
|
+
arterial stubs are demoted, dead ends receive cul-de-sac bulbs, and the playground exposes a
|
|
125
|
+
linkable plan view plus plot spacing and variety controls.
|
|
126
|
+
|
|
127
|
+
- **`lifecycle: "always-live"` sentinel + `GameLifecycle` type (#1337).** `defineGame`'s `lifecycle`
|
|
128
|
+
now accepts the string sentinel `"always-live"` alongside a `LifecycleConfig`. It is an explicit,
|
|
129
|
+
truthful declaration that the game has no menu/pause/end screens and runs live from boot — runtime
|
|
130
|
+
behavior is identical to declaring no lifecycle at all (no phase sync, no start/restart commands),
|
|
131
|
+
but the intent is now *stated*, not implied by silence. `defineGameDefinition` resolves the sentinel
|
|
132
|
+
to `undefined`, so downstream consumers still only ever see a real `LifecycleConfig` or nothing.
|
|
133
|
+
`@jgengine/core/gameplay` exports the `GameLifecycle` union type. Pairs with the new
|
|
134
|
+
`check-game-shape` run-phase requirement (see Migrate).
|
|
135
|
+
- **`debug_snapshot().probes.textureErrors` surfaces in-GLB texture-load failures (#1342).**
|
|
136
|
+
`@jgengine/core/devtools/textureErrors` adds an allocation-aware collector
|
|
137
|
+
(`armTextureErrors`/`reportTextureLoadError`/`resetTextureErrors`/`textureErrorsSnapshot`) that the
|
|
138
|
+
shell's shared GLB loading manager feeds on every texture/image `onError`. Previously only whole-model
|
|
139
|
+
catalog fallbacks (missing mapping/pack/scene) reached the `fallbacks` probe, so a model that resolved
|
|
140
|
+
but whose textures 404'd read as a clean scene to `debug_snapshot` — the exact signal `jgengine-verify`
|
|
141
|
+
tells agents to trust. The new `probes.textureErrors` list (`{ url, count }[]`) makes those failures
|
|
142
|
+
visible so a texture-404'd scene can be treated like a model fallback. Dev-only (armed with devtools);
|
|
143
|
+
a pure no-op in production.
|
|
144
|
+
- **Result/option types of public barrel functions are now re-exported (#1319).** The
|
|
145
|
+
`@jgengine/core/gameplay` barrel re-exports `ChargeResult`, `ChargeOptions`, and `Overdraft`
|
|
146
|
+
alongside `charge`/`chargeAll`/`canAfford`, and `@jgengine/core/combat` re-exports
|
|
147
|
+
`DefenseResolution` (from `resolveDefense`) and `ResolvedShot` (from `resolveShot`). Consumers can
|
|
148
|
+
now name these return/parameter types directly instead of resorting to `ReturnType<>`/`Parameters<>`
|
|
149
|
+
gymnastics. Purely additive — no runtime or signature change.
|
|
150
|
+
- **Conflict-aware key-rebinding session.** `@jgengine/core/input/rebindSession` adds
|
|
151
|
+
`createRebindSession({ actions | input, overrides?, now? })` — an observable key-remap
|
|
152
|
+
editor over the existing action-binding model: it tracks the effective binding per action
|
|
153
|
+
(default merged with `applyBindingOverrides`), exposes `rows()` with key glyphs + per-row
|
|
154
|
+
conflict sets, groups every clash via `conflicts()`, drives click-to-capture rebinds
|
|
155
|
+
(`beginCapture`/`capture`/`cancelCapture`, codes normalized), resets to defaults, and hands
|
|
156
|
+
back `BindingOverrides` to persist, with `snapshot`/`restore`. React `KeybindingMenu` +
|
|
157
|
+
`useRebindSession` (`@jgengine/react`) are the drop-in controls-settings surface — one row
|
|
158
|
+
per action with key glyph, conflict badges, per-row and Reset-all, HudTheme-skinnable.
|
|
159
|
+
Demo: `key-rebinding`.
|
|
160
|
+
- **Vendor / shop stock + grid.** `@jgengine/core/economy/shopStock` adds a serializable, observable
|
|
161
|
+
`createShopStock({ entries })` — entries carry a free-string `kind`, a `price` in a free-string
|
|
162
|
+
`currency`, a finite or unlimited (`qty: null`) count, and an optional `sellPrice`. `buy`/`sell`
|
|
163
|
+
operate over a **caller-owned** `WalletState` (reusing the existing `wallet` model — `charge`/`grant`/
|
|
164
|
+
`canAfford`), returning the debited/credited wallet for the caller to adopt; plus `restock`/`setPrice`/
|
|
165
|
+
`add`/`remove`/`list`/`get`/`canAfford`/`subscribe`/`snapshot`/`restore`. `@jgengine/react`'s `ShopGrid`
|
|
166
|
+
host + `useShopStock` hook render it as a token-themed grid of item cards (icon, price with currency
|
|
167
|
+
glyph, stock count or "∞", afford-aware Buy, optional Sell) with a wallet balance readout.
|
|
168
|
+
|
|
169
|
+
- **Save-slot / profile select menu.** `@jgengine/core/game/saveSlots` adds `createSaveSlots(config)` — a
|
|
170
|
+
serializable, observable index of per-slot *display* metadata (`{ id, name?, empty, savedAt?, meta }`
|
|
171
|
+
with free-string `meta` the game fills: level, playtime, chapter, thumbnail ref, …) that complements
|
|
172
|
+
`createSaveStore` (which owns the real payload). Ops: `write`/`clear`/`rename`/`get`/`list`/`mostRecent`
|
|
173
|
+
(powers Continue) plus `subscribe`/`snapshot`/`restore`. `@jgengine/react/saveSlots` ships the drop-in
|
|
174
|
+
`SaveSlotMenu` host (+ `useSaveSlots` hook) rendering the index as New / Continue / Load / Delete cards
|
|
175
|
+
with meta chips and relative save times, HudTheme-skinnable. Demo: `save-slots`.
|
|
176
|
+
- **Event-log / kill-feed ticker.** `@jgengine/core/game/eventTicker` adds a thin, serializable,
|
|
177
|
+
observable `createEventTicker({ now?, limit?, ttlMs? })` over the existing `appendFeed`/`pruneFeed`
|
|
178
|
+
helpers: a single rolling, count-capped, time-fading list of free-string `{ kind, text, icon? }`
|
|
179
|
+
entries (`push`/`recent`-with-`fade`/`entries`/`clear`/`subscribe`/`snapshot`/`restore`). `recent()`
|
|
180
|
+
prunes expired entries and returns them newest-first with a `fade` `0..1` (age / `ttlMs`). The React
|
|
181
|
+
`KillFeed` host + `useEventTicker` hook render it as a fading, newest-on-top stack of per-kind
|
|
182
|
+
iconned, accent-colored rows over HudTheme tokens; `kill-feed` demo included.
|
|
183
|
+
- **Interaction prompt registry ("Press E to …").** `@jgengine/core/world` adds `createPromptRegistry()` — a thin
|
|
184
|
+
observable, serializable store over the existing `resolveActivePrompt` resolver that owns positioned proximity
|
|
185
|
+
prompts (`register`/`update`/`unregister`/`clear`/`all`), resolves the nearest in-range prompt as the player moves,
|
|
186
|
+
and notifies subscribers only when the active prompt *changes* (so a HUD does not thrash per frame), with
|
|
187
|
+
`snapshot`/`restore`. React `@jgengine/react` adds `InteractionPrompt` (a screen-anchored callout rendering the active
|
|
188
|
+
prompt — key cap + label, gauge hold bar, or plain label, theme- and per-prompt-accent skinnable) and
|
|
189
|
+
`useInteractionPrompt(registry, playerPosition)`. Demo: `interaction-prompt`.
|
|
190
|
+
- **Seeded trauma-based camera shake.** `@jgengine/core/vfx/cameraShake` adds `createCameraShake(config?)` —
|
|
191
|
+
a serializable, deterministic camera-shake/impulse controller: `add(amount, kind?)` raises trauma `0..1`
|
|
192
|
+
on impacts (free-string `kind` the game styles), `update(dt)` decays it, and `offset()` returns a pooled
|
|
193
|
+
`{ x, y, z, pitch, yaw, roll }` kick (`trauma^exponent` × per-axis maxima × seeded value-noise) with
|
|
194
|
+
snapshot/restore. `@jgengine/react` ships `CameraShakeMeter`/`useCameraShake` (trauma meter + kind label)
|
|
195
|
+
and `@jgengine/shell` ships `ControllerCameraShake`, an R3F consumer that applies the offset to the active
|
|
196
|
+
camera each frame so the view visibly shakes. Demo: `camera-shake`.
|
|
197
|
+
|
|
198
|
+
- **Observable wave/spawn runner + drop-in HUD.** `@jgengine/core/ai/waveRunner` adds
|
|
199
|
+
`createWaveRunner(config)` — a thin, stateful, observable wrapper over the seeded `spawnDirector`
|
|
200
|
+
that owns a `SpawnDirectorState`, ticks it from `update(dt, ctx?)`, forwards each `SpawnRequest` to an
|
|
201
|
+
optional `onSpawn` sink (so the model never instantiates entities), and exposes a pooled `view()`
|
|
202
|
+
readout (1-based `WAVE N`, wave progress `0..1`, budget/alert, spawned-this-wave/total, done) plus
|
|
203
|
+
`forceNextWave`/`raiseAlert`/`subscribe`/`snapshot`/`restore`. `@jgengine/react/waveHud` adds
|
|
204
|
+
`WaveHud`/`useWaveRunner` — a theme-skinnable panel with a big WAVE N label, wave-progress bar, and
|
|
205
|
+
spawn/budget/alert readouts. Spawn-entry kinds stay free strings the runner never interprets.
|
|
206
|
+
- **Count-based combo / multiplier meter.** `@jgengine/core/combat/comboMeter` adds
|
|
207
|
+
`createComboMeter({ windowMs, tiers?, dropStep?, multiplierPerTier? })` — an integer hit chain that
|
|
208
|
+
climbs on `hit(kind?)`, resets a decay window each hit, and drops (to 0, or by `dropStep`) when the
|
|
209
|
+
window elapses, driven by an injected `now` and/or `update(dt)`. Free-string `tiers` derive the active
|
|
210
|
+
`tier()` and a score `multiplier()`, with `peak()`, a pooled `view()`, `subscribe`, and
|
|
211
|
+
`snapshot`/`restore`. React `@jgengine/react/comboMeter` ships `ComboMeterHud` (big live count, tier
|
|
212
|
+
label, draining window bar, multiplier — per-tier colored from a caller map) and a `useComboMeter` hook.
|
|
213
|
+
- **Off-screen objective / waypoint markers.** `@jgengine/core/ui/screenMarkers` adds a serializable,
|
|
214
|
+
observable `createWaypointTracker()` (`set`/`remove`/`clear`/`all`/`subscribe`/`snapshot`/`restore`,
|
|
215
|
+
free-string `kind`s the game styles) plus a pure, allocation-aware `layoutScreenMarker(projection,
|
|
216
|
+
viewport, options?)` that passes an on-screen point through and clamps an off-screen or behind-camera
|
|
217
|
+
point to the viewport edge with a bearing `angle` — the edge-clamp/arrow half that `layoutEntityFrames`
|
|
218
|
+
culls. `@jgengine/react`'s `WaypointMarkers` renders on-screen pins and off-screen directional arrows
|
|
219
|
+
with distance labels over any caller-owned `project` (e.g. shell `useWorldProjection`), skinnable via
|
|
220
|
+
HudTheme tokens and a per-`kind` color map. Demo: `waypoint-markers`.
|
|
221
|
+
|
|
222
|
+
- **Scoreboard / leaderboard ranking.** `@jgengine/core/game/leaderboardRank` adds `rankLeaderboard(rows, options)`
|
|
223
|
+
— a pure, allocation-bounded selector that turns raw leaderboard rows (accepts `LeaderboardRow[]` straight from
|
|
224
|
+
`createLeaderboard().snapshot()`) into a render-ready ranked table: stable value sort (`desc`/`asc`), correct tie
|
|
225
|
+
handling (`standard` → 1,2,2,4; `dense` → 1,2,2,3), `isTie`/`isLocal` flags via `highlightUserId`, and top-N `limit`
|
|
226
|
+
— plus `medalFor(rank)` returning free-string `gold`/`silver`/`bronze` podium tokens. `@jgengine/react`'s reskinnable
|
|
227
|
+
`Scoreboard` table renders it with medal-colored podium icons, a highlighted local row, and HudTheme `--jg-*` tokens.
|
|
228
|
+
- **Talent/upgrade tree from any unlock rule.** `@jgengine/core/game/talentTreeView` adds
|
|
229
|
+
`talentTreeViewFrom(nodes, status, totals?)` — a general builder that places a node graph (branch/tier
|
|
230
|
+
layout, prerequisite edges, learned/available/locked/maxed state) from a caller-supplied per-node
|
|
231
|
+
`{ rank, allocatable }`, so unlocks can come from a currency threshold, a level, a quest flag, or
|
|
232
|
+
nothing — not only point-spend. `talentTreeView(nodes, tree)` is now the point-spend adapter over it.
|
|
233
|
+
The React `TalentTree` widget accepts a precomputed `view` (plus `showPoints`) alongside the existing
|
|
234
|
+
`nodes`+`tree`, so the same widget renders a buy-with-points talent tree *or* a money-gated upgrade tree
|
|
235
|
+
with no renderer change.
|
|
236
|
+
- **Floating combat text / damage numbers.** `@jgengine/core/ui/floatingText`' `createFloatingTextField` is a
|
|
237
|
+
genre-agnostic, deterministic, allocation-aware field of world-anchored text pops (damage, crits, heals, XP/gold,
|
|
238
|
+
status, barks): `emit({ position, text, kind?, color?, size?, rise?, drift?, lifetime? })`, `update(dt)` (rise +
|
|
239
|
+
seeded drift + fade + swap-remove reaping), `active()` views, a capped pool that recycles the oldest, `clear`,
|
|
240
|
+
`subscribe`, and serializable `snapshot`/`restore`. `kind` is a free string the presentation styles — no combat
|
|
241
|
+
coupling. New `@jgengine/react`: `layoutFloatingText` (pure project/cull/depth-sort over the `EntityFrames`
|
|
242
|
+
projector seam) and `FloatingText` (per-`kind` skinnable overlay). New `@jgengine/shell/vfx/WorldFloatingText`
|
|
243
|
+
binds the live R3F camera and advances the field. New `floating-text` demo.
|
|
244
|
+
- **Codex / bestiary.** `@jgengine/core/game/codex`' `createCodex` tracks discovery over a fixed set of
|
|
245
|
+
defined entries — `discover`, `isDiscovered`, category-filtered `list`, `categories`, `discoveredCount`/
|
|
246
|
+
`total`/`completion`, an `onDiscover` seam, secret masking, and serializable `snapshot`/`restore`; the view
|
|
247
|
+
list keeps a stable identity for React. New `@jgengine/react` `Codex` gallery: category tabs, discovered vs.
|
|
248
|
+
locked cards (secret entries masked until found), and a completion header. First adopter: the apps/dev
|
|
249
|
+
`codex` demo.
|
|
250
|
+
- **Generated street elevation** (`@jgengine/core/world/streetGenerator`) — new optional `elevation` (0..1 relief dial, default 0 = flat, byte-identical output) and `maxGrade` (default 0.07) rules: a seeded smooth field emits per-point `Street.heights` (grade-capped, loop-continuous across a circuit's start/finish) and a shared `StreetNetwork.elevationAt(x,z)` so renderers drape roads, junction welds, sidewalks, and building bases off one consistent surface. Distinct from `context.heightAt` terrain (bridges/tunnels unchanged).
|
|
251
|
+
- **Per-corner radius classes on circuits** (`@jgengine/core/world/streetGenerator`) — circuit corners now fillet from a seeded radius mix (hairpins near `minCurveRadius`, standard corners at 2-4x, 1-3 sweepers per lap at 5-8x, inversely correlated with turn magnitude) so track layouts show genuinely different corner radii instead of one pinched minimum.
|
|
252
|
+
- **`compactness` circuit dial — space-filling kart-style tracks** (`@jgengine/core/world/streetGenerator`) — 0..1 (default 0 = the open flowing hull loop, byte-identical): rising compactness lays the lap as the wall-follower cycle around a seeded direction-biased spanning tree on a corridor grid — provably one self-avoiding loop that folds back through its own footprint with parallel corridors at a clearance-safe pitch, switchback mazes, leaf hairpins, and a ≥3-pitch main straight — then runs the same spline/curvature-floor pipeline. At 1 a default-seed lap triples in length with 19-37 corners and ~61-77% footprint usage.
|
|
253
|
+
- **`blockFill` density dial** (`@jgengine/core/world/cityGenerator` / `buildingLots`) — 0..1 (default 0.45): rising fill packs street frontage toward a touching streetwall, deepens plots toward the block spine, and reserves fewer whole blocks as parks — Manhattan-dense cities at 1, sparse suburbs near 0. Block interiors are never filled with street-less buildings; every plot fronts a street.
|
|
254
|
+
- **Plot-first city generator** (`@jgengine/core/world/cityGenerator`) — `generateCity` now extracts closed blocks from the street network's exact graph (`extractGraphBlocks` in `world/cityBlocks`) and subdivides every block's frontage into size-tiered polygonal PLOTS (`small`/`medium`/`large`/`grand` — many different plot sizes per city) via the new `deriveCityPlots`. `GeneratedCity` gains `plots` (`CityPlot[]`, `plots[i]` pairs with `lots[i]`) and `parks` (whole-block open space). The landmark cluster-merge pass and the street-less interior fill are gone: a landmark is just a grand plot (same pass, biggest tier — `content.landmarks` dials its share, `0` disables), building class now follows plot size (wide plots pull towers/slabs, narrow plots pull rowhouses/houses), and every building stands on street-fronting land by construction.
|
|
255
|
+
- **`outline` street dial** (`@jgengine/core/world/streetGenerator`) — 0..1 (default 0 = the legacy full-rectangle footprint, byte-identical): rising values clip the lattice to a seeded organic boundary blob (radial harmonics), so every seed grows a differently-shaped city — lobes, bays, shaved corners — reduced to its largest connected component. `generateCity` defaults it to 0.4.
|
|
256
|
+
- **Residential branch streets and cul-de-sacs** (`@jgengine/core/world/streetGenerator` / `cityGenerator`) — new `residentialBranches` rule (0..1 share, default 0 = legacy alleys byte-identically) grows two spur kinds off the mains under the `branching` dial: service alleys (the legacy lane stubs) and RESIDENTIAL BRANCHES — winding 1–3-segment street-level side streets that may fork once, never cross existing roads, end in a turning-bulb cul-de-sac, and are lined with small/medium house plots down both sides by the plot pass (dead-end corridors carry their street/level through `extractGraphBlocks`). Dead-ending chains are never classified arterial, so no bulb-capped boulevards. `generateCity` defaults `branching` to 0.4 and `residentialBranches` to 0.6.
|
|
257
|
+
- **`TrimmedIntersections.junctionIndices`** (`@jgengine/core/world/roads`) — each welded junction surface now reports which input junction it belongs to, so renderers can color crossing patches by the junction's street level instead of one global junction color.
|
|
258
|
+
- **`trimBandAtJunctions`** (`@jgengine/core/world/roads`) — clip a sidewalk/parallel band polyline out of every junction apron (arm-derived radius widened by the band's half-width + clearance), returning the surviving sub-paths — so sidewalks end at crossings instead of sailing through them. The playground consumes it for `Street.sidewalks`.
|
|
259
|
+
- **Shared map drawings (multiplayer).** `@jgengine/core/world/sharedAnnotations`' `createSharedAnnotations`
|
|
260
|
+
makes a map annotation layer collaborative: local `addStroke`/`addShape`/`addNote`/`remove`/`clear` apply
|
|
261
|
+
then broadcast a serializable edit over the party feed (the same seam `createPingSystem` pings ride), and
|
|
262
|
+
`apply` mirrors inbound edits from other players while dropping our own echoes. Ids are globally unique per
|
|
263
|
+
client (`owner:n`) so two players' strokes never collide; transport-agnostic (`ANNOTATION_FEED_ACTION` +
|
|
264
|
+
any replicated feed). Pings were already shared via `createPingSystem`; this completes the pair. Verified by
|
|
265
|
+
a two-client sync test.
|
|
266
|
+
- **Terrain surface shading + layered grass wind** (`@jgengine/shell/terrain`, #1373) — plain (untextured) ground now reads as terrain instead of a two-tone gradient: `buildGroundGeometry`-based grounds (`ProceduralGround`, field ground) gain slope-aware darkening/desaturation and seeded, deterministic hash mottling, tunable via the new optional `TerrainSurfaceColorOptions` (`FieldGroundOptions.surface`; defaults in `DEFAULT_TERRAIN_SURFACE_COLOR`, strengths `0` restore the old flat lerp). Grass wind gains a layered rolling gust field (`GrassWindOptions.layered`, default on — three phase-offset directional waves so visible fronts sweep the meadow; `false` restores the single sine) plus a warm lit-tip sheen, and the default grass distance fade widens from 35–95 m to 55–150 m so meadows read expansive at no extra instance cost. `createProceduralGroundGeometry` now delegates to the shared ground builder (behavior-identical geometry, minus the duplication).
|
|
267
|
+
- **Debounced control commits: `useDebouncedCommit`** (`@jgengine/react/useDebouncedCommit`, #1372) — a live-mirrored, trailing-debounced commit binding for slider/number/color/text controls whose commit path is expensive (scene-document patches that regenerate streets, scatter, or grass). The control renders from the local mirror for instant thumb/readout feedback; the real commit fires once per pause (default 180 ms) and flushes on release/blur/unmount; external changes (undo, RPC echo) resync the mirror unless a drag is in flight. The editor's inspector fields (`SchemaInspector`, `SliderRow`/`NumberField`, vegetation and scatter-coverage density) and the shell settings sliders now commit through it, so one drag lands one patch instead of ~50.
|
|
268
|
+
- **Stage / objective banner.** `@jgengine/core/ui/objectiveBanner`' `createObjectiveBanner` is a genre-agnostic,
|
|
269
|
+
serializable announcement queue: a game calls `announce({ title, subtitle?, kind?, holdMs?, inMs?, outMs? })` and the
|
|
270
|
+
controller flies the classic transient centered title stamp ("WAVE 3", "VICTORY", "OBJECTIVE COMPLETE") in, holds it,
|
|
271
|
+
and fades it out — one banner at a time — on an injected clock (`advance()`), exposing the current banner plus its
|
|
272
|
+
phase (`in`/`hold`/`out`) and `progress` `0..1` for a renderer to animate. `kind` is a free label the game styles; the
|
|
273
|
+
model never interprets it. Allocation-aware (the `current()` view is pooled) with `subscribe` + `snapshot`/`restore`.
|
|
274
|
+
`@jgengine/react`' `ObjectiveBannerHost` subscribes, drives the clock per frame, and maps phase + progress to
|
|
275
|
+
opacity/scale/translate, HudTheme-skinnable and colorable per `kind` via `kindThemes`. Demo: `objective-banner`.
|
|
276
|
+
- **Talent / skill-tree widget.** `@jgengine/react`'s `TalentTree` is a drop-in widget over the existing
|
|
277
|
+
talent model (`@jgengine/core/game/talents`): pass the node definitions plus a live `createTalentTree`
|
|
278
|
+
instance and it lays nodes out by branch column and prerequisite-depth tier, draws SVG prerequisite
|
|
279
|
+
edges, styles each node learned/available/locked/maxed with an icon + rank badge, and fires
|
|
280
|
+
`onLearn(nodeId)` on allocatable clicks. A new thin core selector `@jgengine/core/game/talentTreeView`
|
|
281
|
+
(`talentTreeView`) flattens the model into a serializable per-node render view (tier, state, met/unmet
|
|
282
|
+
edges) so the widget never re-derives topology or eligibility. HudTheme-token skinned; node ids and
|
|
283
|
+
branches stay opaque game data.
|
|
284
|
+
- **Countdown / timer HUD.** `@jgengine/core/time/timerSet`' `createTimerSet` is a serializable set of named
|
|
285
|
+
countdown/countup timers on an injected clock — one primitive for round timers, respawn clocks, and ability
|
|
286
|
+
cooldown/charge (identical mechanics; `id`/labels are free strings the engine never interprets). Start, pause,
|
|
287
|
+
resume, stop, reset, and read `{ remainingMs, elapsedMs, durationMs, progress01, running, expired }` per timer
|
|
288
|
+
(allocation-aware `read(id, out)`), observe structural changes via `subscribe` and expiry edges via
|
|
289
|
+
`poll`/`onExpire`, and `snapshot`/`restore` round-trip through a save. `@jgengine/react`'s `TimerReadout`
|
|
290
|
+
(live digital mm:ss/m:ss.d) and `TimerRing` (SVG radial fill/drain), plus the `useTimerRead` per-frame hook,
|
|
291
|
+
render it HudTheme-skinned — no hand-rolled interval math. See the `countdown-timer` dev demo.
|
|
292
|
+
- **Branching dialogue UI.** `@jgengine/core/game/dialogueGraph`'s `createDialogueRun` / `selectDialogueView` add a
|
|
293
|
+
thin, serializable branching-conversation model over the existing `features.dialogue` open/close bridge — a graph of
|
|
294
|
+
nodes (free-string speaker + line + choices that name the node they lead to) with choose-to-advance traversal, visited
|
|
295
|
+
history, and snapshot/restore. `@jgengine/react`'s `DialogueView` (+ `useDialogueRun`) is the drop-in view: speaker
|
|
296
|
+
name, an optional portrait slot, the current line, and clickable response buttons that advance the run — a game passes
|
|
297
|
+
its dialogue graph and gets working node traversal and choice state with no hand-rolled walk. Genre-agnostic:
|
|
298
|
+
speaker/choice `kind` are free strings the model never interprets, surfaced as `data-*` for game styling; HudTheme-skinned
|
|
299
|
+
via `--jg-*` tokens. See the `dialogue` demo.
|
|
300
|
+
- **Damage-direction indicators.** `@jgengine/core/vfx/damageDirection`' `createDamageDirectionTracker` is a
|
|
301
|
+
serializable, allocation-aware "hit-from" brain: `registerHit({ angle, intensity?, kind? })` (angle in radians,
|
|
302
|
+
`0` = front, free-string `kind` never interpreted) turns each hit into a directional indicator that fades over a
|
|
303
|
+
duration on an injected clock, with `active()` reporting live indicators at eased current intensity, optional
|
|
304
|
+
same-direction merging, a bounded pool, `subscribe`, and `snapshot`/`restore`. `@jgengine/react`'s
|
|
305
|
+
`DamageDirectionOverlay` (+ `useDamageDirection` hook) renders the classic red arcs flaring around a center reticle
|
|
306
|
+
toward each recent hit, opacity/scale from intensity, color per `kind`/HudTheme, `pointer-events: none`. New
|
|
307
|
+
`damage-direction` dev demo. Genre-agnostic, renderer-free core.
|
|
308
|
+
- **Turnkey day-night cycle.** `@jgengine/core/time/dayNightCycle`' `createDayNightCycle` is a genre-agnostic,
|
|
309
|
+
serializable brain that advances a normalized day fraction on an injected clock and blends per-keyframe phase labels
|
|
310
|
+
and tint/light colors (`DayNightKeyframe` → `DayNightSample`), with `pause`/`play`/`setSpeed`/`setDayFraction`,
|
|
311
|
+
`subscribe`, `snapshot`/`restore`, and a `calendar()` adapter so it drops straight into any `{ dayFraction }` sky
|
|
312
|
+
seam. The shell's `@jgengine/shell/environment` adds `DayNightSky`, a drop-in R3F presenter that drives the existing
|
|
313
|
+
`SkyDome` shader and lights from the model each frame — wire one thing for a moving, color-graded day-night sky
|
|
314
|
+
instead of hand-rolling a clock plus a color lerp. `phase`/`kind` strings stay free-form; the model never interprets
|
|
315
|
+
them. New `day-night` dev demo.
|
|
316
|
+
- **Status-effect timeline HUD.** `@jgengine/react`'s `StatusEffectBar` renders active buffs/debuffs as a row of
|
|
317
|
+
painted icons, each with a radial countdown ring and stack-count badge, driven off the existing status model
|
|
318
|
+
(`combat/statusApplication`'s `StatusInstance`). A thin genre-agnostic core selector,
|
|
319
|
+
`@jgengine/core/combat`'s `toStatusEffectViews` / `toStatusEffectView` (+ `statusEffectRemainingFraction`),
|
|
320
|
+
adapts live `StatusInstance`s into serializable `StatusEffectView`s (`id`, free-string `kind`, `remainingMs`,
|
|
321
|
+
`durationMs`, `stacks`) so the widget re-derives no ring math or timers. `kind` is never interpreted by the engine —
|
|
322
|
+
the game supplies icon/color/label; icons come from the existing game-icons.net icon system. Demo: `status-effects`.
|
|
323
|
+
- **Cutscene / sequence director.** `@jgengine/core/scene/sequenceDirector`' `createSequenceDirector` is a
|
|
324
|
+
serializable, genre-agnostic timeline: an ordered list of typed cues (`{ atMs, kind, payload }`) advanced by one
|
|
325
|
+
injected clock, firing each cue once and in order as its time passes — even across a large `seek` — with
|
|
326
|
+
`play`/`pause`/`seek`/`skip`/`stop` and `snapshot`/`restore`. The director only schedules and emits cues via
|
|
327
|
+
`onCue`; it never interprets a `kind`, so the same primitive drives camera moves, dialogue, fades, or any game
|
|
328
|
+
event without a genre kit. `@jgengine/react`'s `useSequenceDirector` runs the per-frame tick loop and exposes
|
|
329
|
+
playhead/progress + controls, and `CutsceneLetterbox` is a reskinnable cinematic bars + caption + Skip overlay.
|
|
330
|
+
See the `cutscene` dev demo.
|
|
331
|
+
- **Modal / confirmation dialog system + reskinnable pause menu.** `@jgengine/core/ui/modalStack`'s
|
|
332
|
+
`createModalStack` is a genre-agnostic, serializable stack of opaque modal records (`push`/`pop`/`resolve` with a
|
|
333
|
+
free-form result, `top`/`isOpen`/`depth`, optional injected-clock auto-dismiss via `tick`/`timeRemaining`,
|
|
334
|
+
`subscribe`, `snapshot`/`restore`) that never interprets a modal's `kind` or result. `@jgengine/react`'s `ModalHost`
|
|
335
|
+
renders the top modal over a dimmed backdrop with a focus trap and Esc/backdrop-to-cancel, `ConfirmDialog` is a
|
|
336
|
+
generic two-button confirm/cancel dialog, and `PauseMenu` is a drop-in Resume + game-filled Settings/Quit slot list.
|
|
337
|
+
All `HudTheme`-token-driven common parts — the game owns final layout, terminology, and skin. Demo: `pause-menu`.
|
|
338
|
+
- **Screen-state effects (postfx).** `@jgengine/core/vfx/screenEffects`' `createScreenEffects` is a genre-agnostic,
|
|
339
|
+
serializable screen-feedback controller: a game triggers transient full-screen flashes and edge vignettes
|
|
340
|
+
(`flash`/`vignette`) or sustained, optionally oscillating tints (`pulse`, e.g. a low-health breathe) — each a
|
|
341
|
+
free-string `kind` the game styles, plus color, peak intensity, easing, and duration. It is clock-driven
|
|
342
|
+
(`advance()` against an injected `now`) and allocation-aware (the `composite()` array and entries are pooled, so
|
|
343
|
+
steady-state ticking never allocates), with `subscribe` + `snapshot`/`restore`. `@jgengine/shell/postfx`'
|
|
344
|
+
`ScreenEffectsOverlay` subscribes to the model and paints the composite as pointer-transparent color-grade layers
|
|
345
|
+
over the canvas. Demo: `screen-effects`. The model never interprets `kind`; flash/vignette/pulse are just
|
|
346
|
+
parameterizations of the same data (region shape, decay easing, sustained oscillation).
|
|
347
|
+
- **Coach-marks / tutorial hints.** `@jgengine/core/ui/coachMarks`' `createCoachMarkSequence` is a genre-agnostic
|
|
348
|
+
onboarding model: an ordered list of `CoachMarkStep`s (title, body, optional `anchor`/`placement`, and a data-first
|
|
349
|
+
string `condition`), a persisted "seen" set so completed hints never re-show, condition-gating via `satisfy`/
|
|
350
|
+
`setSatisfied`, `current`/`advance`/`dismiss`/`skipAll`, `isComplete`/`remaining`, `subscribe`, and serializable
|
|
351
|
+
`snapshot`/`restore`. New `@jgengine/react`: `useCoachMarks` plus `CoachMark` and `CoachMarkHost` (anchored or
|
|
352
|
+
centered callout with an "N of M" counter, a dimmed spotlight backdrop, Next/Skip, and a `CoachMarkTheme` reskin).
|
|
353
|
+
New `coach-marks` demo.
|
|
354
|
+
- **Fast-travel network.** `@jgengine/core/world/fastTravel`' `createFastTravelNetwork` is a genre-agnostic
|
|
355
|
+
discovered-destinations model: game-defined `TravelPointDef`s (world-XZ position, region, icon, `initial`)
|
|
356
|
+
with per-player discovery tracking — `discover`, `isDiscovered`, distance-sorted `list(from)` and
|
|
357
|
+
`destinations(from)`, `nearest(from, { exclude })`, a `canTravel` gate, `discoveredCount`/`total`, an
|
|
358
|
+
`onDiscover` seam, and serializable `snapshot`/`restore` (re-seeds `initial` points). New `@jgengine/react`:
|
|
359
|
+
`useFastTravel` (re-render on discovery) and `FastTravelMenu` (region-grouped, distance-labeled destination
|
|
360
|
+
picker with a discovery counter and "you are here" flag). New `fast-travel` demo.
|
|
361
|
+
- **Generic particle system.** `@jgengine/core/vfx/particles`' `createParticleSystem` is a genre-agnostic,
|
|
362
|
+
deterministic, allocation-aware particle emitter: a fixed pool, data-only `EmitterConfig` (rate, position,
|
|
363
|
+
lifetime, speed, cone `spread`, `gravity`, `drag`, size/color/alpha over life), burst `emit(n)` and continuous
|
|
364
|
+
`rate`, `update(dt)` integration with swap-remove reaping, Structure-of-Arrays render `buffers()` (positions,
|
|
365
|
+
sizes, colors, alphas) clamped to the live count, injected-seed determinism, and serializable `snapshot`/
|
|
366
|
+
`restore`. New `@jgengine/shell/vfx/ParticleField` renders it as a one-draw-call soft-point GPU cloud with
|
|
367
|
+
per-particle size/color/alpha (additive or normal blending). New `particle-vfx` demo (fire / smoke / sparks).
|
|
368
|
+
- **Notification center.** `@jgengine/core/game/notifications`' `createNotificationCenter` is a persistent,
|
|
369
|
+
read-tracked notification log (newest-first, capped, serializable, observable) — the durable counterpart to
|
|
370
|
+
transient toasts: `push`, `list({ kind, unreadOnly })`, `unreadCount`, `markRead`/`markAllRead`, `remove`,
|
|
371
|
+
`clear`, `snapshot`/`restore`. New `@jgengine/react`: `useNotifications`, `NotificationBell` (icon + unread
|
|
372
|
+
badge), and `NotificationCenter` (scrollable panel with kind markers, relative time, unread highlight, and
|
|
373
|
+
mark-all-read/clear). First adopter: the apps/dev `notification-center` demo.
|
|
374
|
+
- **Photo mode.** `@jgengine/core/ui/photoMode`'s `createPhotoModeStore` is a serializable, observable
|
|
375
|
+
photo-mode state (active + hide-HUD) a game binds its capture flow to. New `@jgengine/react`
|
|
376
|
+
`usePhotoMode` + `PhotoModeControls` (hide-HUD toggle, capture, exit). New `@jgengine/shell/render/
|
|
377
|
+
sceneCapture`: `captureCanvas(gl)` reads the live frame to a PNG data URL (the shell canvas already sets
|
|
378
|
+
`preserveDrawingBuffer`), `downloadImage` saves it, and `SceneCaptureBinding` (mount in `WorldOverlay`)
|
|
379
|
+
hands the in-Canvas capture fn to the HUD's Capture button. The game reads `hideHud` to drop gameplay
|
|
380
|
+
chrome for a clean shot. First adopter: the apps/dev `photo-mode` demo.
|
|
381
|
+
- **Welded street intersections** (`@jgengine/core/world/roads`, #1363) — `trimPathAtJunctions` cuts every road back to an arm-derived apron at each crossing (through-roads split in two) and `buildJunctionSurface` welds a curb-return-filleted junction polygon whose boundary vertices are bitwise-shared with the trimmed ribbon ends; `buildTrimmedIntersections` runs the whole pipeline for a street network in one call. Junctions read as real intersections instead of overlapping ribbons under a floating disc (`buildJunctionPatch` is deprecated but still works). The shell's authored-road and city renderers consume it out of the box.
|
|
382
|
+
- **`GROUND_DECAL_LAYERS`** (`@jgengine/core/world/roads`, #1366) — one owning table for ground-decal elevations (terrain < road = junction < marking < glow) replacing scattered per-callsite Y epsilons; ribbon/junction builders default from it, and markings/glow overlays pair it with renderer-side `polygonOffset` so road surfaces stop z-fighting at distance.
|
|
383
|
+
- **Real curves and sidewalks in the street generator** (`@jgengine/core/world/streetGenerator`, #1364, #1368) — corners are now sampled circular-arc fillets honoring `minCurveRadius` instead of single bevels; street hierarchy comes from sampled betweenness centrality with an arterial-connectivity repair pass instead of a length percentile; and boulevard/avenue/street chains carry `sidewalks` offset polylines (new optional `sidewalkWidth` rule, default 2).
|
|
384
|
+
- **Race-track synthesis in circuit mode** (`@jgengine/core/world/streetGenerator`, #1365) — circuits are grown from seeded scatter → convex hull → inward midpoint displacement with chicane/ess/hairpin corner templates, an enforced start/finish straight, deterministic self-clearance retries, and a pit lane that leaves and rejoins the loop at two real junctions — an actual track layout instead of a wobbled ellipse.
|
|
385
|
+
- **City lot content resolution** (`@jgengine/core/world/cityGenerator`, #1367) — `resolveCityLotContent` (or `generateCity`'s new `content` option) enriches frontage lots with their zone band, building class from cityContent's weighted mixes (street-level-biased), floors, and massing pieces; a deterministic landmark pass merges clusters of adjacent lots into a few block-scale `hall`/`arena`/`market`/`campus` buildings (dial: `content.landmarks`, default 0.04). `GeneratedCity` gains an optional `lotContent`; the bare-lot path is unchanged.
|
|
386
|
+
- **Quick menu takes many forms.** The radial menu grows into a full quick-menu family. `@jgengine/core/ui/radialMenu`
|
|
387
|
+
gains a `RadialArc` (`startAngle`/`sweep`) so the wheel can be a partial **arc** (bottom half, quarter, ping bar)
|
|
388
|
+
with bounds-aware selection. `@jgengine/react`'s `RadialMenu` now renders per-slice **hotkeys, count badges,
|
|
389
|
+
cooldowns, and submenu markers**, and takes an `arc`. New `@jgengine/react` **`QuickMenu`** presents one item
|
|
390
|
+
model in four forms — `radial` · `arc` · `list` · `grid` — with section headers, hotkeys, badges, cooldowns,
|
|
391
|
+
disabled states, and **nested submenus** (drill in / back). First adopter: the apps/dev quick-menu demo (form
|
|
392
|
+
switcher across all four).
|
|
393
|
+
- **Per-context touch control modes** (`@jgengine/core/input/touchScheme` `TouchControlsConfig.modes` + `@jgengine/core/input/touchControlsMode` `setTouchControlsMode`/`activeTouchControlsMode`, re-exported from `gameplay`, #1370) — a game whose verbs change at runtime (on foot vs driving vs flying) declares one named touch config per context; each mode's fields replace the matching base fields and gameplay switches with `setTouchControlsMode(ctx, "car")` / `null`. The shell re-derives the dock the same frame, so a runner on foot never sees flight or vehicle chrome. Blind derivation past 8 buttons now logs a dev warning naming the curation seams. Vice Isle (on-foot/car/aircraft) is the first adopter.
|
|
394
|
+
- **Analog action values through the input snapshot** (`@jgengine/core/runtime/inputSnapshot` `publishAnalog`/`value`/`analog`, `InputFrame.analog`, #1370) — a continuous source (the virtual joystick today, gamepad sticks tomorrow) publishes per-action magnitudes (0..1) alongside the digital held set; `ctx.input.value(action)` reads them, `ctx.input.axis(...)` samples them per bound action (so steer/pitch/roll bound to action names become proportional automatically), and the walk controller consumes them via `resolveMovementIntent`'s new analog path — a slight stick tilt walks slowly in that direction at speed scaled by deflection, instead of slamming a full-speed digital strafe. Serializable: analog rides `InputFrame` to an authoritative host unchanged.
|
|
395
|
+
- **Floating joystick variant + Settings selector** (`@jgengine/shell` touch dock, `@jgengine/core/input/touchScheme` `TouchJoystickVariant`, #1370) — two shipped stick behaviors, player-selectable under Settings → Controls → "Joystick": `floating` (default; the dock corner is a capture zone with a faint resting ring and the stick spawns centered under the thumb) and `fixed` (classic always-visible stick). Both share the analog vector math with a radial deadzone (travel rescaled from 0.16), replacing the old per-axis 0.42 digital thresholds as the movement path.
|
|
396
|
+
- **Radial / quick menu (weapon/emote wheel).** `@jgengine/core/ui/radialMenu` ships the pure geometry —
|
|
397
|
+
`radialSlices`, `radialIndexFromAngle`, `radialIndexFromVector` (pointer/stick vector → slice with a
|
|
398
|
+
neutral dead zone), `radialSlicePosition` — with slices centered from "up" clockwise. New `@jgengine/react`
|
|
399
|
+
`RadialMenu` renders the wheel: pointer angle drives the highlight, the neutral hub closes, click/confirm
|
|
400
|
+
fires `onSelect`; the game supplies option icons/labels and skins it (`highlightIndex` for controller/test
|
|
401
|
+
control). First adopter: the apps/dev `radial-menu` demo.
|
|
402
|
+
- **Accessibility options.** `@jgengine/core/ui/accessibility`'s `createAccessibilityStore` is a
|
|
403
|
+
serializable, observable store of genre-agnostic accessibility preferences — reduced motion, high
|
|
404
|
+
contrast, `textScale` (clamped), colorblind mode, and captions — with `snapshot`/`restore`. Ships the
|
|
405
|
+
`COLORBLIND_MATRICES` (`feColorMatrix` values for protanopia/deuteranopia/tritanopia/grayscale),
|
|
406
|
+
`clampTextScale`, and a `reducedMotionDuration` animation gate. New `@jgengine/react` layer:
|
|
407
|
+
`AccessibilityProvider` (exposes a `--jg-text-scale` CSS var, sets `data-reduced-motion` /
|
|
408
|
+
`data-high-contrast` / `data-colorblind` / `data-captions`, and wraps the subtree in the selected
|
|
409
|
+
colorblind filter), `useAccessibility`, `usePrefersReducedMotion` (OS `prefers-reduced-motion`), and
|
|
410
|
+
`ColorblindFilters`. Input rebinding already existed; this fills the rest of the a11y surface. First
|
|
411
|
+
adopter: the apps/dev `accessibility` demo.
|
|
412
|
+
- **Quest / objective tracker HUD.** `@jgengine/core/game/quest`'s `describeTrackedQuest(def, instance)`
|
|
413
|
+
joins a static `QuestDef` with a player's live `QuestInstance` into a flat, renderer-free `TrackedQuestView`
|
|
414
|
+
(title, status, labelled objective progress — `defaultObjectiveLabel` derives "Defeat 5 Wolves" and can be
|
|
415
|
+
overridden). New `@jgengine/react` `QuestTracker` draws it as a compact HUD panel: per-objective checkmark +
|
|
416
|
+
strike on complete, `progress/count`, and a thin progress bar, with `maxObjectives` truncation. A widget over
|
|
417
|
+
the existing quest models — no runtime coupling to `QuestJournal`. First adopter: the apps/dev `quest-tracker`
|
|
418
|
+
demo.
|
|
419
|
+
- **Localization / i18n.** `@jgengine/core/i18n` (also on the `@jgengine/core/ui` barrel)'s `createI18n`
|
|
420
|
+
translates a message `Catalog` with active-locale lookup, a fallback-locale chain, `{param}`
|
|
421
|
+
interpolation, and `Intl.PluralRules`-based `plural(key, count)`; it's observable so `setLocale` re-renders
|
|
422
|
+
bound UI, and the catalog stays caller-owned static data. New `@jgengine/react` layer: `I18nProvider`,
|
|
423
|
+
`useT()` / `usePlural()` / `useLocale()` hooks, and a `<Trans k=… />` component — every HUD string binds to
|
|
424
|
+
the catalog and swaps live on language change. First fully-absent-system gap closed; genre-agnostic, no
|
|
425
|
+
content shipped. First adopter: the apps/dev `localization` demo (EN / ES / 日本語 switcher).
|
|
426
|
+
- **Click-to-place custom marker kinds** (`@jgengine/core/scene` `definePlaceableMarkerKind` + `@jgengine/editor`) — a game declares its own logical marker kind (a stash, checkpoint, bounty…) in one call and it becomes a click-to-place tool in the editor's `+ Add` menu, grouped under its `category`: pick it, click the world to drop a marker, Shift-click to keep placing, tune its `fields` in the Inspector. The light path skips a full studio's schema/resolver boilerplate; the runtime is unchanged (the game still reads the kind off `editorLayers.markers`). The Add menu now groups registered kinds by their `addCategory` header instead of lumping every one under "Studios". Register from a module the editor loads (a game's `editorLayers` import graph). Pairs with the `add_marker` RPC verb: `definePlaceableMarkerKind` is the designer's click-to-place path, `add_marker` the scripted one.
|
|
427
|
+
- **`add_marker` editor verb** (`@jgengine/editor`, #1360) — the one-shot counterpart to `add_path`: place a bare point marker (a spawn, checkpoint, pickup, bounty, or a game's own custom kind) at `x`/`z` (optional `y`) in a single `--rpc`/MCP call, no `export_document`/`import_document` roundtrip and no hand-editing `editor.scene.json`. `kind` is required; `color`/`label`/`rotationY`/`meta` are optional and `meta` is validated against the kind's registered schema when one exists. Re-ids on a document-global id collision and returns the landed id, exactly like `add_path`. Use `place_asset` when the marker should carry a mesh; `add_marker` is for logical, mesh-free markers a game reads off the document by kind.
|
|
428
|
+
- **World-anchored entity frames (overhead nameplates / health bars)** (`@jgengine/react/entityFrames`, `@jgengine/shell/world/WorldEntityFrames`, #1266) — a data-first, caller-owned seam for floating frames over entities. `EntityFrames` takes a plain `{ id, worldPosition, ... }[]` plus a `project` (world→screen) and a `renderFrame` that composes the shipped `HealthBar` + a name (reskinned via `barTokens`/`HudTheme`); it owns only the reusable behavior — projection offset, off-screen/behind-camera culling, and nearest-on-top stacking. The pure `layoutEntityFrames` core is unit-tested in isolation and imports no React DOM/Three.js. R3F games get the projector for free from `WorldEntityFrames` / `useWorldProjection` / `projectWorldToScreen` (`@jgengine/shell`), which sample the live camera and compose the react primitive — overhead enemy nameplates/health bars from a game's own entity array in a few lines, with no `GameProvider` or entity-store mirroring. See the `jgengine-ui` overhead-entity-frames recipe. Complements the store-driven `WorldNameplates`.
|
|
429
|
+
- **Per-entity render hide: `SceneEntity.hidden`** (`@jgengine/core/scene/entityStore`, `@jgengine/shell`, #1299) — a new optional render-only flag on scene entities, settable at `spawn` and via `update({ hidden })`. When `true` the shell's entity marker skips the visual entirely each frame while the entity keeps simulating (position, stats, AI targeting, queries untouched). Built for seated vehicle riders whose character model otherwise pokes through the vehicle body; equally fits cutscene actors and stealth cloaks. Absent/`false` renders exactly as before.
|
|
430
|
+
- **`ChaseCameraTuning` covers the full driving-feel surface** (`@jgengine/core/runtime/cameraDirector`, #1299) — the runtime `ctx.camera.setChaseTuning` patch now also accepts `shakePerSpeed` and `velocityYaw`, so a game can keep its static `camera.chase` block as a calm on-foot baseline and overlay the whole speed-reactive package (speed→FOV, lead, bank, speed shake, drift-lag) only while a vehicle is piloted. Existing tuning patches are unchanged.
|
|
431
|
+
- **Draw on the map: annotation layer + FullscreenMap draw tool.** `@jgengine/core/world/mapAnnotations`'
|
|
432
|
+
`createAnnotationLayer` is a serializable player-drawing layer — freehand `strokes`, area `shapes`, and
|
|
433
|
+
pinned `notes`, all world-XZ — whose `routes()`/`zones()` project straight into the `routes`/`zones` props
|
|
434
|
+
`Minimap`/`WorldMap`/`FullscreenMap` already render, so drawing needs no new renderer; `snapshot`/`restore`
|
|
435
|
+
round-trip through a save. `@jgengine/react/map`'s `FullscreenMap` gains a `tool` prop (`"pan"` | `"draw"`):
|
|
436
|
+
in draw mode a pointer drag paints a freehand stroke committed to `onStrokeComplete(points)` (with a live
|
|
437
|
+
in-progress preview via `drawTone`/`drawWidth`), while panning and click-to-place are suppressed. First
|
|
438
|
+
adopter: the apps/dev extraction-map demo (Pan/Draw/Clear palette).
|
|
439
|
+
- **`jgengine shoot` / `jgengine drive` CLI verbs** (`jgengine` CLI, #1262) — the browser capture and play-driving rungs are now first-class CLI verbs, so a standalone project (scaffolded with `npx jgengine create`, outside this monorepo) can screenshot and playtest its own game without any monorepo-only tooling. Run inside a project that already ships `scripts/shoot.mjs` / `scripts/drive.mjs` and the verb delegates to them (parity with `bun run shoot`); run in one that lacks them (an older scaffold, or one whose scripts were removed) and it materializes the same dependency-free harness the scaffold embeds and drives the project's dev server. Chrome/Chromium is the only capture engine — no Playwright and no npm deps travel with the published CLI; a missing browser fails with an install hint. Makes the "visual claims are screenshot-judged" rule enforceable for the exact outside users it targets.
|
|
440
|
+
- **Engine preview-fixture capture route** (`@jgengine/react` `PREVIEW_FIXTURES`, #1272) — a registry of deterministic, engine-level preview fixtures (the real exported components `HudThemePreview`, `BarsPreview`, `IconsPreview`, …) keyed by name, with `previewFixtureNames()` and `resolvePreviewFixture(name)`. The dev runner mounts one from a `?fixture=<name>` URL overlay and `bun run shoot --fixture <name>` screenshots it with no game boot and no hand-rolled `--url` page; `bun run shoot --list` (or `--fixture` with no name) prints the registered set. Register any additional deterministic preview component in `PREVIEW_FIXTURES` and the route and CLI pick it up with no further wiring — genre-agnostic, additive.
|
|
441
|
+
- **Per-capture player-spawn override** (`@jgengine/core/world/spawnOverride`, #1255) — `installSpawnOverride`/`clearSpawnOverride`/`readSpawnOverride` and the pure `parseSpawnOverride("x,y,z"|"x,y,z,yaw")` install a session-scoped spawn that `authoredSpawnPosition`/`authoredSpawnRotation` honor for the *default* player-spawn resolution only (an explicit marker `id` or non-`player_spawn` kind still reads the document), so a close-up capture can relocate the player without editing `editor.scene.json`. The dev runner installs one from a `?spawn=` URL overlay (mirroring `?cam=`), and `bun run shoot`/`bun run drive` gain a `--spawn x,y,z` flag. Default is no override, so nothing changes until one is installed.
|
|
442
|
+
- **Aerial editor camera in one call** (`@jgengine/editor`, #1256) — `camera_goto` and `camera_frame` take optional `distance`, `pitch` (degrees above the horizon; 90 = straight-down), `yaw`, and `height`, and the `EditorFocusTarget` carries them so the orbit camera repositions instead of only panning. `camera_frame` with a `pitch` but no `distance` auto-fits the document bounds into view (`orbitCameraPosition`/`frameDistanceForBounds` are exported and unit-tested), so composing a district aerial no longer takes many guess-the-y-offset round-trips or buries the camera in terrain.
|
|
443
|
+
- **Achievements / trophies** — `@jgengine/core/game/achievements`' `createAchievementTracker`
|
|
444
|
+
is a serializable tracker for counter-goal (`progress`/`setProgress`) and boolean (`unlock`)
|
|
445
|
+
achievements, with `score()`, `completion()`, an `onUnlock` seam (wire to a toast queue, feed,
|
|
446
|
+
or sound), and `snapshot`/`restore`; its view `list()` keeps a stable identity so React reads it
|
|
447
|
+
through `useSyncExternalStore` without re-projecting each frame. New `@jgengine/react` presentation:
|
|
448
|
+
`useAchievements(tracker)`, `AchievementGallery` (unlocked/locked grid with counter progress bars,
|
|
449
|
+
secret masking, and a completion/score header), and `AchievementToast` (unlock banner). Genre-agnostic
|
|
450
|
+
and additive — no defaults or content shipped.
|
|
451
|
+
- **World-space pings** (`@jgengine/shell/world/WorldPings`, `@jgengine/shell/world/pingPulse`) — the
|
|
452
|
+
in-scene side of a ping/marker: a bobbing downward arrowhead pointing at the spot, a ground ring, and a
|
|
453
|
+
billboarded callout (glyph + `meta.callout`/`label`), colored by marker kind and fading in/out over the
|
|
454
|
+
marker's lifetime. Reads a `MarkerSet` (typically the one a `createPingSystem` writes to), a marker
|
|
455
|
+
source, or a static array — mount through `PlayableGame.WorldOverlay`. `pingsOnly` (default true) limits
|
|
456
|
+
it to `meta.ping` markers; `renderCallout` overrides the chip. The pure lifecycle math (`pingOpacity`,
|
|
457
|
+
`pingBobOffset`) is exported and unit-tested. First adopter: the apps/dev `world-pings` demo.
|
|
458
|
+
- **Difficulty-aware AI driver** (`@jgengine/core/ai/driver`, #1311) — `driveStep(state, dt, pose, goal, profile, tuning, rng, obstacleAhead?)` turns a vehicle pose + goal into the shared `AxisInput` for `tickDrivableVehicle`, with a `DifficultyProfile` (#1301) deciding how well the car drives: a reaction delay-line makes low tiers chase where the target *was*, corners shed speed instead of full-throttle orbiting, obstacles are perceived late by `speed × reactionSeconds`, steering wobbles by `executionJitter` resampled on an interval, and a wall-grinding car reverses out with counter-steer after a tier-scaled delay. `pathTargetAhead` walks a road/route polyline to a pure-pursuit lookahead point so the same brain follows authored streets and circuits. State is one serializable object; randomness injected; no world scans. First adopter: vice-isle cruiser pursuit, whose cops now drive by wanted level (3★ sloppy rookies → 5★ sharp interceptors) instead of `steer = error*2, throttle = 1`.
|
|
459
|
+
- **Effect primitives: named hooks for the recurring `useEffect` shapes** (`@jgengine/react/hooks`, `@jgengine/shell/render/useDisposable`, #1298) — `useTicker(hz)` re-renders HUD elements at a steady rate for time-derived readouts (cooldowns, cast/swing bars); `useDomEvent(resolveTarget, type, handler, options?)` attaches a DOM listener with automatic cleanup and a stable handler ref; `useRafLoop(onFrame, active?)` runs a cancellable requestAnimationFrame loop with delta seconds; `useAutoScroll(dep)` pins a log/chat/console panel to its newest line; and shell's `useDisposable(create, deps)` (+ `disposeAll`) memoizes a three.js resource or tuple and disposes it on change/unmount, replacing the hand-rolled `useMemo` + dispose-effect pair. Prefer these over direct `useEffect` for their shapes — a lint restriction on direct `useEffect` outside the primitives layer follows in a later phase.
|
|
460
|
+
- **Three-tier AI decision quality** (`@jgengine/core/ai/difficulty`, #1301) — one serializable `DifficultyProfile` (reaction time, decision noise, execution jitter, ability discipline, perception scale, plan depth/width) owns how well any opponent decides, with canonical `easy`/`standard`/`expert` tiers (`DIFFICULTY_TIERS`, `difficultyProfile(tier, overrides)`) and deterministic appliers at the universal decision seams: `advanceReactionGate` (delay acting on new information), `pickScored` (noisy argmax over any caller-scored option list), `executionError` (aim/lead/timing fuzz), `shouldUseAbility` (spend-or-hold specials from a scored opportunity window), and `planLookahead` (depth/width-bounded negamax or own-sequence search over a game-owned `moves`/`apply`/`evaluate` domain — greedy at easy, multi-ply tactics at expert). Composes with the existing substrates: scale `MobBrainConfig.aggroRadius` by `perceptionScale`, route `acquireTarget` scores through `pickScored`, gate `advancePursuit` swaps behind the reaction gate.
|
|
461
|
+
- **Authored per-placement rig animation applies in game** (`@jgengine/core/world/authoredObjects`, `@jgengine/core/scene/objectStore`, #1276) — a placement's editor-authored `marker.meta.animation` (role→clip states, `"auto"`/`"none"`, walk/run/fade, one-shot bindings; the stable contract from #1274) now flows through `resolveAuthoredObjects` → `placeAuthoredObjects` → `ObjectStore.place` onto the placed object's `ModelConfig.animation`, so the override applies at play time instead of being write+preview only. `AuthoredObject.animation`, `PlaceOptions.animation`, and `SceneObject.animation` are new optional fields; the new `markerAnimation(marker)` reader exposes the contract. Markers with no override are unchanged — animation still comes from catalog resolution.
|
|
462
|
+
- **`EffectResult` carries the slain entity's identity** (`@jgengine/core/combat/effects`, #1263) — a lethal `ctx.scene.entity.effect()` hit now returns `slain: { catalogId, name?, userId? }` on the per-target result, captured before the death system despawns the target. Kill credit / XP reads the victim's `catalogId` (its spawn kind) straight off the result — no game-side spawn-time registry mirroring instance ids to kinds. Non-lethal hits omit `slain`. The new `EffectSystemDeps.resolveSlainIdentity` seam is optional and additive, so existing effect-system compositions keep their exact shape.
|
|
463
|
+
- Interactive fullscreen map + player waypoints. `@jgengine/core/world/waypoints`'
|
|
464
|
+
`createWaypointStore` is a serializable player-waypoint layer (place/track/clear,
|
|
465
|
+
snapshot↔restore) that mirrors pins into a `MarkerSet` so every map surface renders
|
|
466
|
+
them, and reports bearing/distance `guidance` for an on-screen arrow. New React
|
|
467
|
+
primitives in `@jgengine/react/map`: `FullscreenMap` (wheel-zoom/drag-pan overlay
|
|
468
|
+
that never fires a click after a pan), `WorldMapSurface` (the viewport-aware map
|
|
469
|
+
`<svg>` now shared by `WorldMap` and `FullscreenMap`), `MapLegend` (marker-kind key),
|
|
470
|
+
and `WaypointArrow` (HUD guide needle). A new `waypoint` entry joins
|
|
471
|
+
`DEFAULT_MARKER_KINDS`.
|
|
472
|
+
- Editor RPC/CLI verb `add_path` (`@jgengine/editor`): author a new path/route into the scene
|
|
473
|
+
document from an ordered list of ≥2 `{x,z}` (optional `y`) points in one call, without an
|
|
474
|
+
`export_document`/`import_document` roundtrip. `kind` defaults to `route`; `meta` is schema-validated
|
|
475
|
+
like `set_path`, and a colliding `id` re-ids in the document-global namespace.
|
|
476
|
+
|
|
477
|
+
### Changed
|
|
478
|
+
|
|
479
|
+
- **`check-game-shape`/doctor accept editor-import-graph modules by convention** (`packages/jgengine` `gameShape`, #1377) — a top-level `src/editor<Name>.ts` module (plus its colocated `.test.ts`) is now an allowed game-shape extra, matched by the `editor<Name>.ts` naming convention instead of an exact-filename enumeration. Fixes `editorKinds.ts` tripping the shape gate and stops the next editor-graph module from re-reddening `main` the moment it lands. No game needs changes; `editorLayers`/`editorCatalogs`/`editorKinds` and any future sibling are all recognized.
|
|
480
|
+
- **Road ribbons no longer self-intersect at bends** (`@jgengine/core/world/roads`) — `buildRoadRibbon` now miter-joins the inner edge of a bend and welds any residual fold when the local turn radius drops under the half-width, so dense arc-sampled corners render as one clean surface instead of a doubled bowtie. Straight ribbons and terminal cross-sections stay byte-identical (junction welds unaffected).
|
|
481
|
+
- **Junction surfaces stopped emitting sliver triangles** (`@jgengine/core/world/roads`) — `buildJunctionSurface` grouped approach corners globally by angle, which interleaved unequal-width approaches and produced shard/sliver fans; corners now stay grouped per approach with wrap-safe ordering and outward curb-return arcs, and the fan runs over an angle-monotonic simple boundary.
|
|
482
|
+
- **Street corner arcs sample at ≤~9° per vertex** (`@jgengine/core/world/streetGenerator`) — corner fillets previously stepped up to `maxTurnAngle` (~28°) per vertex and read as hard polygons at road width; both net and circuit corners now sample finely, and sidewalk offsets became true parallel offsets (outside arcs, inside miter-clamp + weld) that never pinch into the road surface.
|
|
483
|
+
- **Circuit centerlines are curve-first splines** (`@jgengine/core/world/streetGenerator`) — the folded layout polygon (hull + deep inward displacement, guaranteed hairpin/ess) now serves as control points for a periodic centripetal Catmull-Rom spline with a curvature floor at `minCurveRadius` (weighted Laplacian relaxation), so a lap is mostly flowing sweepers and continuous esses with 1-3 deliberate straights — not straight segments with corner caps. Fitted radii form a smooth continuum (~1.1-11x the floor); property tests pin curvature continuity, curved-share, and hairpin+sweeper coexistence.
|
|
484
|
+
|
|
485
|
+
- **Chase camera yaw is smoothed by default** (`camera.chase.yawResponse`, `@jgengine/shell` chase rig, #1370) — the boom now eases toward the followed body's facing with `1-exp(-response*dt)` smoothing (default response 5) instead of rigidly equaling it every frame, so a strafe-flipped facing arcs the camera around the character rather than teleporting it to the far side. Set `yawResponse: Infinity` to restore the legacy rigid follow; drift-lag (`velocityYaw`) keeps its own response as before.
|
|
486
|
+
- **Editor content-browser thumbnails no longer crash the editor when a GLB's textures fail** (`@jgengine/editor`, #1270) — `getGlbThumbnailState` (the `getSnapshot` behind `useGlbThumbnail`/`AssetThumbnail`) returned a fresh object literal on every call, violating the `useSyncExternalStore` stable-snapshot contract; React's post-commit consistency check then forced a re-render every commit ("Maximum update depth exceeded"), and the `GameUiErrorBoundary` blanked the whole editor chrome. It now reuses the prior snapshot object while a URL's observable state (idle/loading/ready/error) is unchanged, so a permanently-failing asset settles to a stable error/glyph fallback instead of looping. No API change; behavior only.
|
|
487
|
+
- **Offline autosave no longer starves in a living world** (`@jgengine/core/runtime/runtimeSave`) — `autosave` mode previously debounced by *resetting* its timer on every world change, so an open world that mutates every frame (AI, physics, the day/night clock) pushed the timer forward faster than it could fire and never persisted; a game that drove no explicit `checkpoint()` effectively never saved. Autosave is now a trailing timer: the first unsaved change arms it, later changes ride the same timer, and it captures the latest snapshot when it fires — at most one write per `autosaveMs` and always within one interval, even while the world never idles. No API change; behavior only.
|
|
488
|
+
- **Effect consolidation phase 2: subscriptions on `useSyncExternalStore`, dispose pairs on `useDisposable`** (`@jgengine/react`, `@jgengine/shell`, `@jgengine/editor`, #1304) — hand-rolled `subscribe(() => setTick)` re-render mirrors across the editor chrome/panels, shell settings (`useSettingsRevision`), camera director, WorldHud selection, and AuthoredScene live-document hook now ride `useSyncExternalStore` (fewer redundant render passes, identical update timing); ~15 shell renderers moved their `useMemo` + dispose-effect pairs onto `useDisposable`; derived-state/prop-reset/focus effects were removed in favor of render-time derivation and `autoFocus` (editor inspector fields, hierarchy active row, animation panel, play-control mirrors via the new shared `usePlayControl`, GamePlayerShell binding overrides — kills a one-frame stale-keybinds render on game switch; `GameViewport` now renders `data-jg-layout-collision` declaratively). No public API removals; `usePlayControl` (`@jgengine/editor`) is new.
|
|
489
|
+
|
|
490
|
+
- **Blocking colliders are now walkable surfaces, and collision is on by default** (`@jgengine/core`
|
|
491
|
+
movement, #1402) — the walking controller treats placed objects' blocking colliders as ground:
|
|
492
|
+
`collideObjects` defaults `true`; a ledge within the new `PlayerMovementConfig.stepHeight`
|
|
493
|
+
(default 0.4) is stepped up instead of walling; a jump that clears an object's top **lands standing
|
|
494
|
+
on it** (new `obstacleSupportHeight`, integrated in absolute space so the arc stays continuous over
|
|
495
|
+
changing ground) instead of sinking inside the box and being rubber-banded out by depenetration; and
|
|
496
|
+
walking off a drop taller than a step falls under gravity instead of teleporting the feet down.
|
|
497
|
+
- **Movement and vehicles no longer tunnel through solids** (`@jgengine/core`, #1402) — the shared
|
|
498
|
+
axis clamp in `resolveObstacleStep` is now swept: a step whose target lands *beyond* a box's far
|
|
499
|
+
face stops on the near face instead of passing straight through (fast cars, dashes/impulses, low-FPS
|
|
500
|
+
frames, thin walls). The walking broadphase also derives its reach from the scene's actual largest
|
|
501
|
+
blocking collider instead of a hardcoded 4 m bound, so the player no longer walks through the edges
|
|
502
|
+
of wide buildings whose center point sat outside the query; and the kinematic-vehicle clamp passes
|
|
503
|
+
collider `offset`/compound `boxes` through instead of silently dropping them.
|
|
504
|
+
- **Walking collision now depenetrates instead of locking.** `resolveObstacleStep`
|
|
505
|
+
(movement) previously slid along box faces but had no escape once the capsule was
|
|
506
|
+
strictly *inside* a solid box — its per-axis clamps returned zero on both axes and the
|
|
507
|
+
player was stuck forever. It now pushes an enclosed capsule out along the box's
|
|
508
|
+
shallowest face before sliding. Resting exactly on a face still reads as contact, so
|
|
509
|
+
normal wall-sliding is byte-for-byte unchanged; only the previously-unrecoverable
|
|
510
|
+
"wedged inside a building" case now frees itself.
|
|
511
|
+
- `import_document` (`@jgengine/editor`) now answers a missing or mis-keyed document param with an
|
|
512
|
+
error naming the expected `json` param instead of surfacing a raw `JSON Parse error: Unexpected
|
|
513
|
+
identifier "undefined"`.
|
|
514
|
+
|
|
515
|
+
### Removed
|
|
516
|
+
|
|
517
|
+
### Fixed
|
|
518
|
+
|
|
519
|
+
- **A missing/mis-served GLB no longer white-screens the whole game — it degrades to one placeholder primitive** (`@jgengine/shell/render/modelLoad`, #1340). A Vite dev server returns its `index.html` fallback (HTTP 200) for a missing `/models/*.glb`; `GLTFLoader` then throws parsing HTML as a GLB, and that failure surfaced as a rejected `useLoader` Suspense promise which does **not** reliably re-throw into a per-model React error boundary inside the react-three-fiber reconciler — so it escaped to the app-level `GameUiErrorBoundary` and blanked the scene. `sharedGltfLoader` (`DiagnosticGLTFLoader`) now probes a failed URL and, for a diagnosed broken asset (missing / HTML fallback / corrupt / unsupported), **resolves to a `createFallbackModel` placeholder box instead of rejecting**, containing the failure at the load seam for every consumer with no reliance on boundary recovery; a genuine parse error over valid-looking bytes is still surfaced. Emits a dev console warning naming the broken path. No API change; `useLoader(sharedGltfLoader, url)` benefits with no call-site change.
|
|
520
|
+
|
|
521
|
+
## 0.14.0
|
|
522
|
+
|
|
523
|
+
### Migrate
|
|
524
|
+
|
|
525
|
+
- **Nothing required** — this release is additive drop-in primitives, editor authoring, CLI discovery, and fixes. Bump `@jgengine/*` pins to `^0.14.0` (CLI `jgengine` is 0.11.0; `@jgengine/github` unchanged at 0.1.0) and rebuild.
|
|
526
|
+
- One behavior fix to know about: in a `rig: "chase"` game, on-foot movement and aim are now camera-relative (the chase rig reports its yaw to the shell like every other rig). If a game somehow relied on world-yaw-0 movement under a chase camera, that was the bug this fixes.
|
|
527
|
+
|
|
528
|
+
### Added
|
|
529
|
+
|
|
530
|
+
- One authoring gesture for scatterable coverage: `@jgengine/core/world/scatterCoverage` owns shared density/budget semantics (per-kind unit, requested→count→capped, one clamp-and-warn phrasing) for `grass_field`/`scatter`/`city`; the editor inspector leads each kind with the same Area → Assets → Density coverage section, scatter truncates to the shared 250k instance budget, and city single-sources its 2,600-lot cap.
|
|
531
|
+
- **Created games ship a drive/playtest script** (#1248) — `npx jgengine create` scaffolds now include `scripts/drive.mjs` (`bun run drive`): dependency-free headless play/testing of the running game — ordered `--click`/`--key`/`--wait`/`--shot`/`--rpc` steps against the agent bridge, plus `--playtest --strict` progress/softlock verdicts off `capture.probe`. Shared Chrome/CDP machinery lives in `scripts/browser.mjs`; `scripts/shoot.mjs` is unchanged in behavior, now a thin CLI over it.
|
|
532
|
+
- Editor: viewport clip preview for rigged assets (Animation dock "Clips" mode — pick a rig or placed instance, play/scrub/loop/speed any catalog clip) and an Inspector "Animation" section that authors a placement's `ModelConfig.animation` (role→clip dropdowns, auto/none, walk/run/fade, one-shot event bindings) as undoable `marker.meta.animation` edits. New subpaths: `@jgengine/editor/shell/clipPreview`, `@jgengine/editor/modelAnimationAuthoring`, `@jgengine/editor/ClipPreviewLayer`.
|
|
533
|
+
- **`npx jgengine find <intent>`** — active capability discovery: searches every shipped domain's `capabilities.md` (staged inside the CLI tarball, so it works regardless of which `@jgengine/*` packages a project installed) and prints the drop-in primitive + its import for an intent like `"toggleable window"`, `inventory`, or `minimap`. Scaffolded games are now briefed (in `AGENTS.md` and the `jgengine` intake skill) to reach for it before hand-rolling a HUD/inventory/window/rig.
|
|
534
|
+
- **`EntityPreview`** (`@jgengine/shell/render/EntityPreview`) — drop-in live 3D entity portrait for character screens, unit inspectors, and loadout viewers. Owns the nested `<Canvas>`, the `GameContextBridge`, and a `StudioStage` lighting rig with optional turntable / face-camera; the game passes its own `renderEntity` as children (or a native `model` + `instanceId`) and, bound to a live entity, the portrait walks/flinches/topples in sync with the world.
|
|
535
|
+
- **`GameContextBridge`** (`@jgengine/react`) — re-provide the running `GameContext` across a nested React reconciler boundary (the R3F `<Canvas>`), so a game building its own preview canvas no longer re-derives the bridge by hand.
|
|
536
|
+
|
|
537
|
+
### Changed
|
|
538
|
+
|
|
539
|
+
- **Chase rig reports its yaw to the shell** — `ChaseRig` now writes the camera yaw back to the shared yaw ref like every other player-facing rig, so on-foot movement and aim in a `rig: "chase"` game are camera-relative instead of frozen to world yaw 0 (WASD no longer fights the camera the moment it swings behind the player). Interior views (cockpit/hood/rear) report the vehicle heading.
|
|
540
|
+
- **Combat VFX no longer render as black squares under AO/DOF post-processing** (#1247) — GTAO/Bokeh scene prepasses skip overlay effects. Games with custom additive overlay effects opt out the same way: spread `POSTFX_OVERLAY_USERDATA` (`@jgengine/shell/postfx/postfxOverlay`) onto the overlay group's `userData`.
|
|
541
|
+
- **`PanelHost` windows stack above the HUD by default** (`@jgengine/react`) — the host establishes its own stacking context at `zIndexBase` (default 40), so open windows always paint over stat bars / nameplates / frames instead of bleeding through. Overridable per instance.
|
|
14
542
|
|
|
15
543
|
## 0.13.0
|
|
16
544
|
|