Skip to content

v0.20: runtime subpaths, ESM-only distribution, demo-asset catalog and two-channel Pages - #861

Merged
Exoridus merged 45 commits into
nextfrom
arch/v020-migration
Oct 22, 2026
Merged

Exoridus merged 45 commits into
nextfrom
arch/v020-migration

Conversation

@Exoridus

@Exoridus Exoridus commented Oct 21, 2026 •

Copy link
Copy Markdown
Owner

ExoJS v0.20: hybrid runtime subpaths, ESM-only distribution, demo-asset catalog in the playground, two-channel Pages

Integration PR for the v0.20 migration on top of cea66372352fb54238211295b22518fce49f531b (after #859 and #860). It carries the runtime/ESM, demo-asset and Pages workstreams and, since the last review of cddba1d7d, the two CI repairs, the complete adoption of the demo catalog by the examples, playground and site, and an interaction and quality pass over every catalog example. Nothing is merged, published, deprecated, released or deployed by this PR.

1. Package and export structure

  • @codexo/exojs ships the runtime modules as Core subpaths from one tarball: @codexo/exojs/tilemap, /particles, /audio-fx, /physics, /tilemap-physics, /lighting, /pathfinding. Their sources stay private workspaces (@codexo/exojs-module-*); one tsc program and an AST specifier rewriter produce NodeNext-clean declarations; one Core instance per app.
  • @codexo/exojs/debug is the single debug entry and also exports PhysicsDebugDraw / PhysicsDebugDrawOptions.
  • ESM-only: the four IIFE bundles and their build/release paths are gone (breaking; see the migration guide). Core tarball 21,804,125 B before ESM-only, 8,872,757 B now (3,359 files, 30,785,583 B unpacked).
  • New optional content package @codexo/exojs-demo-assets 0.1.0 (Core peer 0.20.x, independent version line): frozen flat demoAssets descriptors, demoAssetCategories, createDemoAssets(baseUrl), createExtendedDemoAssets(baseUrl) from /extended, manifest.json, exojs-demo-assets-copy bin.

2. Packages that stay separate, and the legacy shims

  • Separate as before: @codexo/exojs-tiled, -ldtk, -aseprite, -react, create-exo-app, @codexo/exojs-build, @codexo/exojs-cli, @codexo/eslint-plugin-exojs. KTX2/Basis worker and WASM stay in Core; Assets, Sprite and the loader contracts are unchanged.
  • The seven retired names (@codexo/exojs-tilemap, -particles, -audio-fx, -physics incl. /debug, -tilemap-physics, -lighting, -pathfinding) are forwarder-only shims under packages/legacy/ peering on Core 0.20.x. Deprecation text and a registry dry run are prepared; no registry action ran.

3. CI repairs since cddba1d7d

  • verify (package) / publint ./media/*: the demo-assets build generates media/, but the dist artifact handed to the verify jobs carried only dist/ and packages/*/dist/. The artifact now also carries packages/exojs-demo-assets/media/; the export and publint stay as they were (09789eac1).
  • skip-budget (18 unbudgeted skips): the dist-dependent tests (11 hybrid exports, 3 identity, 4 shim consumers; the last moved to legacy-shims-tarball.test.ts) form the Vitest project release-dist, excluded from the unit lane and run in the build job with EXOJS_REQUIRE_PRODUCTION_BUILD=1 (a missing dist fails) and a junit-build report that the skip budget reads. No budget was added (77899e8d9). CI run 39664733145 on 77899e8d9: all jobs green, verify (package) and skip-budget included.

4. Demo assets in examples, playground and site

  • Every runnable example imports import { demoAssets as assets } from '@codexo/exojs-demo-assets' and loads through this.loader.get(assets.x) / await this.loader.load(assets.x); formats whose adapters take a URL (tileMap, ldtkMap, textures with sampler options) use the descriptor's public .source. No loader.basePath: 'assets/' and no global assets remain. The old hierarchical catalog (examples/assets/{assets,catalog,resolver,index}, assets.json), assets-global.d.ts, its generator and drift test, the preview prelude, the shell injection and the string-path completion provider are removed. Guide snippets under examples/guides/ keep user-project paths on purpose (they teach a project's own public/assets).
  • The site serves the package like a consumer installs it: vendor/exojs-demo-assets/esm/ (catalog + declarations) and vendor/exojs-demo-assets/media/ (standard profile). The preview import map maps @codexo/exojs-demo-assets and /extended; Monaco loads its declarations through the existing extension-package path.
  • Monaco acceptance in a real browser (built Stable site, sprite-basics): the TypeScript worker completes assets. with 139 keys, the import specifier suggests demoAssets, no diagnostics on the import; a separate JavaScript model completes assets. too, videoHighRes resolves to LeaflessAsset<Video> with its JSDoc; the visible suggest widget lists the catalog keys.
  • Asset Browser rebuilt on demoAssets + demoAssetCategories and returned to the playground catalog; see section 7.
  • Catalog fixes: ship provenance is ExoJS original work (MIT), as feat(examples): playground quality and experience sweep #860 recorded when it replaced the 505 B chevron; the Kenney Simple Space attribution was a leftover of that chevron and no Simple Space pack exists in the vendor tree. Please confirm. soundImpactMetalHeavy added because convolution-rooms plays it (161 standard files now).
  • README: a complete Scene load()/init() example replaces the never-called Application.prepare(), and README listings are now type-checked; a "Bundlers" section states what a bundler does with the catalog (below).
  • Guides: loading-and-resources gets "Demo media for prototypes"; migration-v020 no longer claims the examples are unchanged.

5. Asset delivery and sizes

Measure Before After
Standard demo media (package media/) 160 files, 18,866,140 B 161 files, 18,872,250 B
Extended media (HDR10, separate archive, not on the site) 15,436,418 B 15,436,418 B
demo-assets tarball - 18,156,697 B packed, 19,115,805 B unpacked, 191 files
Core tarball 8,872,771 B 8,872,757 B
Site assets/ tree (old full copy of examples/assets) 143,139,493 B removed
Site vendor/exojs-demo-assets/ - about 19 MB (standard profile only)
Stable site, unpacked 403,201,374 B 309,381,117 B
Next site, unpacked 407,938,703 B (CI artifact of cddba1d7d: 441,109,574 B) 314,126,043 B
Composed Pages tree (Stable + Next) 816,307,438 B (warning band) about 623,507,160 B (below the 750 MB warning)
Full release ZIP 324,383,184 B (published v0.19.1 ZIP) 100,642,268 B (353,724,687 B unpacked)
  • Repository originals stay (examples/assets/ is the authoring source of the package; the long source videos and the Kenney vendor packs remain for provenance and generators); only the delivery changed. The site's examples:sync no longer copies examples/assets, and the full release ZIP no longer packs it.
  • Per-example browser downloads of demo media (built Stable site, Playwright): hello-world 3,601 B (1 file), video-drawable 13,029 B (2), tiled-map-import 207,691 B (2), convolution-rooms 34,191 B (5), bitmap-text-basic 13,635 B (2), many-lights 0 B. Only claimed descriptors are fetched.
  • Bundler behaviour, measured with Vite 8.3 on packed tarballs: vite build of a one-sprite consumer emits the whole standard set (21,082,124 B; 96 files plus small ones inlined), because all descriptors live in one object. Tiled maps, LDtk projects and bitmap fonts keep relative sibling paths that a hashing bundler breaks; the README routes those through the copy command and createDemoAssets. The Vite dev server serves the media from the package correctly. This is documented, not changed: a per-asset module layout would be a new API decision.
  • Video: the standard profile carries the short 4K SDR (videoHighRes) and 60 fps SDR (videoHighFps) clips plus the demo loop; HDR10 stays extended-only and the video example remains honestly SDR.

6. Example interaction and quality pass

Every one of the 110 catalog entries got a profile and a decision: 86 changed, 24 kept on purpose. A small typed helper demoPanel() in @examples/runtime (choice / slider / toggle / actions / status, one value per control) replaces hand-written control plumbing; keyboard shortcuts call panel.set() / panel.step(), so keys and controls share one state. Example sources shrank by about 1,070 lines net. Permanent key legends moved into the catalog descriptions (88 updated).

Highlights:

  • Comparisons are simultaneous: pivot-and-anchor (three origins side by side), color-matrix-filter (original, grayscale, sepia, 1D curves, 3D LUT on a ramp and painted art), lightmap-normals (left half with normals, right without), frame-animation (five characters at once).
  • Minimal where the lesson is minimal: sprite-basics and emitter-basics lost their overlays; label-only "legends" (retained-container, immediate-mode-rendering) are gone.
  • Audio: looping music at 0.5 to 0.6 (was up to 0.85), the compressor's loud input ramps to 0.6 instead of jumping to 1.0, pooled impacts at 0.3, every audio example shows a locked-state prompt and a clear paused state; convolution-rooms drops undiscoverable right-click room cycling (main-button filtering kept).
  • Asset loading, catalog, lighting, particle, tilemap and input examples follow the same rules; the firework sparks were enlarged after the captures showed them too small.
  • Source hygiene: the comments citing a "migration report" are gone, and non-ASCII punctuation was removed from the comments of touched files.
Example-by-example audit (110 entries)
Category Example Profile Measure Status
start-here hello-world Direct no change needed: Hint Text sits inside the guide:first-scene region; a single click prompt is fine for the first example. kept
start-here resize-and-dpr State no change needed: Info Text shows measured size and DPR, which is the lesson; no legend. kept
start-here scene-less-app Minimal no change needed: Smallest possible app; nothing to add. kept
start-here orb-dodge Showcase no change needed: Complete game loop; keys are in the description; game-over prompt is part of the game, not a legend. kept
scenes-assets multiple-scenes State no change needed: Event log is the lesson; Space/Esc prompts in the scene titles are adequate. kept
scenes-assets loading-screen State Removed the play-scene legend and hint; the on-canvas label already names Space. done
scenes-assets asset-catalogs State Removed legend and hint; kept the load-state status line. done
scenes-assets pause-blur Direct Removed legend, title and hint; kept the Running/Paused status. done
scenes-assets custom-transition Direct no change needed: Prompts live in the scenes; no overlay. kept
scenes-assets level-loading-and-ownership Parameter Legend and hint dropped; four buttons via demoPanel; room label carries room state, panel status shows residency only. done
scenes-assets cached-assets-offline Parameter Legend, hint, status and mode Text removed; toggle plus four buttons via demoPanel; result Text stays as the single readout. done
input-ui keyboard Direct Legend and hint removed; Held: status kept. done
input-ui mouse-and-pointer Direct Legend and hint removed; position/delta/buttons status kept. done
input-ui action-mapping Direct Legend and hint removed; move/pulse status kept. done
input-ui gamepad State Removed the DOM hud (duplicate status and hint); the canvas Text already shows the waiting and connected state. done
input-ui key-rebinding Direct Dynamic legend and hint collapsed into one status line: Jump: Space (J to rebind). done
input-ui multitouch Direct Legend and hint removed; kept the Active touches n / 10 status. done
input-ui hud-and-widgets Showcase no change needed: Widgets are the content; Tab/Enter focus behaviour is covered by the description. kept
input-ui settings-menu Parameter no change needed: In-canvas widgets are the content; applies saved volume to master without playing any sound. kept
input-ui text-entry-and-scrolling Direct no change needed: Native canvas fields; nothing to add. kept
input-ui dialog-system Direct Removed legend, hint and the DOM reply panel; reply choices are now in-canvas ui Buttons. done
input-ui multi-view-split-screen Direct Replaced 16 onActive/onStop key handlers and the pad plumbing with one ActionMap and VectorAction per player; hint removed. done
sprites-cameras sprite-basics Minimal overlay removed; ship animates alone done
sprites-cameras nested-transforms State no change needed: Fixed local/world readout is the lesson. kept
sprites-cameras pivot-and-anchor Comparison three origins side by side with position dot and bounds; panel and legend removed; texture awaited done
sprites-cameras blendmodes State demoPanel choice with name (n/18); click and Left/Right share state; provenance comment removed done
sprites-cameras masks Minimal no change needed: Static stencil demo. kept
sprites-cameras texture-sampling Comparison Kept side-by-side columns; four controls now one demoPanel; P, Space and wheel share panel state; legend, hint and status removed. done
sprites-cameras world-vs-screen-coords Direct Legend and hint removed; on-canvas readout kept. done
sprites-cameras parallax-starfield Direct Removed the overlay; description covers the pointer interaction. done
sprites-cameras picture-in-picture Minimal no change needed: Nothing to add. kept
sprites-cameras mini-map Minimal Removed the overlay; no input needed. done
animation-game-feel frame-animation Comparison Five characters now animate side by side with name labels; Space pauses all; legend, hint, status and character cycling removed. done
animation-game-feel tween-basics Direct Legend and hint removed; Moving/Complete and one-way/round-trip status kept. done
animation-game-feel tween-chains State Title and hint removed; kept the Leg i / 4 status. done
animation-game-feel easing-curves State Legend and hint removed; curve picker is a demoPanel choice showing name and index; Left/Right share its state; click-halves route dropped. done
animation-game-feel screen-shake-on-explosion Direct Removed legend and the hit counter; description already says click. done
animation-game-feel gamepad-spaceship Showcase Removed title and key legend (the on-canvas prompt names the keys); kept the Score status. Engine drone level left unchanged. done
animation-game-feel boss-intro-cinematic Showcase overlay removed; replay hint only after the sequence; swell 0.6 (was 0.85) done
graphics-text graphics-gradient Minimal no change needed: Static showcase of fills. kept
graphics-text typographic-styling Comparison no change needed: Default vs loaded font side by side already. kept
graphics-text multiline-and-wrap Comparison no change needed: Three wrap modes already side by side; titles are captions, not a HUD. kept
graphics-text bitmap-text-basic Minimal no change needed: Live frame counter is part of the demo. kept
graphics-text mesh-textured-quad Comparison no change needed: Vertex colours and UVs shown simultaneously. kept
graphics-text mesh-deformed-grid Minimal no change needed: Nothing to add. kept
graphics-text mesh-typed-uniforms Minimal no change needed: Nothing to add. kept
effects-rendering bloom-filter Parameter 4-slider demoPanel; legend and echo status removed done
effects-rendering blur-filter Comparison demoPanel: filter choice plus one strength slider; bypass toggle, hud and status removed; caption follows chosen filter done
effects-rendering color-matrix-filter Comparison original, grayscale, sepia, 1D curves and 3D LUT side by side on ramp and painted art; cycle removed done
effects-rendering crt-scanlines Parameter demoPanel with CRT toggle and two sliders; hud, status and hint removed done
effects-rendering custom-fragment-shader Direct mountControls overlay removed; pointer ripple unchanged done
effects-rendering metaballs Parameter demoPanel with pass-order choice and blur slider; legend, hint and status removed; drag unchanged done
effects-rendering render-to-texture Comparison legend, hint, status and capture counter removed; captions kept; tap listener now removed on destroy done
effects-rendering trail-feedback Minimal no change needed: Clean. kept
effects-rendering color-pipeline Comparison hint and legend removed; status-only overlay for HDR tone (Space or tap), omitted when HDR unavailable done
effects-rendering water-mirror Direct legend, hint and strength status removed; drag-on-water documented in description done
effects-rendering video-drawable Parameter clip choice + pause toggle in demoPanel; 1-3 and click share state; clearer clip labels done
effects-rendering render-pipeline State panel and hint removed; click or Space toggles blur with 'Blur: on/off' status done
effects-rendering custom-triangle-renderer Minimal no change needed: WebGPU-only, capability-gated. kept
lighting many-lights Parameter demoPanel with lights slider and Resume button; hud removed; draw-call readout in panel status done
lighting shadow-casters Parameter demoPanel with softness slider and occluder toggle; hud removed; draw-call readout in panel status done
lighting light-cookies Parameter demoPanel with time, sun and cookies controls; left/right cookie split kept as toggle (needs second lightmap system) kept
lighting lightmap-normals Comparison split floor: left with normals, right without; height slider and normal view in demoPanel; DOM labels done
lighting radiance-rooms Parameter 7 controls to 5 (renderer, bounce, motion, lamp, softness); Space pauses; phase, debug field and status removed done
particles emitter-basics Minimal overlay removed; ASCII comments done
particles cursor-attractor-particles Direct legend and hint removed; mode status kept; listeners now removed on destroy done
particles fireworks Direct overlay and launch counters removed; burst scale and timing needs visual check done
particles bonfire Parameter demoPanel with Intensity and Wind sliders; static status and hint removed done
particles audio-reactive-particles Showcase overlay removed; ASCII comments; music 0.6 done
particles gpu-particles Parameter demoPanel with spawn-rate slider; occupancy, frame time and backend shown as panel status done
particles custom-wgsl-module State legend and hint removed; status shows simulation path done
audio play-sound Direct description fixed (no volume control); catalog descriptors done
audio sound-pool Direct legend removed; pointer/touch hold fires too; pooled impacts 0.3 done
audio music-loop Parameter demoPanel volume/rate/loop + pause and restart; Space pauses; duplicate status removed; music 0.5 done
audio audio-buses Parameter demoPanel mixer; locked-state prompt added done
audio crossfade-tracks State Legend, hint, status and two buttons replaced by a Track A/B choice; fade guard moved so mid-fade switches work. done
audio reverb-and-delay Parameter Preset choice, Bypass toggle and Play impact action via demoPanel; status shows wet levels; locked prompt added. done
audio pcm-stream Parameter demoPanel with Produce toggle and three actions; Start button replaced by unlock-driven start and locked prompt; status only for drain/errors. done
audio gpu-pcm-stream Parameter demoPanel with format choice, Produce toggle, Drain and New stream; Start replaced by unlock-driven start and locked prompt. done
audio compressor Parameter loud input 0.6 ramped (was 1.0 jump); demoPanel input/bypass/threshold/ratio done
audio ducking Direct Legend, hint and duplicate status removed; click prompt Text on canvas; pointer listener removed in destroy. done
audio convolution-rooms State right-click cycling removed (main button still filtered); room choice + Left/Right; catalog metal impact added done
audio effect-chains Comparison Chain choice, Bypass and Play impact via demoPanel; title shows bypass; note moved to canvas; locked prompt added. done
audio signal-graph Showcase Legend and hud moved to description with on-canvas hint/locked prompt; dynamic per-node panel kept (rebuilt per selected node); comment ASCII. kept (per-node panel is rebuilt dynamically and does not fit a static demoPanel without loosening its types)
audio vocoder State Wrong 'Phrase pads' legend removed; phrase choice, Processed toggle and Speak action via demoPanel; duplicate labels dropped. done
audio audio-visualisation Showcase overlay removed; paused state shown on canvas; music 0.6 done
audio beat-sync-pulse State tuning slider and hint removed; constant pulse; music 0.6 done
audio listener-and-source Parameter Legend, hint and status removed; Movement/Falloff choices and Sound toggle via demoPanel; readout Text unchanged. done
worlds-navigation tiled-isometric Direct Removed legend, hint and the long initial status; shortened the cell status; credit and bridge hint moved to the description. done
worlds-navigation hexagonal-grid State Legend (incl. literal panel key) and hint removed; layout is a demoPanel choice; pointer cell info moved to the panel status. done
worlds-navigation tile-chunks-and-bands Direct 8 handlers to 4 polled bindings (WASD + arrows); legend removed; demoPanel; ASCII comments done
worlds-navigation tiled-map-import Direct Legend and hint removed; inspect status kept. done
worlds-navigation ldtk-world-import State Legend (literal panel key) and hint removed; level choice and IntGrid toggle via demoPanel; level summary in the panel status. done
worlds-navigation editor-objects-gameplay Direct Legend and hint removed; puzzle-step status kept. done
worlds-navigation grid-navigation Parameter Legend and hint removed; five panel controls via demoPanel (now disposed with the scene); counters moved into the panel status, diagonals dropped from it. done
worlds-navigation tilemap-navigation Showcase Legend and hint removed; status shortened to hunter and patrol agent states. done
worlds-navigation paint-autotiled-room Direct Legend, hint and per-stroke status removed; kept Erase toggle as a demoPanel. done
worlds-navigation tiled-infinite-map Direct Legend and hint removed; resident-chunk counter status kept. done
worlds-navigation worker-streamed-terrain Parameter Legend and hint removed; provider choice, cost slider and New seed button via demoPanel; frame-time status moved into the panel status. done
physics-collision rectangles-collision Direct Legend, title and hint removed; OVERLAP w x h status kept. done
physics-collision sprite-follows-body Direct Legend, title and hint removed; status kept; decorative non-ASCII rules in comments replaced. done
physics-collision kinematic-movement Direct Legend, title and hint removed; blocked/free + normal status kept. done
physics-collision character-movement Direct Legend, title and hint removed; ground/velocity status kept; non-ASCII degree signs in the comment replaced. done
physics-collision tiled-map-physics-actor Parameter Legend, hint and static collider-count status removed; Show outlines toggle kept via demoPanel. done
physics-collision joint-driven-shadows Direct Removed the overlay and its churning angle status; description covers the drag. done
performance-diagnostics pointer-and-hittest Direct legend and hint removed; Front status kept; keys 1/2/3 moved to description done
performance-diagnostics backend-comparison Parameter count slider, texture choice, backend choice in demoPanel; Up/Down, T, B share state done
performance-diagnostics retained-container Parameter label-only legend removed; toggle + live status in demoPanel done
performance-diagnostics immediate-mode-rendering Parameter label-only legend removed; toggle + draw-call status in demoPanel done
performance-diagnostics coroutine-loading Direct no change needed: Keys are listed only in the canvas label; acceptable. kept

Before/after evidence: all 109 runnable examples were captured on the old cddba1d7d site (CI artifact) and on this head with the same smoke harness (Chromium, WebGL2); the side-by-side sheets, the Monaco capture and the Asset Browser capture are on a private evidence page shared with the maintainer: https://claude.ai/artifact/LG73qDaVtxq4ftGwAU2DJx (needs maintainer access).

7. Playground: Output panel, Asset Browser and the fixes they exposed

Output panel (all examples)

  • site/src/lib/preview-console.ts: the parent patches log, info, debug, warn and error on the preview iframe's own console (same origin, no postMessage) in onLoad, before the example's module is appended. Each patch calls the original first, so DevTools keeps every message. Uncaught errors and unhandled rejections arrive through the existing onerror / onunhandledrejection handlers and land in the same record.
  • Every iframe load starts a generation; writes carrying an older generation are ignored, the previous window's console is restored, and a reload or source change detaches before the next document loads. Attaching the same window twice does not double entries.
  • Formatting is plain text only, never HTML: %s %d %i %f %o %O %c, several arguments, cross-realm errors by shape (stack as details), arrays, typed arrays, Map/Set, DOM nodes, circular references ([Circular]), throwing getters; depth 3, 20 items, 2,000 characters per message.
  • Bounded: ring buffer of 500 entries (evictions counted), token-bucket rate limit (burst 100, 50/s) with a running "N messages dropped" entry, identical consecutive messages collapse into one counted entry, and React hears at most one coalesced change per 100 ms (useSyncExternalStore).
  • An uncaught error that the code also logged with console.error moments earlier is upgraded in place instead of listed twice; console.error alone stays an ordinary entry and the example keeps running. TypeScript diagnostics stay in the existing strip with the jump to the line; catalog and source load failures are pinned at the top of the panel.
  • UI: a one-line bar under the preview (Output, error and warning badges, the latest message), collapsed by default and remembered per browser. Expanded: monospace list, Errors / Warnings / Info / Logs filters (aria-pressed), Copy, Clear, autoscroll that stops while you scroll up, stack details per entry, bounded height. Plain logs never open it; the first uncaught error of a run does, unless the visitor collapsed it during that run. It sits in the preview column in split and follows a swap, between preview and editor when stacked, and spans the width on phones. Logging never takes focus from the preview.
  • Scope, also stated in the debugging guide: only the preview page's console. Workers and audio worklets have their own consoles; their messages stay in DevTools.

Asset Browser (debug-layer/asset-browser)

  • Back in the catalog under Scenes & Assets with the normal split view and editable source. The /tools/asset-browser/ pages that shipped in v0.19 are now plain redirects to the playground entry; EXAMPLE_TOOL_RELOCATIONS, the relocation branch in ExampleBrowser, the tool page component and the playground's tool link are gone. Historical versions keep their own catalogs.
  • Layout on the 1280x720 stage: types and a searchable list on the left (Textures 40, Spritesheets 12, Maps 6, Audio 61, Video 3, Fonts 6, all derived from the catalog; no Meshes or Data group because the catalog has none), the preview in the middle, an inspector on the right with facts, the type's controls (demoPanel) and a "Loader holds N assets" list read from loader.inspect().
  • Previews: textures and SVGs with zoom, drag-pan, wheel zoom, Linear/Nearest, checkerboard or plain backdrops, pixel size, format and file size from the package's exported manifest.json (now vendored and in the preview import map). Spritesheets show the atlas with frame bounds, a tapped frame enlarged, and an AnimatedSprite over the atlas's own animations or a chosen frame range with FPS, loop and scale; no animation is assumed when the atlas defines none. Tiled and LDtk maps render through TileMapNode with level choice, per-layer visibility and object shapes; tileset paths resolve against the catalog's absolute source. Sounds draw their waveform with the selected sprite clip highlighted; sounds and music play, pause and stop with volume and loop. Videos play, pause, stop, seek, mute and change speed, with resolution, codec, frame rate, pixel format and audio from the manifest; dynamic range is labelled as file metadata. Fonts and bitmap fonts render editable sample text at three sizes. Maps and atlases have a Data view with the raw JSON, truncated beyond 60,000 characters.
  • Code: a complete scene module built from the current settings (descriptor import, this.loader load, sampler choice as textureOptions, sprite scale, animation clip, map extension, onUnlock-gated playback, video options), with Copy. Only public APIs and the demo catalog; every generated variant is type-checked by the browser test.
  • Races: each selection takes a generation and its own child LoaderScope of the scene. Selecting something else disposes the preview and destroys that scope, which cancels a load still in flight and frees what it held unless another scope holds it. A result that settles after a newer selection is disposed before it can show, play, mount controls or log. Nothing plays on selection; Play registers on app.audio.onUnlock, and Stop and every switch remove that registration, so a start still waiting for the gesture never plays for another selection. destroy() bumps the generation and closes everything.

Fixes found on the way

  • fix(demo-assets): the three sound-sprite sidecars (ui, impacts, digital) were { sprites: { name: { start, duration } } }, but the sound loader reads { name: { start, end } }, so uiSoundSprite, impactsSoundSprite and digitalSoundSprite failed to load. Converted, manifest regenerated, and the catalog test now checks every sidecar's format.
  • fix(site): the playground's TypeScript defaults set noEmit, so getEmitOutput was empty and an edited TypeScript example never reached the preview; Reload also did nothing when the code had not changed. TypeScript models now emit, and every run restarts the preview. The browser test exercises both.
  • fix(audio): the input system cancels pointerdown on the canvas and stops its propagation, which also suppresses the compatibility mousedown, so a mouse click on the canvas never unlocked audio. The unlock listeners now include pointerdown / pointerup and run in the capture phase; the regression test fails without the fix. Audio guide and JSDoc (API JSON regenerated) updated.
  • chore(examples): no-console is off for examples/**; the Output panel makes logging part of how an example explains itself.

Logging in examples

Keyboard (KeyDown: Space, Action: Recentre, held-set changes), Action Mapping (resolved move direction and pulse), Pointer and Hit Testing (which ship a tap hit, where a drag ended, front changes), Drag and Throw Physics (grab, release speed, floor contacts from onCollisionStart), Settings Menu (toggle, theme and reset; not the volume slider, which reports every step of a drag). All log on transitions, never per frame; their catalog descriptions mention the Output panel.

8. Validation

Check Result
CI on 77899e8d9 (A + B fixes) all green, run 39664733145
CI on ed91212c4 all green
CI on the final head 702582560 all 20 jobs green, run 39682090665 (verify package, skip-budget, smoke, webgl, webgpu, firefox, audio included)
Pre-push hook on ed91212c4 (typecheck, lint, sync, unit, webgl, webgpu, audio, tilemap, bench, site, smoke) all PASS
release-dist locally with a production build 18/18, 0 skipped
Gates typecheck / lint / sync / site PASS (guide snippet typecheck needed the package path, fixed)
test/site, test/ci, test/release, test/examples, demo-assets loading 1,049 + 25 tests pass, incl. new demo-panel.test.ts
Full example smoke, Chromium WebGL2, all 110 109 pass, 1 capability skip (custom-triangle-renderer needs a WebGPU adapter; headless Chromium has none)
Stable and Next channel builds at their real bases + channel-artifact verify both verified; Next sample smoke 17/17
Monaco TS + JS completion, Asset Browser, per-example downloads, Vite consumer as reported in sections 4 and 5
Pre-push hook on 3b26123e0 27 gates PASS; lanes unit, webgl, webgpu, audio, tilemap, bench, smoke PASS
CI on the final head 3b26123e0 all 22 jobs green, run 39713584706 (smoke job includes the new playground browser test)
Playground browser test (pnpm test:playground:e2e, new, runs in the CI smoke job) 35/35 against the built site: five severities, arguments, stacks, circular values, rejection recorded once, flood bounded with drop count while DevTools receives all 1,500, collapse respected, reload isolation, focus kept, keyboard, split/swap/stacked/phone layouts, an input example logging; Asset Browser listing, 13 representative assets of every kind, spritesheet and texture settings in the snippet, a delayed late video discarded, many quick switches leave only manifest + current asset resident, a start waiting for unlock cancelled by a switch, double Play starts one voice, leaving the example mid-load raises nothing; every generated snippet type-checks
Unit tests for this pass preview-console.test.ts (18), alias routes, audio unlock regression, sidecar format; test/site slice 157, test/audio 663, demo-assets 175
Gates on the new head typecheck, lint, sync (incl. docs:api:check, examples:sync:check) and site: PASS; browser-audio-chromium 38/38
Example smoke, Chromium WebGL2, changed examples asset-browser, keyboard, action-mapping, pointer-and-hittest, sprite-follows-body, settings-menu: 6/6
verify:package-policy, verify:publish, verify:external-consumers, verify:release-matrix, size, release:prepare incl. full ZIP all exit 0

9. Open limitations

  • Pages: default-branch activation (W15) and the first live verification of /ExoJS/ and /ExoJS/next/ (W16) need separate approval; real GitHub API candidate selection runs only in the publisher.
  • No human listening test: levels are digital values in code, not perceived loudness. Human HDR acceptance of the extended clip was not run.
  • The WebGPU-only example and the WebGPU backend of the backend-comparison panel were not exercised on a real GPU in this pass (headless Chromium has no adapter).
  • A bundler build of demoAssets copies the whole standard set and breaks relative references of maps and bitmap fonts; documented, not redesigned.
  • The ship provenance change rests on feat(examples): playground quality and experience sweep #860's record; please confirm it.
  • Optional, not done: replacing the deliberately ordered src/debug/index.ts re-export of PhysicsDebugDraw with an internal base module.
  • Output panel: worker and worklet consoles are not captured, and DevTools shows the bridge function as a captured message's source location.
  • Engine finding, not changed here: the two-argument View.screenToWorld(x, y) divides by view.width, which setZoom changes, so it maps wrongly at any zoom other than 1 (the world-vs-screen-coords readout drifts when zoomed). The Asset Browser uses the four-argument form. Worth a separate fix with a decision on the documented contract.
  • Observed, not investigated: the editor's status bar shows "modified" right after an example opens; the preview still loads once.
  • The 1280x720 stage scales the Asset Browser's DOM controls with the canvas, to about 50 % in the split layout: usable, but small.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create an environment for this repo.

@codecov

codecov Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Bundle Report

Changes will increase total bundle size by 1.48MB (4.16%) ⬆️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
exo-esm-esm 1.4MB 26 bytes (0.0%) ⬆️
exo-esm-modules-esm 3.99MB 169 bytes (0.0%) ⬆️
site-server-esm 14.36MB 953 bytes (0.01%) ⬆️
exo-esm-pathfinding-esm 59.48kB 59.48kB (100%) ⬆️
exo-esm-lighting-esm 280.9kB 280.9kB (100%) ⬆️
exo-esm-tilemap-esm 274.95kB 274.95kB (100%) ⬆️
exo-esm-particles-esm 250.85kB 250.85kB (100%) ⬆️
exo-esm-audio-fx-esm 244.8kB 244.8kB (100%) ⬆️
exo-esm-physics-esm 344.15kB 344.15kB (100%) ⬆️
exo-esm-tilemap-physics-esm 27.09kB 27.09kB (100%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: site-server-esm

Assets Changed:

Asset Name Size Change Total Size Change (%)
_astro/vendor-monaco.COA-KsTm.js (New) 4.18MB 4.18MB 100.0% 🚀
_astro/vendor-monaco.cx6mqOoS.css (New) 321.61kB 321.61kB 100.0% 🚀
_astro/ExampleBrowser.DDZwyZVq.js (New) 46.79kB 46.79kB 100.0% 🚀
_astro/ExoHeader.CQjR0Y8s.js (New) 33.18kB 33.18kB 100.0% 🚀
_astro/EditorPreview.DIAEY6N_.js (New) 15.95kB 15.95kB 100.0% 🚀
_astro/EditorCode.BDAO5t71.js (New) 14.74kB 14.74kB 100.0% 🚀
_astro/GuideExamplePreview.D1g111iW.js (New) 1.55kB 1.55kB 100.0% 🚀
_astro/vendor-monaco.DHhsN6x-.js (Deleted) -4.18MB 0 bytes -100.0% 🗑️
_astro/vendor-monaco.CTGdSUG0.css (Deleted) -321.61kB 0 bytes -100.0% 🗑️
_astro/ExampleBrowser.DWdPPPgG.js (Deleted) -32.88kB 0 bytes -100.0% 🗑️
_astro/ExoHeader.RYGSSURu.js (Deleted) -31.68kB 0 bytes -100.0% 🗑️
_astro/EditorPreview.DHvRZuGN.js (Deleted) -20.78kB 0 bytes -100.0% 🗑️
_astro/EditorCode.DxjfxXy1.js (Deleted) -16.74kB 0 bytes -100.0% 🗑️
_astro/GuideExamplePreview.DiK2OyEB.js (Deleted) -1.59kB 0 bytes -100.0% 🗑️
_astro/url-builder.Df6b2gTD.js (Deleted) -1.02kB 0 bytes -100.0% 🗑️
_astro/tsMode.BAiVbzXv.js (Deleted) -691 bytes 0 bytes -100.0% 🗑️
_astro/htmlMode.BVnfQFTF.js (Deleted) -674 bytes 0 bytes -100.0% 🗑️
_astro/jsonMode.BVJtg0v8.js (Deleted) -673 bytes 0 bytes -100.0% 🗑️
_astro/cssMode.B4Quq5UG.js (Deleted) -651 bytes 0 bytes -100.0% 🗑️
_astro/freemarker2.DFaDDwPZ.js (Deleted) -300 bytes 0 bytes -100.0% 🗑️
_astro/abap.c4nUsoAl.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/apex.C1FueZ21.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/azcli.CtFIETPK.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/bat.DRFnt8Ez.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/bicep.BK-74pps.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/clojure._KykpezW.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/cameligo.C452lLNY.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/cpp.BEwdyNGe.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/csharp.BZF6GALN.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/coffee.B6fd3_Hh.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/csp.BtyNp4Z6.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/css.UuZJihe_.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/dart.DX-_QmvY.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/dockerfile.DOgCl75i.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/cypher.Bn3phady.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/elixir.BkG1AtQo.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/ecl.pLXA6TLw.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/flow9.B9cloa1O.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/fsharp.CdsNMP-Y.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/go.BTMlUKyu.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/handlebars.DXZW8hFP.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/graphql.DiTniczd.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/html.DIilynmU.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/hcl.CQ1ZHE1_.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/java.DovkAtb-.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/ini.ChCPj8Qo.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/javascript.CdOROqNO.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/julia.B3Cerxln.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/less.CcjNpsMe.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/kotlin.Bn-hxYuk.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/lexon.DDktZp2O.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/liquid.BkTK8DuE.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/lua.B1XvjJF8.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/m3.DeMzqVnU.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/markdown.CWfbTo-0.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/mdx.nbZLpzGO.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/mips.Cj3b_RQP.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/msdax.Dj7HVb3S.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/mysql.DreFu195.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/objective-c.BAn301dR.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/pascaligo.qxjzr3Mr.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/pascal.CvziQwU1.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/perl.k0QqPwz3.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/pgsql.C7q9VSlE.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/php.ByTC34NY.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/pla.Do02v04G.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/powerquery.B-f9NBhx.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/postiats.D8x-wU8Q.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/powershell.Cb4J6K19.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/protobuf.DsJBIo3n.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/pug.BKVaCn71.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/python.1QMcVJPJ.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/r.B72EZ76v.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/qsharp.DQsW9gGT.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/razor.olpbeJdt.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/redis.Dl8qcy2C.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/redshift.DOeVtGcU.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/restructuredtext.BSgMlc4t.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/ruby.DPSL-8ez.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/rust.BVRsZrdT.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/sb.w94-NYgS.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/scala.5QHhIvRZ.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/scheme.7Keqtd_p.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/scss.CX_SVBHT.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/shell.1BAKOmIl.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/sophia.CtkhufDD.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/solidity.DLWMTY9Q.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/sparql.CkpMPB48.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/sql.C5UlFbar.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/st.CPl8cNo5.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/swift.BwJvT9kr.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/systemverilog.elyj6uRL.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/tcl.D3qo4jAc.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/twig.CxA0KwCF.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/typescript.CQUKV5No.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/typespec.Bvymw_YG.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/vb.Msryfknv.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/wgsl.CSPUEjbk.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/xml.CfIAEqM4.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
_astro/yaml.CLWUwsIJ.js (Deleted) -88 bytes 0 bytes -100.0% 🗑️
view changes for bundle: exo-esm-esm

Assets Changed:

Asset Name Size Change Total Size Change (%)
exo.esm.js 26 bytes 1.4MB 0.0%
view changes for bundle: exo-esm-modules-esm

Assets Changed:

Asset Name Size Change Total Size Change (%)
audio/audioContext.js 89 bytes 8.47kB 1.06%
debug/index.js 80 bytes 687 bytes 13.18% ⚠️

Copy link
Copy Markdown
Owner Author

Independent review of head 702582560ce345d380630d4e29dd373564827c69

Confirmed: CI run 39682090665 is successful, including the previously failed package/publint verification and the separate release-dist lane (18/18, reported in JUnit). The CI smoke job runs a 17-example sample; the uploaded contact sheets cover the 109 renderable examples (the WebGPU-only entry is skipped on that host). The new ESM/subpaths, typed catalog and release artifact flow are materially improved.

P2 — stale Asset Browser media requests can play after selection changes

In examples/debug-layer/asset-browser.ts, select() increments request, awaits preview(key), and only afterward checks whether request === this.request. But preview() itself starts this.app.audio.play(sound/stream) and video.play() after awaiting the media load, before that check. If the user switches assets while loading, an earlier selection can start a stale voice/video. In a fast A→B switch, the older completion may also replace this.voice/this.video, leaving previously started playback untracked by stopPlayback().

Please gate playback and other side effects against the current request token before starting them, and handle teardown/in-flight completion. Add a targeted async test for A→B out-of-order resolution (including sound/music/video) and switching during destroy. This issue is not exercised by first-frame smoke.

Release/DX decision — production bundlers and composite-media paths

packages/exojs-demo-assets/src/standard.generated.ts creates a single large demoAssets object with one literal new URL(..., import.meta.url) per file. The README correctly warns that Vite emits the whole standard set and can break relative Tiled/LDtk/bitmap-font links through hashed/flattened paths. createDemoAssets(baseUrl) + exojs-demo-assets-copy is the documented workaround. Before advertising standalone reuse of every catalog entry, verify a real production Vite consumer for a composite asset using that supported path; document the default import as suitable for demos rather than automatically production-tree-shakeable. This may be tracked separately if the intentional limitation is accepted; don't silently change the loader API.

Provenance and visual evidence

The updated ship-a.png is described as ExoJS-original in #860, but binary/hash similarity cannot establish authorship. Keep source/drawing provenance or the responsible author's explicit confirmation before representing it as MIT-owned original work.

The uploaded before/after sheets show a meaningful reduction in HUD clutter and stronger comparison labs (pivot, color matrix, frame animation, normal-map lighting). These are first-frame images, not evidence of animation, clicking, audio quality or GPU acceptance; the Asset Browser screenshot itself was captured before the final category-list fix.

Suggested disposition: complete the bounded Asset Browser race fix; explicitly accept/document the bundler limitation and provenance; rerun relevant validations on the final head. No merge, npm publication or Pages activation was performed by this review.

@Exoridus
Exoridus merged commit d526381 into next Oct 22, 2026
23 checks passed
@Exoridus
Exoridus deleted the arch/v020-migration branch October 22, 2026 07:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant