@jgengine/shell 0.15.0-next.0 → 0.15.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 +17 -0
- package/dist/audio/synthEngine.js +12 -2
- package/package.json +4 -4
- package/skills/jgengine/api.md +2 -2
- package/skills/jgengine-world/api.md +19 -0
package/CHANGELOG.md
CHANGED
|
@@ -28,6 +28,17 @@ At publish, rename this heading to the new version and mirror the entries into
|
|
|
28
28
|
|
|
29
29
|
### Migrate
|
|
30
30
|
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
|
|
37
|
+
## 0.15.0
|
|
38
|
+
|
|
39
|
+
### Migrate
|
|
40
|
+
|
|
41
|
+
- **Bump lockstep SDK packages to `^0.15.0`:** `@jgengine/{core,react,ws,node,sql,convex,shell,editor,assets}`. CLI `jgengine` is `0.12.0`; `@jgengine/github` is `0.2.0`.
|
|
31
42
|
- **`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
43
|
- **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
44
|
- **Every game must now declare its run-phase story — silence is a gate error (#1337).** The run-phase
|
|
@@ -73,10 +84,16 @@ At publish, rename this heading to the new version and mirror the entries into
|
|
|
73
84
|
|
|
74
85
|
### Added
|
|
75
86
|
|
|
87
|
+
- **`advanceStepCadence` (`@jgengine/core/audio/stepCadence`) — distance-driven footstep timing.** Pure state-in/state-out: feed it the ground distance a mover covered this frame and it reports how many steps landed and which foot led, so foley stays locked to distance instead of a wall-clock timer that desyncs the moment speed changes. Configurable stride, per-advance `strideScale` for crouch/sprint, and a shorter first step from rest so walking off is heard immediately.
|
|
88
|
+
- **`resolveMusicState` (`@jgengine/core/audio/musicState`) — combat-reactive soundtrack selection.** The engine could already crossfade themes but nothing decided *when*, so every game re-handrolled the threshold and got a track that flapped as one enemy walked in and out of range. Give it a tier ladder (intensity → theme) and it resolves the theme to play: escalation is immediate, de-escalation waits for the tier's `holdMs` and needs intensity to fall a `release` margin below the tier it is leaving. Pure and serialisable.
|
|
89
|
+
- **Synth voices take a `sustain` (`ToneVoice`/`NoiseVoice`).** Seconds held at full gain before the decay. Default 0 preserves every existing patch. Without it there was no way to author a *sustained* sound at all: every voice decayed to silence inside its duration, so a patch rendered once and looped as an ambient bed pulsed audibly once per loop. Set `sustain` to `duration` for a flat wind, rain, or machine-hum bed that loops seamlessly.
|
|
90
|
+
|
|
76
91
|
- **`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
92
|
|
|
78
93
|
### Fixed
|
|
79
94
|
|
|
95
|
+
- **Noise voices longer than the shared noise buffer no longer go silent partway through.** The 1-second buffer is sampled from a random offset, so any `NoiseVoice` whose duration exceeded the remainder ran off the end and finished in silence — invisible on short impact bursts, fatal for anything sustained. The source now wraps; the voice still ends on time.
|
|
96
|
+
|
|
80
97
|
- **`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
98
|
- **`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
99
|
- **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.
|
|
@@ -16,10 +16,13 @@ function realizeTone(ctx, out, voice, cueGain, at) {
|
|
|
16
16
|
osc.frequency.setValueAtTime(voice.freq, t);
|
|
17
17
|
if (voice.slideTo !== undefined)
|
|
18
18
|
osc.frequency.exponentialRampToValueAtTime(Math.max(1, voice.slideTo), t + voice.duration);
|
|
19
|
+
const sustainEnd = Math.max(attack, voice.sustain ?? 0);
|
|
19
20
|
const g = ctx.createGain();
|
|
20
21
|
g.gain.setValueAtTime(0, t);
|
|
21
22
|
g.gain.linearRampToValueAtTime(peak, t + attack);
|
|
22
|
-
|
|
23
|
+
if (sustainEnd > attack)
|
|
24
|
+
g.gain.setValueAtTime(peak, t + sustainEnd);
|
|
25
|
+
g.gain.exponentialRampToValueAtTime(0.001, t + Math.max(voice.duration, sustainEnd + 0.001));
|
|
23
26
|
osc.connect(g).connect(out);
|
|
24
27
|
osc.start(t);
|
|
25
28
|
osc.stop(t + voice.duration + 0.05);
|
|
@@ -31,12 +34,19 @@ function realizeNoise(ctx, out, noiseBuf, voice, cueGain, at) {
|
|
|
31
34
|
const src = ctx.createBufferSource();
|
|
32
35
|
src.buffer = noiseBuf;
|
|
33
36
|
src.playbackRate.value = 0.8 + Math.random() * 0.4;
|
|
37
|
+
// The shared buffer is 1 s and playback starts at a random offset inside it, so any voice longer
|
|
38
|
+
// than the remainder used to run off the end and go silent early. Wrapping keeps it noise throughout;
|
|
39
|
+
// the `duration` argument to start() still ends the voice on time.
|
|
40
|
+
src.loop = true;
|
|
34
41
|
const filter = ctx.createBiquadFilter();
|
|
35
42
|
filter.type = voice.filterType ?? "lowpass";
|
|
36
43
|
filter.frequency.value = voice.filterFreq;
|
|
44
|
+
const sustain = Math.max(0, voice.sustain ?? 0);
|
|
37
45
|
const g = ctx.createGain();
|
|
38
46
|
g.gain.setValueAtTime(peak, t);
|
|
39
|
-
|
|
47
|
+
if (sustain > 0)
|
|
48
|
+
g.gain.setValueAtTime(peak, t + sustain);
|
|
49
|
+
g.gain.exponentialRampToValueAtTime(0.001, t + Math.max(voice.duration * decay, sustain + 0.001));
|
|
40
50
|
src.connect(filter).connect(g).connect(out);
|
|
41
51
|
src.start(t, Math.random() * 0.5, voice.duration);
|
|
42
52
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jgengine/shell",
|
|
3
|
-
"version": "0.15.0
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Game player shell for JGengine: React Three Fiber canvas, orbit camera, input tracking, HUD mounting, GameUiPreview, and a demo game. Consumers supply a GameRegistry.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -35,9 +35,9 @@
|
|
|
35
35
|
"test": "bun test src"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@jgengine/core": "^0.15.0
|
|
39
|
-
"@jgengine/react": "^0.15.0
|
|
40
|
-
"@jgengine/ws": "^0.15.0
|
|
38
|
+
"@jgengine/core": "^0.15.0",
|
|
39
|
+
"@jgengine/react": "^0.15.0",
|
|
40
|
+
"@jgengine/ws": "^0.15.0"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"@react-three/drei": "^10.0.0",
|
package/skills/jgengine/api.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
- `CHANGELOG` (const): const CHANGELOG: Record<string, ChangelogEntry> — Per-version engine changelog keyed by semver string (e.g. `"0.10.0"`).
|
|
8
8
|
- `ChangelogEntry` (interface): interface ChangelogEntry — One release's migrate steps plus added/changed/removed notes (typed mirror of CHANGELOG.md).
|
|
9
|
-
- `VERSION` (const): const VERSION: "0.15.0
|
|
9
|
+
- `VERSION` (const): const VERSION: "0.15.0" — Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating.
|
|
10
10
|
|
|
11
11
|
## @jgengine/core/authoring
|
|
12
12
|
|
|
@@ -228,7 +228,7 @@
|
|
|
228
228
|
|
|
229
229
|
- `CHANGELOG` (const): const CHANGELOG: Record<string, ChangelogEntry> — Per-version engine changelog keyed by semver string (e.g. `"0.10.0"`).
|
|
230
230
|
- `ChangelogEntry` (interface): interface ChangelogEntry — One release's migrate steps plus added/changed/removed notes (typed mirror of CHANGELOG.md).
|
|
231
|
-
- `VERSION` (const): const VERSION: "0.15.0
|
|
231
|
+
- `VERSION` (const): const VERSION: "0.15.0" — Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating.
|
|
232
232
|
|
|
233
233
|
## @jgengine/core/runtime/adapter
|
|
234
234
|
|
|
@@ -223,6 +223,25 @@
|
|
|
223
223
|
- `notesInWindow` (function): function notesInWindow(theme: MusicTheme, anchorSec: number, fromSec: number, toSec: number): ScheduledNote[] — Pure lookahead scheduler: every note occurrence of `theme` whose onset falls in the half-open window `(fromSec, toSec]`, given the theme's loop-zero at `anchorSec`. Handles any number of loop wraps, so a director calls it once per tick with a non-overlapping window and never double-schedules a note.
|
|
224
224
|
- `themeLoopSeconds` (function): function themeLoopSeconds(theme: MusicTheme): number — Loop length of a theme in seconds.
|
|
225
225
|
|
|
226
|
+
## @jgengine/core/audio/musicState
|
|
227
|
+
|
|
228
|
+
- `MusicStateConfig` (interface): interface MusicStateConfig — The tier ladder plus the release margin that keeps a hovering intensity from flapping between tiers.
|
|
229
|
+
- `MusicStateInput` (interface): interface MusicStateInput — The current intensity signal and clock reading for one resolve.
|
|
230
|
+
- `MusicStateResult` (interface): interface MusicStateResult — Next state, the theme that should be playing, and whether this advance is the one that changed it.
|
|
231
|
+
- `MusicStateSnapshot` (interface): interface MusicStateSnapshot — Which tier is playing and when it was entered, on the caller's clock. Serialisable.
|
|
232
|
+
- `MusicTier` (interface): interface MusicTier — Hysteretic music-state selector: turns a continuous intensity signal (nearby threat, alert count, chase proximity) into a stable theme id. The engine can already crossfade a soundtrack; this decides *when*, without the flapping a bare threshold produces when one enemy walks in and out of range.
|
|
233
|
+
- `createMusicState` (function): function createMusicState(): MusicStateSnapshot — Fresh state with no tier entered, so the first resolve always settles and reports `changed`.
|
|
234
|
+
- `resolveMusicState` (function): function resolveMusicState(state: MusicStateSnapshot, input: MusicStateInput, config: MusicStateConfig): MusicStateResult — Pick the theme for the current intensity. Escalation is immediate; de-escalation waits for the tier's `holdMs` and needs the intensity to fall a `release` margin below the tier it is leaving.
|
|
235
|
+
|
|
236
|
+
## @jgengine/core/audio/stepCadence
|
|
237
|
+
|
|
238
|
+
- `StepCadenceConfig` (interface): interface StepCadenceConfig — Distance-driven footstep cadence. Pure state-in/state-out: feed it how far a mover travelled this frame and it tells you how many steps landed, so foley stays locked to ground distance instead of a wall-clock timer that desyncs when speed changes.
|
|
239
|
+
- `StepCadenceInput` (interface): interface StepCadenceInput — What the mover did this advance: ground distance covered, whether feet are planting, and the stride multiplier.
|
|
240
|
+
- `StepCadenceResult` (interface): interface StepCadenceResult — Outcome of one advance: the next state plus how many steps landed and which foot led.
|
|
241
|
+
- `StepCadenceState` (interface): interface StepCadenceState — Cadence bookkeeping between advances — banked distance, last moving state, and the step count whose parity is the foot.
|
|
242
|
+
- `advanceStepCadence` (function): function advanceStepCadence(state: StepCadenceState, input: StepCadenceInput, config: StepCadenceConfig): StepCadenceResult — Bank this advance's distance and report the steps it completed. Pure — same state and input always give the same result, so footstep timing is testable headless and replays identically.
|
|
243
|
+
- `createStepCadenceState` (function): function createStepCadenceState(): StepCadenceState — Fresh cadence state for a mover that is standing still.
|
|
244
|
+
|
|
226
245
|
## @jgengine/core/audio/synth
|
|
227
246
|
|
|
228
247
|
- `NoiseVoice` (interface): interface NoiseVoice — A filtered white-noise burst — impacts, whooshes, breath, crackle. Realised from a shared 1s noise buffer at a randomised playback rate and start offset, decaying exponentially to silence at `duration * decay`.
|