diff --git a/.claude/skills/jgengine/api.md b/.claude/skills/jgengine/api.md index f9e943725..51724e60f 100644 --- a/.claude/skills/jgengine/api.md +++ b/.claude/skills/jgengine/api.md @@ -6,7 +6,7 @@ - `CHANGELOG` (const): const CHANGELOG: Record — Per-version engine changelog keyed by semver string (e.g. `"0.10.0"`). - `ChangelogEntry` (interface): interface ChangelogEntry — One release's migrate steps plus added/changed/removed notes (typed mirror of CHANGELOG.md). -- `VERSION` (const): const VERSION: "0.37.0" — Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating. +- `VERSION` (const): const VERSION: "0.38.0" — Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating. ## @jgengine/core/authoring @@ -285,7 +285,7 @@ - `CHANGELOG` (const): const CHANGELOG: Record — Per-version engine changelog keyed by semver string (e.g. `"0.10.0"`). - `ChangelogEntry` (interface): interface ChangelogEntry — One release's migrate steps plus added/changed/removed notes (typed mirror of CHANGELOG.md). -- `VERSION` (const): const VERSION: "0.37.0" — Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating. +- `VERSION` (const): const VERSION: "0.38.0" — Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating. ## @jgengine/core/runtime/adapter diff --git a/CHANGELOG.md b/CHANGELOG.md index 778e27f27..e2ccf41b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,69 @@ At publish, rename this heading to the new version and mirror the entries into `packages/core/src/meta/changelog.ts` (the typed `CHANGELOG` export). --> +## 0.38.0 + +### Migrate + +- **Bump lockstep SDK packages to `^0.38.0`:** `@jgengine/{core,rapier,react,ws,node,sql,convex,shell,editor,assets,navbake}`. CLI `jgengine` is `0.35.0`; `@jgengine/github` is `0.25.0`. +- Composing a session loop directly: pass your rule as `settle`, `carries: false` when nothing is carried, and your authored options as `offers`. `createRunLoop` is removed one minor release after Beacon Bastion and Drift Foundry stop using it. + +### Added + +- `GameMenu` and `GamePauseMenu` (`@jgengine/react/gameMenu`): a full-bleed menu surface over the live scene or drifting key art, with a token-sized logo, an animated navigable verb list, a generated selection tick and a detail pane per verb. The document scroll is locked while it is open, so the surface is exactly the viewport at any size. +- Panes for the detail slot: `ContinuePane`, `NewCharacterPane`, `SaveSlotsPane` (slot cards with a `meta.thumbnail`, playtime, progress, timestamp and a two-press delete) and `CreditsPane` (`@jgengine/react/gameMenuSaves`). +- `shrinkCapture` (`@jgengine/react/saveThumbnail`) turns a `captureCanvas` frame into a card-sized thumbnail data URL. +- `createSessionLoop` (`@jgengine/core/run/sessionLoop`) is one session loop for every game: a session opens, may carry a quantity, closes at its boundary, and the game's own `settle` callback decides what happens to what it carried. `carries: false` for a session with nothing at stake, and an optional `offers` table (with `present`/`choose`) for a game whose next session opens with an authored change; without a table nothing is drawn. `installSessionProbe` reports the live session on `window.__jgengineRun`. + +### Changed + +- `switchSlot(slot, { flush: false })` (`@jgengine/core/game/gameSave`) loads a slot without first writing the live state over it. Opening a save from a menu uses it so Continue cannot overwrite the save it resumes. +- The dev runner's registry and environment modules import outside Vite, so a test or tool can read them. +- `createRunLoop` is now a thin composition of `createSessionLoop` and is deprecated: bank-or-lose and a per-run change table are one game type's rules, so they live in the composition instead of the base. Its API, state shape and tests are unchanged. One difference: the option draw is seeded under `:offer:` rather than `:run:` (`seededRng(\`${seed}:offer:${index}\`)`), so a career that keeps its seed draws a different change on a given session than 0.37.0 did for the same seed. Ids, payloads and saves stay valid: a saved `changeId` still restores the same change. + +### Fixed + +- `shoot` fails instead of writing a PNG when the whole frame is one flat colour, and when a scene is still behind its loading cover. Both used to write a plausible screenshot that read as evidence of a rendered frame. +- The editor's play-mode pixel test waits for the warmup cover to clear before screenshotting. It shot the cover, so it read `0.9988` one-colour locally and passed in CI where the race fell the other way. +- `shoot` prints the WebGL2 renderer it draws with and fails the capture outright when the launched browser has none. + +Root cause: capture readiness was trusted to mean "the scene has rendered", but a loading cover and a canvas that never committed both photograph as a flat frame, and nothing measured the frame after the fact. Headless Chrome on this PC was never the problem: it renders with the GPU (`ANGLE (NVIDIA GeForce RTX 3070, D3D11)`) and with SwiftShader when asked. + +Before: `bun run shoot --url ` exited 0 and wrote a one-colour PNG, and `packages/editor/src/CreatorApp.test.ts` failed locally at `dominantColorShare 0.9988`. +After: the same shoot exits 1 with `the whole frame is one flat color (100.0%) - no scene reached the capture - nothing was written`, and the editor test passes in 12s with sky and ground in the PNG. + +Cost: forcing SwiftShader on the same game shot costs 2.4x the capture (16.0s vs 6.7s warm, beacon-bastion play at half size), so the launch keeps the native GPU. The two checks add about 10ms to a shot. + +Verification: +- `bun test scripts/shot-metrics.test.ts` - pass +- `bun test packages/editor/src/CreatorApp.test.ts` - pass +- `bun run shoot beacon-bastion --mode play --size half` - exit 0, scene renders +- `bun run shoot --url ` - exit 1, no PNG written +- `bun run verify:fast` - pass + +The CI reliability fix this lane also carried (a browser setup hook shorter than Playwright's launch default) is #2335, so either change can be reverted without the other. +- The publish workflow's last step no longer reports a whole release as unpublished because it ran seconds after `npm publish`. `check:release-artifacts --wait ` re-checks while the only failures are absent packages (no metadata, version missing, tarball 404) and gives up when the budget runs out; a wrong version in the tarball, a missing subpath or a main entry that will not import still fails on the first check. The 0.37.0 publish run failed this step at 04:48 listing all 13 packages, then npm served every one of them. The step now passes `-- --wait 300`. +- A Playwright test file's `beforeAll` now outlives the Chrome launch it waits on, and `browser-test-budget` rejects a file that leaves the hook at or under Playwright's 30 s launch default. On `test (g)` in #2326 the hook's 30 s budget and that default expired together, so CI reported only `(unnamed) hook timed out` beside `killed 1 dangling process` and nothing named the launch. +- `packages/react/src/dialogBehavior.test.ts` gives its browser navigation its own 20 s deadline and one retry inside a 60 s hook. + +Root cause: Bun prints a `beforeAll` timeout as an unnamed hook timeout, so a setup budget equal to Playwright's own 30 s launch default reports the anonymous timer instead of the launch that stalled. + +Before: `test (g)` on #2326 reported `(fail) (unnamed) [30001.51ms] ^ a beforeEach/afterEach hook timed out` and `killed 1 dangling process`. +After: a stall now surfaces as Playwright's own `Timeout ... exceeded`, and `bun scripts/browser-test-budget.ts` names any file whose setup hook sits at or under the launch default. + +Verification: +- `bun scripts/browser-test-budget.ts` - pass, and the new rule names the unraised files on the previous tip +- `bun test scripts/browser-test-budget.test.ts` - pass +- `bun test packages/react/src/dialogBehavior.test.ts packages/react/src/scrollRail.test.ts packages/shell/src/settingsBoundary.test.ts` - pass +- `bun run verify:fast` - pass +- A prop placed at runtime honours its model's `variants` levels. `StaticObjectModels` applied the level switch only inside its batched branch, so a prop placed with `scene.object.place` — Harbor Heat's palm line — drew its full asset at every camera distance: 15 palms at 9,134 triangles each, and pointing the far level at a dispatch booth changed nothing on screen. `IsolatedEntityModel` now mounts `SingleVariantModel`, which asks the same `createModelVariantSelector` and `lodBandIndex` authority the batched path uses, at the prop's own camera distance, and keeps only the active level mounted so both levels never draw at once. The switch carries the selector's hysteresis, warms every level at mount, measures only the near level, and reads the placement from the scene store through scratch vectors, so a frame allocates nothing. One palm past 60 m draws 4,110 triangles instead of 9,134 (5,024 saved, 45% of the full level). + +- `singleVariantLevel` and `singleVariantModel` (`@jgengine/shell` internal, `render/singleVariantLod`) are the isolated path's level decision and the model it draws, so a fixture pins the same switch the component runs. + +`bun test packages/shell/src/render/singleVariantLod.test.ts` — 6 pass. With the old render decision restored, the wiring test fails (`Expected: SingleVariantModel, Received: EntityModel`). +`bun --cwd=packages/shell run test` — 1,229 pass, 0 fail. +`bun --cwd=packages/shell run check-types` — clean. + ## 0.37.0 ### Migrate diff --git a/bun.lock b/bun.lock index 672b3603f..deb1f851f 100644 --- a/bun.lock +++ b/bun.lock @@ -186,7 +186,7 @@ }, "packages/assets": { "name": "@jgengine/assets", - "version": "0.37.0", + "version": "0.38.0", "bin": { "assets": "dist/cli/pull.js", }, @@ -194,7 +194,7 @@ "@gltf-transform/core": "4.2.1", "@gltf-transform/extensions": "4.2.1", "@gltf-transform/functions": "4.2.1", - "@jgengine/core": "^0.36.0", + "@jgengine/core": "^0.38.0", "fflate": "^0.8.2", }, "devDependencies": { @@ -203,9 +203,9 @@ }, "packages/convex": { "name": "@jgengine/convex", - "version": "0.37.0", + "version": "0.38.0", "dependencies": { - "@jgengine/core": "^0.36.0", + "@jgengine/core": "^0.38.0", }, "devDependencies": { "@types/react": "^19", @@ -219,17 +219,17 @@ }, "packages/core": { "name": "@jgengine/core", - "version": "0.37.0", + "version": "0.38.0", }, "packages/editor": { "name": "@jgengine/editor", - "version": "0.37.0", + "version": "0.38.0", "dependencies": { - "@jgengine/assets": "^0.36.0", - "@jgengine/core": "^0.36.0", - "@jgengine/navbake": "^0.36.0", - "@jgengine/react": "^0.36.0", - "@jgengine/shell": "^0.36.0", + "@jgengine/assets": "^0.38.0", + "@jgengine/core": "^0.38.0", + "@jgengine/navbake": "^0.38.0", + "@jgengine/react": "^0.38.0", + "@jgengine/shell": "^0.38.0", }, "devDependencies": { "@react-three/drei": "^10.7.7", @@ -252,16 +252,16 @@ }, "packages/github": { "name": "@jgengine/github", - "version": "0.24.0", + "version": "0.25.0", }, "packages/jgengine": { "name": "jgengine", - "version": "0.34.0", + "version": "0.35.0", "bin": { "jgengine": "dist/cli/index.js", }, "dependencies": { - "@jgengine/assets": "^0.36.0", + "@jgengine/assets": "^0.38.0", }, "devDependencies": { "@types/node": "^24", @@ -269,18 +269,18 @@ }, "packages/navbake": { "name": "@jgengine/navbake", - "version": "0.37.0", + "version": "0.38.0", "dependencies": { - "@jgengine/core": "^0.36.0", + "@jgengine/core": "^0.38.0", "recast-navigation": "^0.34.0", }, }, "packages/node": { "name": "@jgengine/node", - "version": "0.37.0", + "version": "0.38.0", "dependencies": { - "@jgengine/core": "^0.36.0", - "@jgengine/ws": "^0.36.0", + "@jgengine/core": "^0.38.0", + "@jgengine/ws": "^0.38.0", "ws": "^8.18.0", }, "devDependencies": { @@ -293,17 +293,17 @@ }, "packages/rapier": { "name": "@jgengine/rapier", - "version": "0.37.0", + "version": "0.38.0", "dependencies": { "@dimforge/rapier3d-compat": "0.19.3", - "@jgengine/core": "^0.36.0", + "@jgengine/core": "^0.38.0", }, }, "packages/react": { "name": "@jgengine/react", - "version": "0.37.0", + "version": "0.38.0", "dependencies": { - "@jgengine/core": "^0.36.0", + "@jgengine/core": "^0.38.0", }, "devDependencies": { "@types/react": "^19", @@ -318,11 +318,11 @@ }, "packages/shell": { "name": "@jgengine/shell", - "version": "0.37.0", + "version": "0.38.0", "dependencies": { - "@jgengine/core": "^0.36.0", - "@jgengine/react": "^0.36.0", - "@jgengine/ws": "^0.36.0", + "@jgengine/core": "^0.38.0", + "@jgengine/react": "^0.38.0", + "@jgengine/ws": "^0.38.0", }, "devDependencies": { "@react-three/drei": "^10.7.7", @@ -343,9 +343,9 @@ }, "packages/sql": { "name": "@jgengine/sql", - "version": "0.37.0", + "version": "0.38.0", "dependencies": { - "@jgengine/core": "^0.36.0", + "@jgengine/core": "^0.38.0", }, "devDependencies": { "pg-mem": "^3.0.5", @@ -353,9 +353,9 @@ }, "packages/ws": { "name": "@jgengine/ws", - "version": "0.37.0", + "version": "0.38.0", "dependencies": { - "@jgengine/core": "^0.36.0", + "@jgengine/core": "^0.38.0", }, }, "registry": { diff --git a/changes/agent-lane-main-menu.md b/changes/agent-lane-main-menu.md deleted file mode 100644 index 5b1976a3a..000000000 --- a/changes/agent-lane-main-menu.md +++ /dev/null @@ -1,10 +0,0 @@ -### Added - -- `GameMenu` and `GamePauseMenu` (`@jgengine/react/gameMenu`): a full-bleed menu surface over the live scene or drifting key art, with a token-sized logo, an animated navigable verb list, a generated selection tick and a detail pane per verb. The document scroll is locked while it is open, so the surface is exactly the viewport at any size. -- Panes for the detail slot: `ContinuePane`, `NewCharacterPane`, `SaveSlotsPane` (slot cards with a `meta.thumbnail`, playtime, progress, timestamp and a two-press delete) and `CreditsPane` (`@jgengine/react/gameMenuSaves`). -- `shrinkCapture` (`@jgengine/react/saveThumbnail`) turns a `captureCanvas` frame into a card-sized thumbnail data URL. - -### Changed - -- `switchSlot(slot, { flush: false })` (`@jgengine/core/game/gameSave`) loads a slot without first writing the live state over it. Opening a save from a menu uses it so Continue cannot overwrite the save it resumes. -- The dev runner's registry and environment modules import outside Vite, so a test or tool can read them. diff --git a/changes/lane-headless-webgl.md b/changes/lane-headless-webgl.md deleted file mode 100644 index bc86d5094..000000000 --- a/changes/lane-headless-webgl.md +++ /dev/null @@ -1,21 +0,0 @@ -### Fixed - -- `shoot` fails instead of writing a PNG when the whole frame is one flat colour, and when a scene is still behind its loading cover. Both used to write a plausible screenshot that read as evidence of a rendered frame. -- The editor's play-mode pixel test waits for the warmup cover to clear before screenshotting. It shot the cover, so it read `0.9988` one-colour locally and passed in CI where the race fell the other way. -- `shoot` prints the WebGL2 renderer it draws with and fails the capture outright when the launched browser has none. - -Root cause: capture readiness was trusted to mean "the scene has rendered", but a loading cover and a canvas that never committed both photograph as a flat frame, and nothing measured the frame after the fact. Headless Chrome on this PC was never the problem: it renders with the GPU (`ANGLE (NVIDIA GeForce RTX 3070, D3D11)`) and with SwiftShader when asked. - -Before: `bun run shoot --url ` exited 0 and wrote a one-colour PNG, and `packages/editor/src/CreatorApp.test.ts` failed locally at `dominantColorShare 0.9988`. -After: the same shoot exits 1 with `the whole frame is one flat color (100.0%) - no scene reached the capture - nothing was written`, and the editor test passes in 12s with sky and ground in the PNG. - -Cost: forcing SwiftShader on the same game shot costs 2.4x the capture (16.0s vs 6.7s warm, beacon-bastion play at half size), so the launch keeps the native GPU. The two checks add about 10ms to a shot. - -Verification: -- `bun test scripts/shot-metrics.test.ts` - pass -- `bun test packages/editor/src/CreatorApp.test.ts` - pass -- `bun run shoot beacon-bastion --mode play --size half` - exit 0, scene renders -- `bun run shoot --url ` - exit 1, no PNG written -- `bun run verify:fast` - pass - -The CI reliability fix this lane also carried (a browser setup hook shorter than Playwright's launch default) is #2335, so either change can be reverted without the other. diff --git a/changes/lane-publish-wait.md b/changes/lane-publish-wait.md deleted file mode 100644 index 6b6ed1af2..000000000 --- a/changes/lane-publish-wait.md +++ /dev/null @@ -1,3 +0,0 @@ -### Fixed - -- The publish workflow's last step no longer reports a whole release as unpublished because it ran seconds after `npm publish`. `check:release-artifacts --wait ` re-checks while the only failures are absent packages (no metadata, version missing, tarball 404) and gives up when the budget runs out; a wrong version in the tarball, a missing subpath or a main entry that will not import still fails on the first check. The 0.37.0 publish run failed this step at 04:48 listing all 13 packages, then npm served every one of them. The step now passes `-- --wait 300`. diff --git a/changes/lane-run-split.md b/changes/lane-run-split.md deleted file mode 100644 index 24fffc273..000000000 --- a/changes/lane-run-split.md +++ /dev/null @@ -1,11 +0,0 @@ -### Added - -- `createSessionLoop` (`@jgengine/core/run/sessionLoop`) is one session loop for every game: a session opens, may carry a quantity, closes at its boundary, and the game's own `settle` callback decides what happens to what it carried. `carries: false` for a session with nothing at stake, and an optional `offers` table (with `present`/`choose`) for a game whose next session opens with an authored change; without a table nothing is drawn. `installSessionProbe` reports the live session on `window.__jgengineRun`. - -### Changed - -- `createRunLoop` is now a thin composition of `createSessionLoop` and is deprecated: bank-or-lose and a per-run change table are one game type's rules, so they live in the composition instead of the base. Its API, state shape and tests are unchanged. One difference: the option draw is seeded under `offer` rather than `run`, so a career that keeps its seed can see a different change on a given run than it would have on 0.37.0. Saved `changeId`s stay valid. - -### Migrate - -- Composing a session loop directly: pass your rule as `settle`, `carries: false` when nothing is carried, and your authored options as `offers`. `createRunLoop` is removed one minor release after Beacon Bastion and Drift Foundry stop using it. diff --git a/changes/lane-setup-budget.md b/changes/lane-setup-budget.md deleted file mode 100644 index 75124ebf1..000000000 --- a/changes/lane-setup-budget.md +++ /dev/null @@ -1,15 +0,0 @@ -### Fixed - -- A Playwright test file's `beforeAll` now outlives the Chrome launch it waits on, and `browser-test-budget` rejects a file that leaves the hook at or under Playwright's 30 s launch default. On `test (g)` in #2326 the hook's 30 s budget and that default expired together, so CI reported only `(unnamed) hook timed out` beside `killed 1 dangling process` and nothing named the launch. -- `packages/react/src/dialogBehavior.test.ts` gives its browser navigation its own 20 s deadline and one retry inside a 60 s hook. - -Root cause: Bun prints a `beforeAll` timeout as an unnamed hook timeout, so a setup budget equal to Playwright's own 30 s launch default reports the anonymous timer instead of the launch that stalled. - -Before: `test (g)` on #2326 reported `(fail) (unnamed) [30001.51ms] ^ a beforeEach/afterEach hook timed out` and `killed 1 dangling process`. -After: a stall now surfaces as Playwright's own `Timeout ... exceeded`, and `bun scripts/browser-test-budget.ts` names any file whose setup hook sits at or under the launch default. - -Verification: -- `bun scripts/browser-test-budget.ts` - pass, and the new rule names the unraised files on the previous tip -- `bun test scripts/browser-test-budget.test.ts` - pass -- `bun test packages/react/src/dialogBehavior.test.ts packages/react/src/scrollRail.test.ts packages/shell/src/settingsBoundary.test.ts` - pass -- `bun run verify:fast` - pass diff --git a/changes/lane-single-lod.md b/changes/lane-single-lod.md deleted file mode 100644 index 7cbf75b36..000000000 --- a/changes/lane-single-lod.md +++ /dev/null @@ -1,9 +0,0 @@ -### Fixed - -- A prop placed at runtime honours its model's `variants` levels. `StaticObjectModels` applied the level switch only inside its batched branch, so a prop placed with `scene.object.place` — Harbor Heat's palm line — drew its full asset at every camera distance: 15 palms at 9,134 triangles each, and pointing the far level at a dispatch booth changed nothing on screen. `IsolatedEntityModel` now mounts `SingleVariantModel`, which asks the same `createModelVariantSelector` and `lodBandIndex` authority the batched path uses, at the prop's own camera distance, and keeps only the active level mounted so both levels never draw at once. The switch carries the selector's hysteresis, warms every level at mount, measures only the near level, and reads the placement from the scene store through scratch vectors, so a frame allocates nothing. One palm past 60 m draws 4,110 triangles instead of 9,134 (5,024 saved, 45% of the full level). - -- `singleVariantLevel` and `singleVariantModel` (`@jgengine/shell` internal, `render/singleVariantLod`) are the isolated path's level decision and the model it draws, so a fixture pins the same switch the component runs. - -`bun test packages/shell/src/render/singleVariantLod.test.ts` — 6 pass. With the old render decision restored, the wiring test fails (`Expected: SingleVariantModel, Received: EntityModel`). -`bun --cwd=packages/shell run test` — 1,229 pass, 0 fail. -`bun --cwd=packages/shell run check-types` — clean. diff --git a/packages/assets/package.json b/packages/assets/package.json index 6f4b0ce7a..5fa8edf4f 100644 --- a/packages/assets/package.json +++ b/packages/assets/package.json @@ -1,6 +1,6 @@ { "name": "@jgengine/assets", - "version": "0.37.0", + "version": "0.38.0", "description": "Self-generating, license-verified index of thousands of CC0 3D models for JGengine. Sources fetch from providers' own CDNs at pull time; the npm tarball ships only the typed index and CLI (no GLB bytes).", "license": "Apache-2.0", "type": "module", @@ -38,7 +38,7 @@ "@gltf-transform/core": "4.2.1", "@gltf-transform/extensions": "4.2.1", "@gltf-transform/functions": "4.2.1", - "@jgengine/core": "^0.37.0", + "@jgengine/core": "^0.38.0", "fflate": "^0.8.2" }, "devDependencies": { diff --git a/packages/convex/package.json b/packages/convex/package.json index e02a4df5e..a73e397fd 100644 --- a/packages/convex/package.json +++ b/packages/convex/package.json @@ -1,6 +1,6 @@ { "name": "@jgengine/convex", - "version": "0.37.0", + "version": "0.38.0", "description": "Convex adapters for JGengine: game transport, presence transport, and backend wiring over @jgengine/core.", "license": "Apache-2.0", "type": "module", @@ -30,7 +30,7 @@ "check-types": "tsgo --noEmit -p tsconfig.json" }, "dependencies": { - "@jgengine/core": "^0.37.0" + "@jgengine/core": "^0.38.0" }, "peerDependencies": { "convex": "^1.31.2", diff --git a/packages/core/package.json b/packages/core/package.json index e86803f8d..482eea826 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@jgengine/core", - "version": "0.37.0", + "version": "0.38.0", "description": "jgengine core — pure-TypeScript game framework SDK (npm @jgengine/core). Entity stores, commands, combat, inventory, multiplayer seams. Zero dependencies. Not an ECS. Agent skills ship in the tarball. https://jgengine.com", "keywords": [ "jgengine", diff --git a/packages/core/src/meta/changelog.ts b/packages/core/src/meta/changelog.ts index de1af38dc..feb59945d 100644 --- a/packages/core/src/meta/changelog.ts +++ b/packages/core/src/meta/changelog.ts @@ -1,5 +1,5 @@ /** Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating. */ -export const VERSION = "0.37.0"; +export const VERSION = "0.38.0"; /** One release's migrate steps plus added/changed/removed notes (typed mirror of CHANGELOG.md). */ export interface ChangelogEntry { @@ -11,6 +11,41 @@ export interface ChangelogEntry { /** Per-version engine changelog keyed by semver string (e.g. `"0.10.0"`). */ export const CHANGELOG: Record = { + "0.38.0": { + migrate: [ + "Bump lockstep SDK packages to `^0.38.0`: `@jgengine/{core,rapier,react,ws,node,sql,convex,shell,editor,assets,navbake}`. CLI `jgengine` is `0.35.0`; `@jgengine/github` is `0.25.0`.", + "Composing a session loop directly: pass your rule as `settle`, `carries: false` when nothing is carried, and your authored options as `offers`. `createRunLoop` is removed one minor release after Beacon Bastion and Drift Foundry stop using it.", + ], + added: [ + "`GameMenu` and `GamePauseMenu` (`@jgengine/react/gameMenu`): a full-bleed menu surface over the live scene or drifting key art, with a token-sized logo, an animated navigable verb list, a generated selection tick and a detail pane per verb. The document scroll is locked while it is open, so the surface is exactly the viewport at any size.", + "Panes for the detail slot: `ContinuePane`, `NewCharacterPane`, `SaveSlotsPane` (slot cards with a `meta.thumbnail`, playtime, progress, timestamp and a two-press delete) and `CreditsPane` (`@jgengine/react/gameMenuSaves`).", + "`shrinkCapture` (`@jgengine/react/saveThumbnail`) turns a `captureCanvas` frame into a card-sized thumbnail data URL.", + "`createSessionLoop` (`@jgengine/core/run/sessionLoop`) is one session loop for every game: a session opens, may carry a quantity, closes at its boundary, and the game's own `settle` callback decides what happens to what it carried. `carries: false` for a session with nothing at stake, and an optional `offers` table (with `present`/`choose`) for a game whose next session opens with an authored change; without a table nothing is drawn. `installSessionProbe` reports the live session on `window.__jgengineRun`.", + ], + changed: [ + "`switchSlot(slot, { flush: false })` (`@jgengine/core/game/gameSave`) loads a slot without first writing the live state over it. Opening a save from a menu uses it so Continue cannot overwrite the save it resumes.", + "The dev runner's registry and environment modules import outside Vite, so a test or tool can read them.", + "`createRunLoop` is now a thin composition of `createSessionLoop` and is deprecated: bank-or-lose and a per-run change table are one game type's rules, so they live in the composition instead of the base. Its API, state shape and tests are unchanged. One difference: the option draw is seeded under `:offer:` rather than `:run:` (`seededRng(\\`${seed}:offer:${index}\\`)`), so a career that keeps its seed draws a different change on a given session than 0.37.0 did for the same seed. Ids, payloads and saves stay valid: a saved `changeId` still restores the same change.", + "`shoot` fails instead of writing a PNG when the whole frame is one flat colour, and when a scene is still behind its loading cover. Both used to write a plausible screenshot that read as evidence of a rendered frame.", + "The editor's play-mode pixel test waits for the warmup cover to clear before screenshotting. It shot the cover, so it read `0.9988` one-colour locally and passed in CI where the race fell the other way.", + "`shoot` prints the WebGL2 renderer it draws with and fails the capture outright when the launched browser has none.", + "`bun test scripts/shot-metrics.test.ts` - pass", + "`bun test packages/editor/src/CreatorApp.test.ts` - pass", + "`bun run shoot beacon-bastion --mode play --size half` - exit 0, scene renders", + "`bun run shoot --url ` - exit 1, no PNG written", + "`bun run verify:fast` - pass", + "The publish workflow's last step no longer reports a whole release as unpublished because it ran seconds after `npm publish`. `check:release-artifacts --wait ` re-checks while the only failures are absent packages (no metadata, version missing, tarball 404) and gives up when the budget runs out; a wrong version in the tarball, a missing subpath or a main entry that will not import still fails on the first check. The 0.37.0 publish run failed this step at 04:48 listing all 13 packages, then npm served every one of them. The step now passes `-- --wait 300`.", + "A Playwright test file's `beforeAll` now outlives the Chrome launch it waits on, and `browser-test-budget` rejects a file that leaves the hook at or under Playwright's 30 s launch default. On `test (g)` in #2326 the hook's 30 s budget and that default expired together, so CI reported only `(unnamed) hook timed out` beside `killed 1 dangling process` and nothing named the launch.", + "`packages/react/src/dialogBehavior.test.ts` gives its browser navigation its own 20 s deadline and one retry inside a 60 s hook.", + "`bun scripts/browser-test-budget.ts` - pass, and the new rule names the unraised files on the previous tip", + "`bun test scripts/browser-test-budget.test.ts` - pass", + "`bun test packages/react/src/dialogBehavior.test.ts packages/react/src/scrollRail.test.ts packages/shell/src/settingsBoundary.test.ts` - pass", + "`bun run verify:fast` - pass", + "A prop placed at runtime honours its model's `variants` levels. `StaticObjectModels` applied the level switch only inside its batched branch, so a prop placed with `scene.object.place` — Harbor Heat's palm line — drew its full asset at every camera distance: 15 palms at 9,134 triangles each, and pointing the far level at a dispatch booth changed nothing on screen. `IsolatedEntityModel` now mounts `SingleVariantModel`, which asks the same `createModelVariantSelector` and `lodBandIndex` authority the batched path uses, at the prop's own camera distance, and keeps only the active level mounted so both levels never draw at once. The switch carries the selector's hysteresis, warms every level at mount, measures only the near level, and reads the placement from the scene store through scratch vectors, so a frame allocates nothing. One palm past 60 m draws 4,110 triangles instead of 9,134 (5,024 saved, 45% of the full level).", + "`singleVariantLevel` and `singleVariantModel` (`@jgengine/shell` internal, `render/singleVariantLod`) are the isolated path's level decision and the model it draws, so a fixture pins the same switch the component runs.", + ], + removed: [], + }, "0.37.0": { migrate: [ "Bump lockstep SDK packages to `^0.37.0`: `@jgengine/{core,rapier,react,ws,node,sql,convex,shell,editor,assets,navbake}`. CLI `jgengine` is `0.34.0`; `@jgengine/github` is `0.24.0`.", diff --git a/packages/editor/package.json b/packages/editor/package.json index b398147cc..4cf6fec77 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -1,6 +1,6 @@ { "name": "@jgengine/editor", - "version": "0.37.0", + "version": "0.38.0", "description": "Scene/world/asset editor for JGengine — loaded lazily by the runner; never import from game entrypoints.", "license": "Apache-2.0", "type": "module", @@ -31,11 +31,11 @@ "test": "bun test src" }, "dependencies": { - "@jgengine/assets": "^0.37.0", - "@jgengine/core": "^0.37.0", - "@jgengine/navbake": "^0.37.0", - "@jgengine/react": "^0.37.0", - "@jgengine/shell": "^0.37.0" + "@jgengine/assets": "^0.38.0", + "@jgengine/core": "^0.38.0", + "@jgengine/navbake": "^0.38.0", + "@jgengine/react": "^0.38.0", + "@jgengine/shell": "^0.38.0" }, "peerDependencies": { "@react-three/drei": "^10.0.0", diff --git a/packages/github/package.json b/packages/github/package.json index a9ea6858c..ebbd75fe7 100644 --- a/packages/github/package.json +++ b/packages/github/package.json @@ -1,6 +1,6 @@ { "name": "@jgengine/github", - "version": "0.24.0", + "version": "0.25.0", "description": "GitHub data source for JGengine: contribution calendar fetch (GraphQL + HTML-scrape) with a server proxy handler, plus a browser client and contribution analytics.", "license": "Apache-2.0", "type": "module", diff --git a/packages/jgengine/package.json b/packages/jgengine/package.json index 5de72d761..92f9a90bb 100644 --- a/packages/jgengine/package.json +++ b/packages/jgengine/package.json @@ -1,6 +1,6 @@ { "name": "jgengine", - "version": "0.34.0", + "version": "0.35.0", "description": "jgengine — game framework SDK CLI (npm package jgengine / @jgengine/*). Not automotive. Humans: tell a coding agent Make a game that ... with jgengine. Agents: create, skills, doctor, desktop. Skills ship in the package under skills/. https://jgengine.com", "keywords": [ "jgengine", @@ -49,7 +49,7 @@ "test": "bun test src" }, "dependencies": { - "@jgengine/assets": "^0.37.0" + "@jgengine/assets": "^0.38.0" }, "devDependencies": { "@types/node": "^24" diff --git a/packages/navbake/package.json b/packages/navbake/package.json index ea9d31d8d..af2ecf275 100644 --- a/packages/navbake/package.json +++ b/packages/navbake/package.json @@ -1,6 +1,6 @@ { "name": "@jgengine/navbake", - "version": "0.37.0", + "version": "0.38.0", "description": "Deterministic navigation-mesh baking for JGengine scenes.", "license": "Apache-2.0", "type": "module", @@ -32,7 +32,7 @@ "test": "bun test src" }, "dependencies": { - "@jgengine/core": "^0.37.0", + "@jgengine/core": "^0.38.0", "recast-navigation": "^0.34.0" }, "publishConfig": { diff --git a/packages/node/package.json b/packages/node/package.json index 74e96f4db..8eda2c3b1 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -1,6 +1,6 @@ { "name": "@jgengine/node", - "version": "0.37.0", + "version": "0.38.0", "description": "Standalone authoritative game host for JGengine: in-memory server snapshots, tick loop, save-cadence flush, WebSocket server, memory/file persistence.", "license": "Apache-2.0", "type": "module", @@ -31,8 +31,8 @@ "test": "bun test src" }, "dependencies": { - "@jgengine/core": "^0.37.0", - "@jgengine/ws": "^0.37.0", + "@jgengine/core": "^0.38.0", + "@jgengine/ws": "^0.38.0", "ws": "^8.18.0" }, "devDependencies": { diff --git a/packages/rapier/package.json b/packages/rapier/package.json index 1ae0fc2c9..a55901c03 100644 --- a/packages/rapier/package.json +++ b/packages/rapier/package.json @@ -1,6 +1,6 @@ { "name": "@jgengine/rapier", - "version": "0.37.0", + "version": "0.38.0", "description": "Rapier physics backend for JGengine", "license": "Apache-2.0", "type": "module", @@ -30,7 +30,7 @@ "test": "bun test src" }, "dependencies": { - "@jgengine/core": "^0.37.0", + "@jgengine/core": "^0.38.0", "@dimforge/rapier3d-compat": "0.19.3" }, "publishConfig": { diff --git a/packages/react/package.json b/packages/react/package.json index 324802262..0e59be204 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@jgengine/react", - "version": "0.37.0", + "version": "0.38.0", "description": "React UI layer for JGengine: GameProvider, hooks, and headless primitives over @jgengine/core.", "license": "Apache-2.0", "type": "module", @@ -35,7 +35,7 @@ "styles": "bun scripts/styles.ts" }, "dependencies": { - "@jgengine/core": "^0.37.0" + "@jgengine/core": "^0.38.0" }, "peerDependencies": { "react": "^19.0.0", diff --git a/packages/shell/package.json b/packages/shell/package.json index 6838e0960..098511df7 100644 --- a/packages/shell/package.json +++ b/packages/shell/package.json @@ -1,6 +1,6 @@ { "name": "@jgengine/shell", - "version": "0.37.0", + "version": "0.38.0", "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.", "license": "Apache-2.0", "type": "module", @@ -39,9 +39,9 @@ "test": "bun test src" }, "dependencies": { - "@jgengine/core": "^0.37.0", - "@jgengine/react": "^0.37.0", - "@jgengine/ws": "^0.37.0" + "@jgengine/core": "^0.38.0", + "@jgengine/react": "^0.38.0", + "@jgengine/ws": "^0.38.0" }, "peerDependencies": { "@react-three/drei": "^10.0.0", diff --git a/packages/sql/package.json b/packages/sql/package.json index 41df77842..def0b4f78 100644 --- a/packages/sql/package.json +++ b/packages/sql/package.json @@ -1,6 +1,6 @@ { "name": "@jgengine/sql", - "version": "0.37.0", + "version": "0.38.0", "description": "Postgres persistence for the JGengine host: implements HostPersistence over a structural pool interface (no hard pg dependency).", "license": "Apache-2.0", "type": "module", @@ -31,7 +31,7 @@ "test": "bun test src" }, "dependencies": { - "@jgengine/core": "^0.37.0" + "@jgengine/core": "^0.38.0" }, "devDependencies": { "pg-mem": "^3.0.5" diff --git a/packages/ws/package.json b/packages/ws/package.json index c61887223..aaefef3f6 100644 --- a/packages/ws/package.json +++ b/packages/ws/package.json @@ -1,6 +1,6 @@ { "name": "@jgengine/ws", - "version": "0.37.0", + "version": "0.38.0", "description": "Browser-safe WebSocket client backend for JGengine: protocol codec, createWsBackend, and HTTP read helpers.", "license": "Apache-2.0", "type": "module", @@ -31,7 +31,7 @@ "test": "bun test src" }, "dependencies": { - "@jgengine/core": "^0.37.0" + "@jgengine/core": "^0.38.0" }, "publishConfig": { "access": "public"