@jgengine/core 0.25.0 → 0.26.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 +65 -0
- package/dist/ai/rvo.d.ts +106 -0
- package/dist/ai/rvo.js +581 -0
- package/dist/ai/utilityBrain.d.ts +173 -0
- package/dist/ai/utilityBrain.js +277 -0
- package/dist/combat/cover.d.ts +169 -0
- package/dist/combat/cover.js +264 -0
- package/dist/combat/damageRuntime.d.ts +150 -0
- package/dist/combat/damageRuntime.js +208 -0
- package/dist/combat/damageTable.d.ts +116 -0
- package/dist/combat/damageTable.js +165 -0
- package/dist/combat/hitZones.d.ts +70 -0
- package/dist/combat/hitZones.js +77 -0
- package/dist/combat/magazine.d.ts +8 -0
- package/dist/combat/magazine.js +20 -2
- package/dist/combat/squadCombat.d.ts +167 -0
- package/dist/combat/squadCombat.js +557 -0
- package/dist/combat/squadTactics.d.ts +73 -0
- package/dist/combat/squadTactics.js +49 -0
- package/dist/combat/statusRuntime.d.ts +142 -0
- package/dist/combat/statusRuntime.js +236 -0
- package/dist/combat/weaponDefinition.d.ts +106 -0
- package/dist/combat/weaponDefinition.js +108 -0
- package/dist/combat/weaponSystem.d.ts +189 -0
- package/dist/combat/weaponSystem.js +469 -0
- package/dist/combat.d.ts +9 -0
- package/dist/combat.js +9 -0
- package/dist/devtools/devtools.d.ts +4 -2
- package/dist/devtools/devtools.js +2 -1
- package/dist/devtools/discover.d.ts +2 -1
- package/dist/devtools/discover.js +38 -0
- package/dist/devtools/frame.d.ts +0 -2
- package/dist/devtools/frame.js +1 -2
- package/dist/devtools/logBus.d.ts +58 -0
- package/dist/devtools/logBus.js +79 -0
- package/dist/devtools/logs.d.ts +6 -3
- package/dist/devtools/logs.js +17 -13
- package/dist/devtools/report.d.ts +120 -0
- package/dist/devtools/report.js +49 -0
- package/dist/devtools/reportEndpoint.d.ts +17 -0
- package/dist/devtools/reportEndpoint.js +7 -0
- package/dist/devtools/types.d.ts +48 -11
- package/dist/editor/document.js +210 -0
- package/dist/editor/environment.d.ts +48 -1
- package/dist/editor/environment.js +161 -0
- package/dist/editor/index.d.ts +6 -2
- package/dist/editor/index.js +1 -1
- package/dist/editor/types.d.ts +54 -0
- package/dist/gameplay.d.ts +4 -0
- package/dist/gameplay.js +4 -0
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +56 -1
- package/dist/nav/navCrowd.d.ts +138 -0
- package/dist/nav/navCrowd.js +412 -0
- package/dist/nav/navRuntime.d.ts +108 -0
- package/dist/nav/navRuntime.js +256 -0
- package/dist/render/colorPipeline.d.ts +53 -0
- package/dist/render/colorPipeline.js +79 -0
- package/dist/render/lightProbes.d.ts +97 -0
- package/dist/render/lightProbes.js +221 -0
- package/dist/render/lookPreset.js +4 -4
- package/dist/render/physicalSky.d.ts +39 -0
- package/dist/render/physicalSky.js +89 -0
- package/dist/render/postProcessing.d.ts +40 -8
- package/dist/render/postProcessing.js +1 -1
- package/dist/render/skyOwnedLighting.d.ts +31 -0
- package/dist/render/skyOwnedLighting.js +46 -0
- package/dist/sensor/perception.d.ts +66 -5
- package/dist/sensor/perception.js +167 -42
- package/dist/sim/nearAgents.d.ts +68 -0
- package/dist/sim/nearAgents.js +152 -0
- package/dist/sim/needs.d.ts +60 -0
- package/dist/sim/needs.js +108 -0
- package/dist/sim/offScreen.d.ts +181 -0
- package/dist/sim/offScreen.js +450 -0
- package/dist/sim/routine.d.ts +79 -0
- package/dist/sim/routine.js +137 -0
- package/dist/visibility/visibilitySystem.js +21 -0
- package/dist/world/features.d.ts +8 -0
- package/dist/world.d.ts +5 -0
- package/dist/world.js +5 -0
- package/package.json +1 -1
- package/skills/jgengine/api.md +59 -6
- package/skills/jgengine/capabilities.md +4 -0
- package/skills/jgengine-combat/SKILL.md +17 -0
- package/skills/jgengine-combat/api.md +246 -0
- package/skills/jgengine-combat/capabilities.md +112 -0
- package/skills/jgengine-combat/reference.md +32 -0
- package/skills/jgengine-gameplay/SKILL.md +8 -0
- package/skills/jgengine-gameplay/api.md +71 -1
- package/skills/jgengine-gameplay/capabilities.md +16 -0
- package/skills/jgengine-world/SKILL.md +3 -1
- package/skills/jgengine-world/api.md +107 -3
- package/skills/jgengine-world/capabilities.md +22 -2
- package/skills/jgengine-world/recipes/npc-senses.md +8 -2
package/dist/editor/types.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type { MinimapBakeBounds } from "../world/minimapBake.js";
|
|
|
7
7
|
import type { TerraformSnapshot } from "../world/terraform.js";
|
|
8
8
|
import type { EditorGridLayer } from "./grid.js";
|
|
9
9
|
import type { EnvironmentSource } from "../render/environment.js";
|
|
10
|
+
import type { IrradianceBakeSettings, IrradianceVolume } from "../render/lightProbes.js";
|
|
10
11
|
import type { PointLightingConfig } from "../game/playableGame.js";
|
|
11
12
|
/** Runtime point-light shape reused by authored editor light markers. */
|
|
12
13
|
export type { PointLightingConfig } from "../game/playableGame.js";
|
|
@@ -196,6 +197,17 @@ export interface EditorBake {
|
|
|
196
197
|
}
|
|
197
198
|
/** Named sky look stored on the scene document; matches runtime `SkyEnvironmentConfig.preset`. */
|
|
198
199
|
export type EditorSkyPreset = "day" | "dusk" | "night";
|
|
200
|
+
/**
|
|
201
|
+
* An irradiance volume authored on the scene document: a grid of baked SH probes over a box, plus
|
|
202
|
+
* the bake settings that produced them so a scene can re-bake without the editor session.
|
|
203
|
+
* @capability light-probes author a baked irradiance volume on the scene document
|
|
204
|
+
*/
|
|
205
|
+
export interface EditorLightProbe extends IrradianceVolume {
|
|
206
|
+
/** Label shown in the lighting workspace. */
|
|
207
|
+
label?: string;
|
|
208
|
+
/** The authored bake inputs; absent means the volume was authored elsewhere and only the probe data matters. */
|
|
209
|
+
bake?: IrradianceBakeSettings;
|
|
210
|
+
}
|
|
199
211
|
/**
|
|
200
212
|
* Linear distance fog authored on the scene document — serializable subset of sky/backdrop fog.
|
|
201
213
|
* Absent fields keep engine defaults when the document is applied at runtime.
|
|
@@ -205,6 +217,40 @@ export interface EditorFogConfig {
|
|
|
205
217
|
near?: number;
|
|
206
218
|
far?: number;
|
|
207
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* Authored colour grading for the scene document's final image. The editor bakes a small
|
|
222
|
+
* display-space LUT from these controls and the shell applies it after tone mapping, so a game's
|
|
223
|
+
* grade is authored data like its sky, not a hardcoded pass in the game's config.
|
|
224
|
+
*
|
|
225
|
+
* The three band colours are display swatches (`#rrggbb`) that the pixels of that band are pulled
|
|
226
|
+
* toward, by `tint`. `saturation` and `contrast` are applied before the bands. Baking at a small cube
|
|
227
|
+
* size keeps the whole grade — including the LUT — a few hundred bytes of JSON.
|
|
228
|
+
*/
|
|
229
|
+
export interface EditorColorGrading {
|
|
230
|
+
/** Cube edge length the runtime bakes, 2..16. Default 8 — small enough to read, smooth enough for a grade. */
|
|
231
|
+
size?: number;
|
|
232
|
+
/** Swatch shadows are tinted toward, e.g. `#2a3a52` for a cool night. Absent leaves shadows alone. */
|
|
233
|
+
shadows?: string;
|
|
234
|
+
/** Swatch midtones are tinted toward. Absent leaves midtones alone. */
|
|
235
|
+
midtones?: string;
|
|
236
|
+
/** Swatch highlights are tinted toward, e.g. `#ffe9c4` for a warm sun. Absent leaves highlights alone. */
|
|
237
|
+
highlights?: string;
|
|
238
|
+
/** How far each band is pulled toward its swatch, 0..1. Default 0.45. */
|
|
239
|
+
tint?: number;
|
|
240
|
+
/** Display saturation around luma. Default 1. */
|
|
241
|
+
saturation?: number;
|
|
242
|
+
/** Display contrast around mid-grey. Default 1. */
|
|
243
|
+
contrast?: number;
|
|
244
|
+
/** Clamped auto-exposure authored with the grade; absent uses the pipeline's default range. */
|
|
245
|
+
autoExposure?: EditorAutoExposure;
|
|
246
|
+
}
|
|
247
|
+
/** Authored clamped auto-exposure range: how far the meter may pull a dark room or a bright street. */
|
|
248
|
+
export interface EditorAutoExposure {
|
|
249
|
+
/** Lowest exposure multiplier the meter may reach. Default 0.5. */
|
|
250
|
+
min?: number;
|
|
251
|
+
/** Highest exposure multiplier the meter may reach. Default 2.5. */
|
|
252
|
+
max?: number;
|
|
253
|
+
}
|
|
208
254
|
/**
|
|
209
255
|
* Scene-document environment/lighting authoring (#1110): sky preset, optional time-of-day drive,
|
|
210
256
|
* horizon/zenith tints, sun/ambient intensity, and fog. Serializable and genre-agnostic — games
|
|
@@ -216,8 +262,16 @@ export interface EditorFogConfig {
|
|
|
216
262
|
export interface EditorEnvironment {
|
|
217
263
|
/** Optional runtime environment map source; absent keeps the procedural gradient. */
|
|
218
264
|
source?: EnvironmentSource;
|
|
265
|
+
/** Authored clamped auto-exposure and colour grade; the shell applies it to the post chain. */
|
|
266
|
+
grading?: EditorColorGrading;
|
|
219
267
|
/** Authored local point lights; the runtime clamps these to its bounded light budget. */
|
|
220
268
|
pointLights?: readonly PointLightingConfig[];
|
|
269
|
+
/**
|
|
270
|
+
* Authored irradiance volumes: baked light probes for interiors and shaded streets. The runtime
|
|
271
|
+
* samples them at the camera and feeds three.js's scene light probe, so a room's ambient comes
|
|
272
|
+
* from its own lights instead of the sky.
|
|
273
|
+
*/
|
|
274
|
+
probes?: readonly EditorLightProbe[];
|
|
221
275
|
/** Fixed sky look when `timeOfDay` is off (or no clock is available). */
|
|
222
276
|
preset?: EditorSkyPreset;
|
|
223
277
|
/** Drive sun/sky from the world clock's day fraction instead of the fixed `preset`. */
|
package/dist/gameplay.d.ts
CHANGED
|
@@ -95,6 +95,10 @@ export { createTriggeredRuleEngine, type ActiveEffect, type EffectRef, type Firi
|
|
|
95
95
|
export { createRing, ringSampleAt, type Ring, type RingConfig, type RingPhase } from "./session/ring.js";
|
|
96
96
|
export { type RoleSpec } from "./session/roles.js";
|
|
97
97
|
export { type RoundConfig, type RoundSnapshot } from "./session/roundState.js";
|
|
98
|
+
export { createNearAgents, type NearAgentSnapshot, type NearAgentView, type NearAgentsOptions, type NearCrowdAgents, } from "./sim/nearAgents.js";
|
|
99
|
+
export { createNeedSet, type NeedDefinition, type NeedReading, type NeedSet, type NeedSnapshot } from "./sim/needs.js";
|
|
100
|
+
export { createOffScreenWorld, type NearAgents, type NearHandoff, type NearSpawn, type OffScreenAgentView, type OffScreenMode, type OffScreenOptions, type OffScreenPolicy, type OffScreenPolicyPatch, type OffScreenResidentSpec, type OffScreenSnapshot, type OffScreenStats, type OffScreenWorld, } from "./sim/offScreen.js";
|
|
101
|
+
export { resolveRoutine, type RoutineConfig, type RoutinePhase, type RoutineStop, type RoutineStopSpec, type RoutineTimeline, } from "./sim/routine.js";
|
|
98
102
|
export { createDecayMeterSet, decayMeterMoodles, decayMeterSnapshot, decayMeterState, decayMeters, initDecayMeters, refillMeter, type DecayMeterSet, type DecayMeterValues, type DecayModifier, } from "./survival/decayMeter.js";
|
|
99
103
|
export { createMoodleStack, stackMoodles, type Moodle, type MoodleStack } from "./survival/moodle.js";
|
|
100
104
|
export { createMultiRegionHealth, type MultiRegionHealth } from "./survival/regionHealth.js";
|
package/dist/gameplay.js
CHANGED
|
@@ -95,6 +95,10 @@ export { createTriggeredRuleEngine, } from "./rules/triggeredRules.js";
|
|
|
95
95
|
export { createRing, ringSampleAt } from "./session/ring.js";
|
|
96
96
|
export {} from "./session/roles.js";
|
|
97
97
|
export {} from "./session/roundState.js";
|
|
98
|
+
export { createNearAgents, } from "./sim/nearAgents.js";
|
|
99
|
+
export { createNeedSet } from "./sim/needs.js";
|
|
100
|
+
export { createOffScreenWorld, } from "./sim/offScreen.js";
|
|
101
|
+
export { resolveRoutine, } from "./sim/routine.js";
|
|
98
102
|
export { createDecayMeterSet, decayMeterMoodles, decayMeterSnapshot, decayMeterState, decayMeters, initDecayMeters, refillMeter, } from "./survival/decayMeter.js";
|
|
99
103
|
export { createMoodleStack, stackMoodles } from "./survival/moodle.js";
|
|
100
104
|
export { createMultiRegionHealth } from "./survival/regionHealth.js";
|
package/dist/meta/changelog.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating. */
|
|
2
|
-
export declare const VERSION = "0.
|
|
2
|
+
export declare const VERSION = "0.26.0";
|
|
3
3
|
/** One release's migrate steps plus added/changed/removed notes (typed mirror of CHANGELOG.md). */
|
|
4
4
|
export interface ChangelogEntry {
|
|
5
5
|
migrate: readonly string[];
|
package/dist/meta/changelog.js
CHANGED
|
@@ -1,7 +1,62 @@
|
|
|
1
1
|
/** Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating. */
|
|
2
|
-
export const VERSION = "0.
|
|
2
|
+
export const VERSION = "0.26.0";
|
|
3
3
|
/** Per-version engine changelog keyed by semver string (e.g. `"0.10.0"`). */
|
|
4
4
|
export const CHANGELOG = {
|
|
5
|
+
"0.26.0": {
|
|
6
|
+
migrate: [
|
|
7
|
+
"Bump lockstep SDK packages to `^0.26.0`: `@jgengine/{core,rapier,react,ws,node,sql,convex,shell,editor,assets,navbake}`. CLI `jgengine` is `0.23.0`; `@jgengine/github` is `0.13.0`.",
|
|
8
|
+
],
|
|
9
|
+
added: [
|
|
10
|
+
"`createLogBus` (`@jgengine/core/devtools/logBus`): a fixed-capacity ring of log lines tagged by owning system, with a separate ring for warnings and errors so a crash line is never pushed out by info traffic. `devtools.logs` now writes through it (`write(level, system, message)`, `snapshot()`, `counts()`, `dropped()`); `DevtoolsLogEntry` gained `system`.",
|
|
11
|
+
"`devtools.probes.registerSystem(id, { counters, timers, state })`: a system reports its own counters, named timings and one state line in a few lines, with no shell wiring. Readings surface as `systems` in `devtools.snapshot()`, in `debug_snapshot`, and in every perf record.",
|
|
12
|
+
"`PerfReport` (`@jgengine/core/devtools/report`): the one perf record shape shared by `bun run perf` and the in-page report.",
|
|
13
|
+
"The shell samples frame telemetry in-page in dev builds: `window.__JG.report()` returns that record, and the page pushes it about once a second to the dev server's `GET/POST /__jgengine/report` route. `bun run debug <game>` prints the record of a game already open and exits non-zero on a budget breach in `scripts/perf-budget.json`.",
|
|
14
|
+
"`createNeedSet` (`@jgengine/core/gameplay`) drains named needs on game time and reads them by index — decaying hunger, energy, fun with snapshot/restore and per-need rate retuning.",
|
|
15
|
+
"`createUtilityBrain` (`@jgengine/core/world`) picks an action from decaying-need appeal: linear, quadratic, logistic, exponential and threshold response curves, candidate cost (travel, queues, price), a switch margin plus minimum hold (hysteresis), declared interrupts, optional seeded jitter, and a devtools probe string.",
|
|
16
|
+
"`createNavRuntime` (`@jgengine/core/world`) consumes an editor-baked nav mesh: reads the `nav` bake off a scene document (`navMeshFromBake`), routes with the mesh query, resolves authored doors, ladders and jumps (`AuthoredNavLink`, priced by kind) onto the current surface, and swaps in a new bake after an edit while matching polygon identity by geometry — `applyMesh` returns a `remap` so only routes over changed polygons need re-planning.",
|
|
17
|
+
"`createNavCrowd` (`@jgengine/core/world`) moves many agents across that mesh: plans a corridor, follows the funnel-smoothed waypoints, keeps agents apart with reciprocal velocity obstacle avoidance, bounds path searches per tick by count and milliseconds, fans converging agents out with `spread`, and round-trips through `snapshot`/`restore`.",
|
|
18
|
+
"`createRvoCrowd` (`@jgengine/core/world`) is the avoidance solver on its own: RVO/ORCA half-plane solve over a uniform-grid neighbour search capped at `maxNeighbors`, deterministic tie-breaking, overlap relief, retunable `timeHorizon`/`neighborDist`/`maxNeighbors`/`sideBias`/`overlapRelief`, and `snapshot`/`restore` for saves and replays.",
|
|
19
|
+
"`resolveRoutine` (`@jgengine/core/gameplay`) resolves an authored day into real arrival and departure times against the world clock. Travel time is injected per stop or as a callback, so a stop departs early enough to walk to the next one and an arrival that runs late slips instead of teleporting; `phaseAt(time)` returns the held place, the walking leg and its progress, and `nextChangeAfter(time)` drives a scheduler. A day whose walks cannot fit its cycle throws at construction.",
|
|
20
|
+
"`createOffScreenWorld` (`@jgengine/core/gameplay`) keeps a whole population without simulating it: residents beyond the injected radius are cheap state (position, routine phase, needs) that only moves when the clock crosses a routine boundary, and they spawn into the game's own near simulation through a `NearAgents` interface when they come close. The radius, an optional per-agent cost model, a near budget and a per-tick switch budget are all injected; `snapshot`/`restore` round-trip the whole population, and crossing the boundary preserves identity, schedule progress, need values and position.",
|
|
21
|
+
"`createNearAgents` (`@jgengine/core/gameplay`) is the default near half of that seam: a nav crowd with a need set and utility brain per agent, goal-tracking as routines move residents between places, and a decision counter for tests and probes.",
|
|
22
|
+
"`createPerception` shares squad knowledge: `squadMates(observerId)` names an observer's roster, and a sighting one member makes is written into every mate's memory with `sourceId` (who saw it) and `sourceTick` (the observe pass it was recorded on). A mate's own sighting at the same instant replaces the hand-off. `maxSharesPerTick` (default 64, per observer pass) bounds a large roster and reports elided shares.",
|
|
23
|
+
"Perception memory is auditable without guessing: `memoryOf(observerId, targetId)` returns the one remembered target with its confidence right now or `null` once it has faded, and `stats()` counts pair checks, range prunes, cone tests, line-of-sight calls, sightings, heard stimuli, shares and dropped shares. The check order is range → cone → line of sight, so occlusion work only sees the pruned set. `resetStats()` opens a measurement window; `memories` are pruned per observer so a 200-observer pass does not rescan every memory.",
|
|
24
|
+
"`devtoolsName` registers a probe showing observers, remembered targets and the work counters, and `dispose()` removes it.",
|
|
25
|
+
"`createCoverField` and `coverPointsFromSolids` (`@jgengine/core/combat`) derive stand-able cover from authored solid boxes instead of a coordinate list: one row of points per vertical face, clear of the corners, filtered by an injected `walkable` test, rebuilt when the world is edited, and queried through a bounded `near(position, radius, limit)`. `CoverSource` is the seam, so a game with its own spatial index can supply cover without the box generator.",
|
|
26
|
+
"`selectCover` / `scoreCoverPoint` choose cover by threat: `hidden` is the confidence-weighted share of remembered threats whose line of sight the point (and a body-sized footprint at it) blocks, scored against the role's preferred fighting range, flank angle around the threat, distance, a squadmate's claim on the point, and whether the walk to it is a detour. Fading memories stop steering at `minConfidence`, and a point that hides from only one of two shooters is not cover.",
|
|
27
|
+
"`createSquadCombat` (`@jgengine/core/combat`) drives a squad over existing seams: role-driven cover selection, fire discipline (hold, lean out, shoot), suppression from `underFire()`, retreat below a role's health threshold, and relocation under sustained fire. Threat comes only from `threatsFromMemory`, i.e. the perception memory the game already keeps; movement goes through `NavCrowd` with a tighter arrival tolerance so a body actually reaches the stand point. Serializable through `snapshot()`/`restore()` (member decisions, crowd, tuning), retunable, and a `devtoolsName` probe reports pins, peeks, shots and the worst exposure.",
|
|
28
|
+
"`assignSquadRoles` and `threatsFromMemory` (`@jgengine/core/combat`): deterministic role assignment over a caller-authored `SquadComposition` (role ids, counts, ranges, flank angles, retreat thresholds and cover weights are all content), and a bounded, confidence-ranked threat list from one observer's memory.",
|
|
29
|
+
"A 2v4 arena soak proves the behavior: 90 simulated seconds, 2700 ticks, 4 squad members against 2 scripted raiders, worst exposure 2.33 s (bar 3 s), 0 of 38 exposure spells over 3 s, per-tick cost p50 0.05 ms / p95 0.19 ms.",
|
|
30
|
+
"`createDamageRuntime` (`@jgengine/core/combat`) composes a damage table, hit zones, the armor curve and carried statuses into one stateful model: `hit()` returns every stage of the number (`base × zone multiplier × type multiplier × (1 − armor mitigation)`), `tick()` runs status effects through the same table and armor, `timeToKill()` quotes a fight, and `table()` reads the whole chart. `snapshot()`/`restore()` carry registered targets and carried statuses; `retune()` swaps table, zones or status definitions without dropping either; a `devtoolsName` probe reports hits, crits, ticks and the last hit.",
|
|
31
|
+
"A damage table (`combat/damageTable`) of damage type × resistance class, with `armor / (armor + halfValue)` mitigation — armor equal to the half value halves the hit — a per-type `bypassArmor`, and `readDamageTable` / `formatDamageTable` to read every row at given armor values. An unknown damage type throws instead of silently dealing 1×.",
|
|
32
|
+
"Hit zones and crits (`combat/hitZones`): a zone carries a normal multiplier and an optional crit multiplier, `resolveHitZone` rolls once and returns one multiplier, and a crit replaces the zone multiplier rather than compounding with it. `hitZoneMultiplier` quotes the same number without a roll.",
|
|
33
|
+
"`createStatusRuntime` (`combat/statusRuntime`): data-defined statuses with the existing `StatusStackPolicy` (`refresh`, stack to `max`, `replace`, `ignore`) as the only thing that changes stacks, per-tick effects of `{ kind: \"damage\", channel }` or `{ kind: \"stat\", stat }`, damage routed through an injected `applyDamage` so resistances and armor still apply, bounded ticks per call, and plain-data state out and back.",
|
|
34
|
+
"Numbers from the tests: 25 base kinetic against a 150 HP target, 0.15 s between shots — 8 shots / 1.05 s unarmored, 20 shots / 2.85 s at 60 armor, 36 shots / 5.25 s at 200 armor; two stacked burns of 4 per tick deal 8, not 16; a 1.5× head zone with a 2.5× crit multiplier deals 2.5×, not 3.75×.",
|
|
35
|
+
"`createWeaponSystem` (`@jgengine/core/combat`) runs one `WeaponDefinition`: single, automatic or burst fire, hitscan or ballistic projectiles, recoil that moves the aim the shot flies down, spread bloom, interrupted/resumed reloads, a readable ammo economy (`ammo()`, `stats()`), `snapshot()`/`restore()` and a devtools probe. Every hit, hitscan or projectile, resolves through `createDamageRuntime`. Pellet work is bounded per tick (`maxShotsPerTick`, default 4) and per weapon (`maxInFlight`, default 32).",
|
|
36
|
+
"`WeaponDefinition` (`@jgengine/core/combat/weaponDefinition`) as plain JSON — rate, fire mode, delivery, magazine, handling, damage, falloff — with `assertWeaponDefinition`, `weaponIntervalMs`, `weaponDamageAt` and `weaponMaxInFlight`. A generated gun is the same shape.",
|
|
37
|
+
"`Magazine.pauseReload()` / `resumeReload()` / `isPaused()` suspend a reload and keep its progress; a paused reload is not `isReloading()`, does not advance in `tick`, and round-trips through `snapshot()`/`restore()`.",
|
|
38
|
+
"`environment.grading` on the scene document authors a per-game colour grade: shadow/midtone/highlight swatches plus tint, saturation and contrast bake into a small display-space LUT (`lutCubeFromGrading`, `colorGradeFromDocument` in `@jgengine/core/editor/environment`), edited in the Lighting workspace. The shell applies it from `editorLayers`, and a graded game needs no LUT asset or hardcoded pass.",
|
|
39
|
+
"Clamped auto-exposure: `postProcessing.autoExposure: { min, max, adaptationSeconds } | false` meters the pre-tone-map frame every four frames toward 18% grey and stays inside the authored range. `WHITE_POINT` and `MID_GREY` in `@jgengine/core/render/colorPipeline` are the single documented calibration the pipeline uses.",
|
|
40
|
+
"`GradeConfig.lut` also accepts a baked `{ cube: { size, data } }` LUT, sampled trilinearly after the numeric grade.",
|
|
41
|
+
"`MATERIAL_LIBRARY` (`@jgengine/assets/catalogs/materialLibrary`) names 56 PBR surfaces — wet asphalt, painted metal, stucco, brick, wood, foliage with cut-out coverage and sheen, glass, water, sand, concrete and more — each mapping a name to an ambientCG source, a world tile size in metres and a physical response. `libraryMaterialAsset(\"asphalt/wet\")` returns the editable `MaterialAsset` those names resolve to, and `filterLibraryMaterials` backs pickers. `assets pull library` pulls every pinned map set.",
|
|
42
|
+
"`TriplanarAssetMaterial` / `useTriplanarAssetMaterial` / `applyTriplanarProjection` (`@jgengine/shell/render/triplanarMaterial`) render a material asset from world position, so world geometry without a `uv` attribute still gets colour, normal, roughness and AO maps. Tile size is world metres; instanced meshes project in instanced space.",
|
|
43
|
+
"The editor's Materials workspace browses the library by name and writes the chosen entry into the document's material assets.",
|
|
44
|
+
],
|
|
45
|
+
changed: [
|
|
46
|
+
"`createPerception` (`@jgengine/core/world`, also `@jgengine/core/sensor/perception`) takes `isSegmentClear` instead of `occluded`: the game supplies collision as a \"nothing blocks this segment\" test, and it is only called for pairs already inside range and cone. Migration: flip the predicate — a `occluded: (from, to) => blocked(from, to)` becomes `isSegmentClear: (from, to) => !blocked(from, to)`.",
|
|
47
|
+
"`MagazineSnapshot` gains an optional `paused: true` field, present only while a reload is suspended, so existing snapshots and idle shapes are unchanged.",
|
|
48
|
+
"AgX is the pipeline's tone map: `postProcessing.toneMapping` defaults to `\"agx\"`, and the `cinematic`, `photoreal`, `comic`, `retro` and `STUDIO_STAGE_POST` configs now pick it too. `\"toon\"` keeps `\"linear\"` on purpose. Games that authored `\"aces\"` explicitly are unchanged.",
|
|
49
|
+
"The grade pass samples LUT tiles at texel centres and blends the blue axis by hand, so a supplied strip LUT no longer bleeds across slices.",
|
|
50
|
+
"`bun run debug <game>` now finds the server `bun dev` started. The website dev server publishes the same checkout marker the runner does (`scripts/devServerMarker.ts`, written at listen, removed on shutdown, dev builds only), and the marker records the host that answers, since Vite's default `localhost` can be IPv6-only where `127.0.0.1` hangs.",
|
|
51
|
+
"The website dev server serves `GET/POST /__jgengine/report` too, so a game opened at `/games/<id>` reports from the `/play` runner it embeds.",
|
|
52
|
+
"The in-page sampler installs when the dev report route answers, not on a build-time `PROD` check: the `/play` runner is a production build, so the old check silently disabled the report on `bun dev`.",
|
|
53
|
+
"A report taken inside the sampler's warmup is no longer judged on link/framebuffer counts, which only mean something after the warmup baseline exists.",
|
|
54
|
+
"`extractMaterialMaps` and `buildMaterialCatalog` now recognise ambientCG's `_Opacity` map, so cut-out foliage and decal scans keep their coverage instead of rendering as solid cards.",
|
|
55
|
+
"Capture runs no longer leak a ~150 MB temp Chrome profile each. `shoot`, `drive` and `perf` delete their profile on every exit path — success, throw, watchdog and SIGINT — `bun run shoot` reclaims profiles a killed run left untouched for three hours, the capture guard sweeps the profile of a run it force-kills, and the generated `browser.mjs` harness for game repos removes its own. A capture also refuses to start when the temp drive has less free space than it needs.",
|
|
56
|
+
"The post-processing chain, graphics settings and textured terrain material keep a stable identity across renders. Before, every render rebuilt the effect composer and recompiled shaders, which dropped games to single-digit FPS.",
|
|
57
|
+
],
|
|
58
|
+
removed: [],
|
|
59
|
+
},
|
|
5
60
|
"0.25.0": {
|
|
6
61
|
migrate: [
|
|
7
62
|
"Bump lockstep SDK packages to `^0.25.0`: `@jgengine/{core,rapier,react,ws,node,sql,convex,shell,editor,assets,navbake}`. CLI `jgengine` is `0.22.0`; `@jgengine/github` is `0.12.0`.",
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { Vec3 } from "../world/geometry.js";
|
|
2
|
+
import type { NavRebuildStats, NavRegion, NavRuntime } from "./navRuntime.js";
|
|
3
|
+
import { type RvoTunables } from "../ai/rvo.js";
|
|
4
|
+
/** How one crowd agent was configured when it joined. */
|
|
5
|
+
export interface NavCrowdAgentSpec {
|
|
6
|
+
position: Vec3;
|
|
7
|
+
goal: Vec3;
|
|
8
|
+
/** World units per second; defaults to the crowd's `speed`. */
|
|
9
|
+
speed?: number;
|
|
10
|
+
/** Avoidance radius; defaults to the crowd's `radius`. */
|
|
11
|
+
radius?: number;
|
|
12
|
+
}
|
|
13
|
+
/** One crowd agent's live state, as a game or the devtools overlay reads it. */
|
|
14
|
+
export interface NavCrowdAgentView {
|
|
15
|
+
id: string;
|
|
16
|
+
position: Vec3;
|
|
17
|
+
velocity: readonly [number, number];
|
|
18
|
+
goal: Vec3;
|
|
19
|
+
/** Index of the waypoint it is steering toward; equals the waypoint count once the route is done. */
|
|
20
|
+
waypoint: number;
|
|
21
|
+
waypoints: number;
|
|
22
|
+
arrived: boolean;
|
|
23
|
+
/** True while the agent waits for its turn in the bounded re-plan budget. */
|
|
24
|
+
waitingForPlan: boolean;
|
|
25
|
+
}
|
|
26
|
+
/** Result of telling the crowd the nav mesh changed. */
|
|
27
|
+
export interface NavCrowdRevalidate {
|
|
28
|
+
/** Agents whose remaining route crossed replaced polygons and were queued for a new plan. */
|
|
29
|
+
queued: number;
|
|
30
|
+
/** Agents whose route stayed entirely on kept polygons and was left alone. */
|
|
31
|
+
untouched: number;
|
|
32
|
+
}
|
|
33
|
+
/** Counters a devtools probe or test reads. */
|
|
34
|
+
export interface NavCrowdStats {
|
|
35
|
+
agents: number;
|
|
36
|
+
arrived: number;
|
|
37
|
+
waitingForPlan: number;
|
|
38
|
+
/** Path searches run since creation — add, setGoal, queued plans and repaths. */
|
|
39
|
+
plans: number;
|
|
40
|
+
/** Path searches that returned no route. */
|
|
41
|
+
unroutable: number;
|
|
42
|
+
ticks: number;
|
|
43
|
+
/** Slowest single `tick` seen so far, in milliseconds. */
|
|
44
|
+
slowestTickMs: number;
|
|
45
|
+
}
|
|
46
|
+
/** Serializable crowd state: agents, their routes, and the avoidance tuning they run under. */
|
|
47
|
+
export interface NavCrowdSnapshot {
|
|
48
|
+
tunables: {
|
|
49
|
+
speed: number;
|
|
50
|
+
radius: number;
|
|
51
|
+
goalRadius: number;
|
|
52
|
+
spread: number;
|
|
53
|
+
waypointRadius: number;
|
|
54
|
+
replanBudget: number;
|
|
55
|
+
replanMs: number;
|
|
56
|
+
avoid: RvoTunables;
|
|
57
|
+
};
|
|
58
|
+
agents: {
|
|
59
|
+
id: string;
|
|
60
|
+
position: Vec3;
|
|
61
|
+
velocity: readonly [number, number];
|
|
62
|
+
goal: Vec3;
|
|
63
|
+
offsetX: number;
|
|
64
|
+
offsetZ: number;
|
|
65
|
+
speed: number;
|
|
66
|
+
radius: number;
|
|
67
|
+
waypoints: Vec3[];
|
|
68
|
+
waypoint: number;
|
|
69
|
+
arrived: boolean;
|
|
70
|
+
polys: number[];
|
|
71
|
+
waitingForPlan: boolean;
|
|
72
|
+
}[];
|
|
73
|
+
}
|
|
74
|
+
/** Tunables for {@link createNavCrowd}, every one retunable in play. */
|
|
75
|
+
export interface NavCrowdOptions {
|
|
76
|
+
/** Default walking speed for agents that do not set their own. */
|
|
77
|
+
speed?: number;
|
|
78
|
+
radius?: number;
|
|
79
|
+
/** Distance to the goal that counts as arrived. Default 0.5. */
|
|
80
|
+
goalRadius?: number;
|
|
81
|
+
/**
|
|
82
|
+
* Radius, in world units, over which agents targeting the same goal fan out onto distinct spots
|
|
83
|
+
* (`ai/crowd`'s stable per-id `spreadOffset`). `0` walks every agent to the exact goal. Default 0.
|
|
84
|
+
*/
|
|
85
|
+
spread?: number;
|
|
86
|
+
/** Distance to a waypoint that advances to the next one. Default 0.6. */
|
|
87
|
+
waypointRadius?: number;
|
|
88
|
+
/** Most path searches one `tick` may run for queued agents. Default 8. */
|
|
89
|
+
replanBudget?: number;
|
|
90
|
+
/** Milliseconds of path searching one `tick` may spend, whatever the budget. Default 1.5. */
|
|
91
|
+
replanMs?: number;
|
|
92
|
+
/** Avoidance tuning for the internal {@link RvoCrowd}. */
|
|
93
|
+
avoid?: Partial<RvoTunables>;
|
|
94
|
+
/** Register a devtools probe under this name so the perf overlay shows crowd counters. */
|
|
95
|
+
devtoolsName?: string;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Agents that cross an editor-baked nav mesh: each one plans a corridor with {@link NavRuntime},
|
|
99
|
+
* walks the funnel-smoothed waypoints, and keeps its distance through reciprocal velocity obstacle
|
|
100
|
+
* avoidance. Goals, arrivals and replans are serializable; a mesh edit only re-plans the agents
|
|
101
|
+
* whose remaining route actually crossed the rebuilt region.
|
|
102
|
+
*/
|
|
103
|
+
export interface NavCrowd {
|
|
104
|
+
/** Add an agent and plan its first route; false when the id is already present. */
|
|
105
|
+
add(id: string, spec: NavCrowdAgentSpec): boolean;
|
|
106
|
+
remove(id: string): boolean;
|
|
107
|
+
has(id: string): boolean;
|
|
108
|
+
count(): number;
|
|
109
|
+
ids(): readonly string[];
|
|
110
|
+
/** Send an agent somewhere new; it re-plans on the next tick within the re-plan budget. */
|
|
111
|
+
setGoal(id: string, goal: Vec3): boolean;
|
|
112
|
+
/** Plan one agent now, ignoring the budget. Returns false when no route exists. */
|
|
113
|
+
plan(id: string): boolean;
|
|
114
|
+
agent(id: string): NavCrowdAgentView | null;
|
|
115
|
+
/** Advance every agent by `dt` seconds. */
|
|
116
|
+
tick(dt: number): void;
|
|
117
|
+
/**
|
|
118
|
+
* Queue a re-plan for agents whose remaining route crossed polygons the last
|
|
119
|
+
* {@link NavRuntime.applyMesh} replaced or moved; routes on kept polygons are left intact.
|
|
120
|
+
*/
|
|
121
|
+
revalidate(stats: NavRebuildStats, region?: NavRegion): NavCrowdRevalidate;
|
|
122
|
+
stats(): NavCrowdStats;
|
|
123
|
+
retune(patch: NavCrowdOptions): void;
|
|
124
|
+
snapshot(): NavCrowdSnapshot;
|
|
125
|
+
restore(next: NavCrowdSnapshot): void;
|
|
126
|
+
/** Drop the devtools probe registered by `devtoolsName`. Safe to call twice. */
|
|
127
|
+
dispose(): void;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Compose {@link createNavRuntime} and {@link createRvoCrowd} into a moving crowd: plan on the mesh,
|
|
131
|
+
* follow the smoothed corridor, avoid each other. Deterministic — agents advance in insertion order
|
|
132
|
+
* and the avoidance solve breaks ties by slot, not chance.
|
|
133
|
+
*
|
|
134
|
+
* @capability nav-crowd move many agents across an editor-baked nav mesh with avoidance
|
|
135
|
+
*/
|
|
136
|
+
export declare function createNavCrowd(options: NavCrowdOptions & {
|
|
137
|
+
nav: NavRuntime;
|
|
138
|
+
}): NavCrowd;
|