From 2903a88160f207d6e7df726dc91af73f2513259e Mon Sep 17 00:00:00 2001 From: Exoridus Date: Tue, 23 Jun 2026 08:18:21 +0200 Subject: [PATCH] chore(release): prepare v0.14.0 - bump all six lockstep packages 0.13.0 -> 0.14.0 (+ peer ranges to 0.14.x) - curate CHANGELOG [0.14.0] from the 42 commits since v0.13.0 - verify-lockstep now covers @codexo/exojs-audio-fx (was outside the 5-package check) - move live version-coherence asserts to changelog-v0.14.test.ts; v0.13 test keeps its historical invariants - RELEASING.md updated to the six-package lockstep set --- CHANGELOG.md | 84 ++++++++++++++++++++++++++ package.json | 2 +- packages/exojs-audio-fx/package.json | 4 +- packages/exojs-particles/package.json | 4 +- packages/exojs-physics/package.json | 4 +- packages/exojs-tiled/package.json | 4 +- packages/exojs-tilemap/package.json | 4 +- scripts/release/RELEASING.md | 18 +++--- scripts/verify-lockstep-versions.ts | 11 ++-- test/release/changelog-v0.13.test.ts | 35 +---------- test/release/changelog-v0.14.test.ts | 87 +++++++++++++++++++++++++++ 11 files changed, 202 insertions(+), 55 deletions(-) create mode 100644 test/release/changelog-v0.14.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 550ae039..3d7391b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,90 @@ merged pull requests and commits since the previous tag (each with its commit / PR link); `pnpm release:notes` then renders that section into the published GitHub release with a `PREVIOUS_TAG...CURRENT_TAG` compare link. +## [0.14.0] - 2026-06-23 + +The architecture release. Two new packages — `@codexo/exojs-physics` (a native +2D collision, query, and dynamics world) and `@codexo/exojs-audio-fx` (the audio +effect suite, extracted from core) — join the lockstep set. Core gains a UI +layer, scene-graph serialization with prefabs and save files, immediate-mode +rendering, an ordered System scheduler, and a multi-instance-safe foundation +(deterministic disposal, app-owned managers). The Scene model is simplified to a +single active scene. This is a pre-1.0 release and includes intentional breaking +changes; see **Changed** and **Removed**. + +### Added + +- **`@codexo/exojs-physics` — native 2D physics.** Shapes, colliders, and bodies + with an SAP broadphase, manifold narrow-phase, and a warm-started + sequential-impulse solver (2×2 block normal LCP + NGS position correction, + pre-gravity restitution). Contact graph and events, spatial queries, + scene-graph binding, and a `/debug` draw subpath. Allocation-free per step; + the dynamics surface (`velocity`, `applyForce`/`Torque`/`Impulse`) is public + (#131, #140, #141, #142, #143, #155, #156). +- **`@codexo/exojs-audio-fx` — audio effect package.** Extracted from core: the + `*Effect` suite, `AudioAnalyser`, `BeatDetector`, worklets, and DSP helpers. + Core keeps the audio engine and effect base classes (#133). +- **UI core.** `scene.ui` with a `Widget`/`Label`/`Panel`/`Button`/`ProgressBar` + set, row/column/stack layout and anchoring, a `FocusManager` with keyboard + navigation, and `app.focus` (#138). +- **Scene-graph serialization.** `SerializationRegistry`, `NodeSerializer`, + `Prefab`, and `SaveManager` with `Scene.serialize`/`deserialize`; serializers + for containers, sprites, text, meshes, graphics, nine-slice/repeating sprites, + animated sprites, bitmap text, video, and UI widgets. Tilemap nodes serialize + through an extension binding (#144, #145, #146, #147, #148). +- **Immediate-mode rendering.** `RenderingContext.drawGeometry` for one-off + geometry and `RenderBatch` + `drawBatch` for instanced draws collapsing to a + single draw call (#150, #151, #159). +- **System scheduler.** `app.systems` and `scene.systems` run the core managers + as ordered systems; `update` signatures converge on `(delta: Time)` (#134). +- **Design-space coordinates.** Automatic DPR handling, letterbox sizing, and + `canvas`-mount / `sizingMode` options on `Application` (#130). +- **Typed tilemap object layers.** Object layers and queries converted from + Tiled object groups, plus an `ObjectKind` `as const` schema and a generic + `ObjectLayer` with `byType`/`byKind`/`where` (#132, #157). +- **Combined Tiled + physics examples** with an `ObjectLayer`→collider bridge + (#160), a rebuilt example catalog on a shared runtime helper kit, and a live + hero example with an expandable playground preview. + +### Changed + +- **Audio re-architecture.** `Sound`/`AudioStream`/`AudioGenerator` descriptors + with a voice capability matrix; the audio singleton is gone and `AudioFilter` + becomes `AudioEffect`. Playback defers until the autoplay gesture unlocks + audio (#133). +- **Multi-instance foundation.** `Destroyable`/`DisposalScope` for deterministic + teardown; `Interaction`, `Audio`, `Random`, and the serializer registry are + app-owned rather than process singletons; `ObservableVector` sheds per-node + closures (#133, #134, #154). +- **BREAKING — API hygiene.** Value-type footgun fixes (`Matrix.getInverse`, + `Color.toRgba`, honest `Rectangle` types), curated barrels, and namespaced + utilities (`MathUtils`, `MeshBuilder`, `Sweep`, `Collision`, …) (#135). +- **BREAKING — `Random` engine.** Mersenne Twister replaced with xoshiro128\*\* + and SplitMix32 seeding; the `iteration` getter is removed (#137). +- **BREAKING — physics body construction.** `new PhysicsBody({ colliders })` + + `world.add`/`world.attach` replace `createBody`/`createStaticCollider` (#156). +- **BREAKING — rendering barrel.** Backend renderer internals move behind the + `@codexo/exojs/renderer-sdk` subpath; the root barrel is curated (#153). +- **Site islands migrated from Lit to React** (#149); a shared `Registry` + primitive backs constructor-keyed dispatch (#136). + +### Removed + +- **BREAKING — scene stack.** `SceneStackMode`, `SceneParticipation`, + `pushScene`, and `popScene` are removed in favor of one active scene with + `setScene`, fade transitions, and `scene.paused` (#139). + +### Fixed + +- Physics contact pair keys no longer collide past 65,536 body IDs (#155). +- New and mutated textures upload correctly after their first bind, and pointer + coordinates map to backing-store pixels when the canvas is scaled (#130). + +### Docs + +- ADR on shared geometry with separate collision detection (#158) and an + immediate-mode rendering guide (#159). + ## [0.13.0] - 2026-06-13 The scalable-sprites and tilemap release. `TextureRegion`, `NineSliceSprite`, diff --git a/package.json b/package.json index 3d89933c..e5152fce 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@codexo/exojs", "description": "A TypeScript-first browser 2D runtime for games and interactive apps.", - "version": "0.13.0", + "version": "0.14.0", "type": "module", "packageManager": "pnpm@11.4.0", "files": [ diff --git a/packages/exojs-audio-fx/package.json b/packages/exojs-audio-fx/package.json index a93bf8e5..14401e95 100644 --- a/packages/exojs-audio-fx/package.json +++ b/packages/exojs-audio-fx/package.json @@ -1,6 +1,6 @@ { "name": "@codexo/exojs-audio-fx", - "version": "0.13.0", + "version": "0.14.0", "description": "Audio effects, DSP, beat detection and analysis for ExoJS.", "repository": { "type": "git", @@ -33,7 +33,7 @@ "test": "vitest run --root ../.. --project=exojs-audio-fx" }, "peerDependencies": { - "@codexo/exojs": "0.13.x" + "@codexo/exojs": "0.14.x" }, "devDependencies": { "@codexo/exojs": "workspace:*", diff --git a/packages/exojs-particles/package.json b/packages/exojs-particles/package.json index 638acc3c..4a658064 100644 --- a/packages/exojs-particles/package.json +++ b/packages/exojs-particles/package.json @@ -1,6 +1,6 @@ { "name": "@codexo/exojs-particles", - "version": "0.13.0", + "version": "0.14.0", "description": "Particle system extension for ExoJS.", "repository": { "type": "git", @@ -47,7 +47,7 @@ "test": "vitest run --root ../.. --project=exojs-particles" }, "peerDependencies": { - "@codexo/exojs": "0.13.x" + "@codexo/exojs": "0.14.x" }, "devDependencies": { "@codexo/exojs": "workspace:*", diff --git a/packages/exojs-physics/package.json b/packages/exojs-physics/package.json index ae20a99a..65acf2b1 100644 --- a/packages/exojs-physics/package.json +++ b/packages/exojs-physics/package.json @@ -1,6 +1,6 @@ { "name": "@codexo/exojs-physics", - "version": "0.13.0", + "version": "0.14.0", "description": "Native 2D collision, query and sensor runtime for ExoJS.", "repository": { "type": "git", @@ -38,7 +38,7 @@ "test": "vitest run --root ../.. --project=exojs-physics" }, "peerDependencies": { - "@codexo/exojs": "0.13.x" + "@codexo/exojs": "0.14.x" }, "devDependencies": { "@codexo/exojs": "workspace:*", diff --git a/packages/exojs-tiled/package.json b/packages/exojs-tiled/package.json index c7ce8c51..f1a2b81b 100644 --- a/packages/exojs-tiled/package.json +++ b/packages/exojs-tiled/package.json @@ -1,6 +1,6 @@ { "name": "@codexo/exojs-tiled", - "version": "0.13.0", + "version": "0.14.0", "description": "Tiled map format asset extension for ExoJS.", "repository": { "type": "git", @@ -40,7 +40,7 @@ "test": "vitest run --root ../.. --project=exojs-tiled" }, "peerDependencies": { - "@codexo/exojs": "0.13.x" + "@codexo/exojs": "0.14.x" }, "dependencies": { "@codexo/exojs-tilemap": "workspace:*" diff --git a/packages/exojs-tilemap/package.json b/packages/exojs-tilemap/package.json index 0aea9cbe..c57ed53b 100644 --- a/packages/exojs-tilemap/package.json +++ b/packages/exojs-tilemap/package.json @@ -1,6 +1,6 @@ { "name": "@codexo/exojs-tilemap", - "version": "0.13.0", + "version": "0.14.0", "description": "Generic, format-independent tilemap runtime for ExoJS.", "repository": { "type": "git", @@ -40,7 +40,7 @@ "test": "vitest run --root ../.. --project=exojs-tilemap" }, "peerDependencies": { - "@codexo/exojs": "0.13.x" + "@codexo/exojs": "0.14.x" }, "devDependencies": { "@codexo/exojs": "workspace:*", diff --git a/scripts/release/RELEASING.md b/scripts/release/RELEASING.md index cb02e906..4af34944 100644 --- a/scripts/release/RELEASING.md +++ b/scripts/release/RELEASING.md @@ -1,19 +1,21 @@ # Releasing ExoJS -The coordinated release publishes the four lockstep packages — `@codexo/exojs`, -`@codexo/exojs-particles`, `@codexo/exojs-tilemap`, `@codexo/exojs-tiled` — at one -shared version via the two-stage, build-once pipeline (`scripts/release/`). +The coordinated release publishes the six lockstep packages — `@codexo/exojs`, +`@codexo/exojs-particles`, `@codexo/exojs-tilemap`, `@codexo/exojs-tiled`, +`@codexo/exojs-physics`, `@codexo/exojs-audio-fx` — at one shared version via the +two-stage, build-once pipeline (`scripts/release/`). ## Normal release 1. Land everything on `main`. Curate the `## [Unreleased]` CHANGELOG section into `## [x.y.z] - YYYY-MM-DD` (a concrete date — `release:notes` rejects "Unreleased"). -2. Bump all four package versions in lockstep (and the peer ranges to `x.y.x`). +2. Bump all six package versions in lockstep (and the peer ranges to `x.y.x`). 3. Tag and push: `git tag -a vX.Y.Z -m "ExoJS vX.Y.Z" && git push origin refs/tags/vX.Y.Z`. 4. The `Release` workflow checks out the **tag**, runs the full CI gate, builds - once, packs/hashes/attw/consumer-tests the four tarballs, publishes them via - OIDC (Core → Particles → Tilemap → Tiled) to a staging dist-tag, promotes all - four to `latest`, and creates the GitHub release with the Full ZIP. + once, packs/hashes/attw/consumer-tests the six tarballs, publishes them via + OIDC (Core → Particles → Tilemap → Tiled → Physics → Audio-FX) to a staging + dist-tag, promotes all six to `latest`, and creates the GitHub release with + the Full ZIP. The workflow checks out the **tag commit**, so any fix to the release _scripts_ must be on the tag — re-point the tag (`git tag -d` + `git tag -a` + force-push) @@ -55,6 +57,8 @@ npm dist-tag add @codexo/exojs@X.Y.Z latest npm dist-tag add @codexo/exojs-particles@X.Y.Z latest npm dist-tag add @codexo/exojs-tilemap@X.Y.Z latest npm dist-tag add @codexo/exojs-tiled@X.Y.Z latest +npm dist-tag add @codexo/exojs-physics@X.Y.Z latest +npm dist-tag add @codexo/exojs-audio-fx@X.Y.Z latest ``` The packages are already published and immutable at this point — this only moves diff --git a/scripts/verify-lockstep-versions.ts b/scripts/verify-lockstep-versions.ts index 8f03b472..46c23b9e 100644 --- a/scripts/verify-lockstep-versions.ts +++ b/scripts/verify-lockstep-versions.ts @@ -1,9 +1,9 @@ /** - * Verifies that all five official ExoJS packages share the same version. + * Verifies that all six official ExoJS packages share the same version. * * The lockstep version contract: @codexo/exojs, @codexo/exojs-particles, - * @codexo/exojs-tilemap, @codexo/exojs-tiled, and @codexo/exojs-physics must - * all be on the same X.Y.Z version for every coordinated release. + * @codexo/exojs-tilemap, @codexo/exojs-tiled, @codexo/exojs-physics, and + * @codexo/exojs-audio-fx must all be on the same X.Y.Z version per release. */ import { readFileSync } from 'node:fs'; import { dirname, resolve } from 'node:path'; @@ -32,6 +32,7 @@ const extensionPkgs = [ readPackage('packages/exojs-tilemap/package.json'), readPackage('packages/exojs-tiled/package.json'), readPackage('packages/exojs-physics/package.json'), + readPackage('packages/exojs-audio-fx/package.json'), ]; const packages = [corePkg, ...extensionPkgs]; @@ -42,7 +43,7 @@ if (versions.length !== 1) { for (const p of packages) { process.stderr.write(` ${p.name}: ${p.version}\n`); } - process.stderr.write('\nAll five packages must be on the same version before release.\n' + 'Update all five package.json files to the same version.\n'); + process.stderr.write('\nAll six packages must be on the same version before release.\n' + 'Update all six package.json files to the same version.\n'); process.exit(1); } @@ -67,4 +68,4 @@ if (peerProblems.length > 0) { process.exit(1); } -process.stdout.write(`verify-lockstep: all 5 packages at v${versions[0]}; extension peer ranges = "${expectedPeer}" ✓\n`); +process.stdout.write(`verify-lockstep: all ${packages.length} packages at v${versions[0]}; extension peer ranges = "${expectedPeer}" ✓\n`); diff --git a/test/release/changelog-v0.13.test.ts b/test/release/changelog-v0.13.test.ts index 1583a896..5a8b213f 100644 --- a/test/release/changelog-v0.13.test.ts +++ b/test/release/changelog-v0.13.test.ts @@ -22,10 +22,9 @@ const extractV013Section = (): string => { return content.slice(afterStart + 1, nextSection === -1 ? undefined : nextSection); }; -/** Load package.json manifests for the 4 lockstep packages. */ -const loadPackageJson = (name: string): Record => - JSON.parse(readFileSync(resolve(repoRoot, 'packages', name.replace('@codexo/', ''), 'package.json'), 'utf8')); - +// Historical invariants for the shipped 0.13.0 section. Live package-version +// coherence is asserted by the CURRENT release's test (changelog-v0.14.test.ts), +// not here — that check moves forward with each release. describe('v0.13 release text invariants', () => { const section = extractV013Section(); @@ -83,31 +82,3 @@ describe('v0.13 release text invariants', () => { expect(section).toMatch(/structured/); }); }); - -describe('package manifest / changelog version coherence', () => { - const packages = ['@codexo/exojs-particles', '@codexo/exojs-tilemap', '@codexo/exojs-tiled']; - - for (const pkg of packages) { - it(`${pkg} manifest version is 0.13.0`, () => { - const manifest = loadPackageJson(pkg); - expect(manifest.version).toBe('0.13.0'); - }); - - it(`${pkg} peer dependency range is 0.13.x`, () => { - const manifest = loadPackageJson(pkg); - const peers = (manifest.peerDependencies ?? {}) as Record; - expect(peers['@codexo/exojs']).toBe('0.13.x'); - }); - } - - it('@codexo/exojs-tiled has @codexo/exojs-tilemap as a regular dependency', () => { - const manifest = loadPackageJson('@codexo/exojs-tiled'); - const deps = (manifest.dependencies ?? {}) as Record; - expect(deps['@codexo/exojs-tilemap']).toBeDefined(); - }); - - it('root package version is 0.13.0', () => { - const root = JSON.parse(readFileSync(resolve(repoRoot, 'package.json'), 'utf8')); - expect(root.version).toBe('0.13.0'); - }); -}); diff --git a/test/release/changelog-v0.14.test.ts b/test/release/changelog-v0.14.test.ts new file mode 100644 index 00000000..1fcb4e0d --- /dev/null +++ b/test/release/changelog-v0.14.test.ts @@ -0,0 +1,87 @@ +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { describe, expect, it } from 'vitest'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = resolve(__filename, '..'); +const repoRoot = resolve(__dirname, '..', '..'); +const changelogPath = resolve(repoRoot, 'CHANGELOG.md'); + +const readChangelog = (): string => readFileSync(changelogPath, 'utf8'); + +/** Extract the 0.14.0 section text (from its heading to the next `## [` heading). */ +const extractV014Section = (): string => { + const content = readChangelog(); + const startMarker = '## [0.14.0]'; + const startIndex = content.indexOf(startMarker); + if (startIndex === -1) throw new Error('0.14.0 section not found in CHANGELOG.md'); + const afterStart = content.indexOf('\n', startIndex); + const nextSection = content.indexOf('\n## [', afterStart + 1); + return content.slice(afterStart + 1, nextSection === -1 ? undefined : nextSection); +}; + +/** Load a lockstep extension package.json by its @codexo/ name. */ +const loadPackageJson = (name: string): Record => + JSON.parse(readFileSync(resolve(repoRoot, 'packages', name.replace('@codexo/', ''), 'package.json'), 'utf8')); + +// Matchers use single tokens (not multi-word phrases) because Prettier +// prose-wraps the changelog, so an inter-word match could span a newline. +describe('v0.14 release text invariants', () => { + const section = extractV014Section(); + + it('carries a concrete release date in the heading', () => { + // `release:notes` (publish job, AFTER npm publish) hard-fails on any + // heading that is not `## [0.14.0] - YYYY-MM-DD` — "Unreleased" would + // publish npm packages and then abort the GitHub release. + expect(readChangelog()).toMatch(/^## \[0\.14\.0\] - \d{4}-\d{2}-\d{2}$/m); + }); + + it('contains no #this placeholder', () => { + expect(section).not.toMatch(/#this/); + }); + + it('introduces the two new lockstep packages', () => { + expect(section).toMatch(/@codexo\/exojs-physics/); + expect(section).toMatch(/@codexo\/exojs-audio-fx/); + }); + + it('documents the breaking scene-stack removal', () => { + expect(section).toMatch(/SceneStackMode/); + }); + + it('documents the major additive features', () => { + expect(section).toMatch(/\bUI\b/); + expect(section).toMatch(/serializ/i); + expect(section).toMatch(/immediate-mode/i); + }); +}); + +describe('package manifest / changelog version coherence', () => { + const packages = ['@codexo/exojs-particles', '@codexo/exojs-tilemap', '@codexo/exojs-tiled', '@codexo/exojs-physics', '@codexo/exojs-audio-fx']; + + for (const pkg of packages) { + it(`${pkg} manifest version is 0.14.0`, () => { + const manifest = loadPackageJson(pkg); + expect(manifest.version).toBe('0.14.0'); + }); + + it(`${pkg} peer dependency range is 0.14.x`, () => { + const manifest = loadPackageJson(pkg); + const peers = (manifest.peerDependencies ?? {}) as Record; + expect(peers['@codexo/exojs']).toBe('0.14.x'); + }); + } + + it('@codexo/exojs-tiled has @codexo/exojs-tilemap as a regular dependency', () => { + const manifest = loadPackageJson('@codexo/exojs-tiled'); + const deps = (manifest.dependencies ?? {}) as Record; + expect(deps['@codexo/exojs-tilemap']).toBeDefined(); + }); + + it('root package version is 0.14.0', () => { + const root = JSON.parse(readFileSync(resolve(repoRoot, 'package.json'), 'utf8')); + expect(root.version).toBe('0.14.0'); + }); +});