@jgengine/ws 0.38.4 → 0.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -26,6 +26,207 @@ At publish, rename this heading to the new version and mirror the entries into
26
26
  `packages/core/src/meta/changelog.ts` (the typed `CHANGELOG` export).
27
27
  -->
28
28
 
29
+ ## 0.40.0
30
+
31
+ ### Migrate
32
+
33
+ - **Bump lockstep SDK packages to `^0.40.0`:** `@jgengine/{core,rapier,react,ws,node,sql,convex,shell,editor,assets,navbake}`. CLI `jgengine` is `0.37.0`; `@jgengine/github` is `0.27.0`.
34
+
35
+ ### Added
36
+
37
+ - Impostors: repeated models with a bounding radius of 2.2 m or more (trees, palms, buildings) placed as scatter or as batched static objects draw as camera-facing cards past a distance that grows with the model's size and the graphics tier (70 m plus 16 m per metre of radius on high). Each model is baked once at load into an albedo and normal atlas seen from 64 directions over the upper hemisphere (36 on low), so far cards keep their lighting and cast shadows. Every far placement of a model shares one instanced card draw, whichever batch cell it sits in. Low tier keeps impostors with a smaller atlas; models that fail to bake keep drawing as meshes.
38
+ - `postProcessing.atmosphere` (`AtmosphereConfig`, `DEFAULT_ATMOSPHERE`): exponential height haze that fades distant ground into the sky and brightens toward the sun, plus screen-space light shafts where the sky shows through gaps. On by default on medium and up, tuned per `mood` (overcast hazes without shafts, golden and dusk have the most of both); skipped when the scene has no shadow-casting sun.
39
+ - World reflection probe: on medium and up the shell captures the scene around the camera into a cube map one face per frame (64/128/256 px by tier, every 8/4/2 s or after the camera travels), and metal, glossy and glass materials without their own environment map reflect it. An authored HDRI or cube map still wins.
40
+ - The sky dome adds a forward-scattering glow around the sun and reddens the horizon on the sun's side when the sun is low, following the day cycle.
41
+ - Wet surfaces: while it rains, every lit opaque surface darkens and turns glossy over about 14 s, and up-facing ground collects puddles that mirror the sky and the world reflection probe; it dries over about 70 s after the rain stops. A world that opens in rain starts soaked. Glass, water and custom shaders stay as they are; `material.userData.noWetness = true` opts a material out.
42
+ - Wind from the weather: plants and cloth bend in the rain field's wind direction and harder as it rises. Models without a `wind` setting now sway when their mesh or material names read as foliage (palm, leaves, bush, tree…) and only their cloth parts flutter when they read as cloth (flag, banner, awning…); `wind: false` keeps a model rigid.
43
+ - `ambientLife` (`AmbientLifeConfig`): a flock of gulls circles over the ground in view under a sky in fair daylight and leaves in rain or at night (on by default, `birds: false` to turn off); `litter` blows paper along the ground when the wind picks up; `steam` vents breathe plumes that thicken in rain and at night. `ambientLife: false` turns all of it off.
44
+ - `postProcessing.contactShadows` (`ContactShadowConfig`, `DEFAULT_CONTACT_SHADOWS`): screen-space shadows toward the sun so feet, wheels and props sit on the ground. On by default on medium and up; `false` turns it off.
45
+ - `aa: "taa"`: jittered temporal antialiasing with neighbourhood clipping and a light sharpen.
46
+ - `createCityLayout`, `cityRoadSource`, `cityLots` and `tuneCityLayout` (`@jgengine/core/world/cityLayout`): one street and lot layout with US-city defaults (3.5 m lanes, 2.5 m sidewalks, 15 cm kerbs, zone setbacks 0-6 m, floor ranges, corner clearance, driveways). Every number is a live devtools slider; streets redraw through `environment({ roadSource })` and lots re-lay on change.
47
+ - `deriveBuildingLots({ crossClearance })` keeps lots further from roads they do not front.
48
+ - `road({ sidewalk: { kerb } })` sets the kerb height; default stays 14 cm.
49
+ - `ModelMaterialOverride.materials` limits an override to named materials.
50
+ - Scale-only `visual.scale` placements now draw the model scaled (and still batch), not only fallback boxes.
51
+ - Facade library materials `facade/brick-red|brick-grey|brick-brown|glass-blue|glass-mirror` (ambientCG Facade018A/019A/020A/001/005).
52
+ - Realistic model library (`@jgengine/assets/catalogs/modelLibrary`): 58 Poly Haven CC0 models by intent across nine genres, `findLibraryModel`, `libraryModel`, `libraryModelCredits`, known gaps, a `generated` origin for game-committed AI models, and `assets pull model:<name>` / `model-library` with md5 checks, 512 px textures, near/far LOD and meshopt compression.
53
+ - Tune-panel sliders and number fields carry their control name as an accessible label, so drives and screen readers can reach them.
54
+ - Rigged characters placed with `instanceId` now plant their feet on the ground (`ik: "auto"`) and turn their head toward the local player when they come within 7 m, from any angle in front of them (`@jgengine/shell/render/characterGlance`). Both skip characters more than 20 m from the camera, dead characters and the low graphics tier. `ik: "none"` turns both off for a model. Debug snapshots show `feet planted N · glancing N` under `render/characters`.
55
+ - Entity models with no `animation` set default to `"auto"`, so a catalog rig picks up its idle, walk and run clips without extra declaration.
56
+ - Positioned sounds are placed around the head by default (`audio.panning`, default `"hrtf"`; `"equalpower"` is a cheaper stereo pan, `"off"` keeps them centred). Distance loudness still follows each sound's `falloff`; a sound's own `spatial` block still wins.
57
+ - World reverb on positioned sounds, on by default (`audio.reverb`): authored zones (sphere or box, preset `room`, `hall`, `cave`, `outdoor` or `none`) win; elsewhere the shell casts six wall rays and one ceiling ray around the player twice a second and picks room, hall or outdoor. Changes of space blend over about a second. Each bus feeds the reverb through its own level, so muting a bus silences its echo. `reverb: false` keeps everything dry.
58
+ - One-shots (gunshots, impacts) that start behind geometry now play quieter and muffled, on their own budget of 6 line tests per 100 ms.
59
+ - Adaptive music (`audio.adaptiveMusic`, off by default): hits the local player deals or takes raise an intensity that decays in quiet, and the tiers crossfade the built-in explore, tension and fight themes (`ADAPTIVE_THEMES`), or the game's own tiers. `audio/adaptiveMusic` probe.
60
+ - `window.__jgengineAudio.spatial()` reports panned voices, reverb sends, muffled one-shots and the current space.
61
+ - Impact layer (`presentationEffects.impacts`, on by default): sparks and dust at every hit sized by weapon class (light, medium, heavy, melee, explosive, vehicle; `impactClassFor` reads weapon words, `classes` overrides), bullet holes and scorch marks where shots miss into a surface (on the drawn road or wall, not the physics floor under it), camera kick and rumble near blasts and crashes with distance falloff, and a short hit-stop on the local player's heavy hits, hits taken and kills. Reduced motion drops the camera kick. `render/impacts` probe.
62
+ - Fallen bodies: a killed rigged character's last pose drops as a physics ragdoll, lies 5 s, then sinks away; up to 6 at once, off on the low tier, `impacts: { ragdolls: false }` turns it off. A game that keeps its dead entity is left alone. `render/ragdolls` probe.
63
+ - `fireProjectile({ impact })` tags how a shot's landing reads (`"explosive"` for a grenade) without changing its flight or damage; `projectile.settled` now carries `item` and `impact`.
64
+ - World model triggers (`triggers`): a rule fires the moment its conditions become true, applying `then`, firing a ripple, logging a note and running a hook; it re-arms when the conditions lapse, or never with `once`. Turf changing hands or a district rioting is now one declaration.
65
+ - Ripple variants: a ripple can be a list, each variant gated by `when`, `whenPlace` (facts of the place it lands on, such as its owner) or `whenTarget`, so one event lands differently by who holds the block.
66
+ - Targeted schedules and ripple targets: a schedule with `on` picks one built instance of a kind or tag (filtered by `whenTarget`, rolled from the world clock so a reload rolls the same), with an optional `chance`; the ripple's `target` changes land on it and `{target}` names it in the note. Slots take a `label` for that name. Action ripples carry their target too.
67
+ - Live facts (`createWorldModel(..., { live })`, `defineGameSpec({ live })`): facts the game computes, such as the hour or the wanted level, that conditions, lines, flows and triggers read and saves never hold.
68
+ - `WorldModel.advance(seconds, { maxFires })` catches up time spent away in one step: flows pay, facts calm, queues finish, triggers fire and each schedule fires a bounded number of times, with the gains and log lines returned for a "while you were away" summary.
69
+ - Police response cover and pay-offs: `PoliceTickContext.hidden` stops sightings and runs the cold trail `hiddenRate` (default 3) times faster for a garage or hideout, and `PoliceResponse.forgive(amount)` takes heat off at once for a bribe or a fine.
70
+ - `@jgengine/core/worldModel/worldModel`: `defineWorldModel` / `createWorldModel` declare one world of factions, places, people, things, jobs, ripples and schedules as flat facts. Missions, dialogue lines, phone contacts, map blips, the next-step tracker and a news log all read from it, and its state lives in one store, so saves and co-op sync come with it. `spend` applies costs all-or-nothing, and `hook` names game code for whatever a declaration can't say (#2412, #2413).
71
+ - Mission `survive` objectives accept `until`: a world-fact condition to hold for, with or without a `duration`.
72
+ - Model library grows to 88 entries: 13 rigged Microsoft Rocketbox humans (MIT; casual, business, police, worker, guard, jogger, nurse, soldier) with Idle/Walk/Run/Talk clips, 10 Rocketbox animals, a Khronos CarConcept sports car (CC-BY 4.0, logos removed), four Poly Haven trees (jacaranda, fir and pine saplings, large quiver tree) and engine-built `scifi/server-rack` and `scifi/robot-arm`.
73
+ - `assets pull` converts Rocketbox FBX to GLB in Bun (`pullRocketboxModel`), pulls Khronos samples with logo scrubbing and builds authored models (`buildAuthoredModel`); every file is checked against its source's hash.
74
+ - `thinIslands` and the Poly Haven origin's `thin` / `error` options make multi-million-triangle tree scans usable (jacaranda: 118k / 53k triangles, 2.7 MB).
75
+ - `imageCodec`: TGA and PNG decode, PNG and JPEG encode, box downscale, Git blob sha.
76
+ - `facade/glass-curtain` (ambientCG Facade006): glass curtain wall with aluminium mullions and spandrels, 28 m tile.
77
+ - `defineGameSpec({ show })` (`@jgengine/core/worldModel/gameSpec`): built instances of the named kinds or tags appear as entities at their cell or slot (on the ground) and leave when removed or sold, with no game code; a loaded save rebuilds them.
78
+ - `defineGameSpec({ autoStart: true })`: the next available story job starts by itself, so a story runs beat after beat without a giver contact.
79
+ - `pursue({ anchor, hunts })` (`@jgengine/core/scene/behaviors`): `anchor` keeps a pursuer beside another entity (a tamed pet heels at about 2.5 m and returns to it after a fight); `hunts` lists the entity names it auto-aggroes on, instead of the player.
80
+ - The dev runner serves a game's own `public/` files (`apps/dev/gamePublicPlugin.ts`), so a game's committed models load under `bun run drive` as they do under the game's own Vite.
81
+ - `registerEntityMuzzle` / `readEntityMuzzle` (`@jgengine/shell` render): say where an entity's shots visibly leave from; rigged characters register their right hand automatically.
82
+ - Hit markers (`presentationEffects.hitMarkers`, default on): when the local player's attack damages something, a tick flashes over the spot hit, larger and red on a kill. It marks the hit itself, so it works with cursor aim and crosshair aim.
83
+ - Muzzle flashes (`presentationEffects.muzzleFlash`, defaults to the `tracers` setting): a 55 ms star where each direct-fire shot leaves the gun hand, barrel or first-person gun. Reduced motion makes it smaller and dimmer.
84
+ - `render/shotFeedback` devtools probe counts hit markers, kills and flashes.
85
+ - Fire commands get `held` (`{ yaw, pitch, aim, held }`): `false` on the click, `true` on `repeatMs` re-fires, so one binding drives a single-shot pistol and an automatic rifle. `ItemUseInput.held` carries it into item-use handlers.
86
+ - `combat.recoil` event (`{ from, pitch, yaw?, recover? }`): the local player's view climbs by the kick in about 65 ms and settles back (default about 85% within 0.3 s), capped at 0.35 rad per burst, in views that aim with the look. `input/recoil` devtools probe.
87
+ - `GameFrame` (`@jgengine/react/gameFrame`): a panel or game button made of a material (`steel`, `brass`, `hull`, `wood`, `stone`, `paper`, `enamel`, `glass`, `carbon`, `leather`) with a cut silhouette (`chamfer`, `notch`, `slant`, `tab`, `banner`, `shield`, `ticket`, `torn`, `hex`, `box`), a rim, tint and glow. Textures are generated SVG noise and lighting, so no art files ship. `as="button"` lifts on hover, sinks on press, glows on keyboard focus and plays the engine UI sounds. `gameFrameVars()` dresses an element the game already owns.
88
+ - `GameCursor` (`@jgengine/react/gameCursor`): a brackets, chevron, bar, diamond or frame cursor that glides to the `useMenuNav` current item, then focus, then hover. Under reduced motion it jumps instead of gliding.
89
+ - `GameSlider` (`@jgengine/react/gameSlider`): a drawn slider (a groove with a knurled knob, or lit pips) with keyboard, drag and screen-reader support. Inside a menu, Up and Down still move between rows.
90
+ - `useGameFonts`, `loadGameFonts` and `gameFontStacks` (`@jgengine/react/gameFonts`): load a game's bundled display and body fonts and get CSS stacks with installed fallbacks.
91
+ - `HudFade` and `useHudAwake` (`@jgengine/react/hudFade`): a HUD group fades after a quiet spell, comes back when a watched value changes or on hover, and holds while urgent.
92
+ - `GameFramesPreview` fixture (`bun run shoot --fixture GameFramesPreview`): every material and cut, game buttons with each cursor, and both slider looks.
93
+ - `UiKitMenuPreview` fixtures for the Continue and Saves panes on every kit (`UiKitMenu-<kit>-continue`, `-saves`).
94
+ - A guard test (`hudNoFilters.test.ts`) fails if a UI kit, a game-UI block or an engine HUD primitive uses `filter` or `backdrop-filter`.
95
+ - World model primitives for every genre:
96
+ - `kinds` are things placed at runtime (towers, rides, racks), with costs, build times, limits and refunds.
97
+ - `slots` and `grids` hold placements with footprints.
98
+ - `actions` (upgrade, repair, train, sell) have costs that can be refused and scale by a target fact.
99
+ - `totals` sum, count or take the max across subjects.
100
+ - `flows` are per-second streams like income and upkeep.
101
+ - `queues` time work on the engine work queue.
102
+ - Refusals say what was short ("Upgrade needs 60 more gold").
103
+ - `@jgengine/core/worldModel/gameSpec`: `defineGameSpec` turns a whole game declaration into an installable system. Its world is one store, so saves and co-op sync come with it. It provides `<id>.place/remove/act/cancel/job.start` commands that refuse with reasons, `onReady`/`onRemoved`/`onLanded` callbacks to put builds on screen, and named code hooks for anything a declaration can't say (#2413).
104
+ - World model kinds take `tags`: an action's `on`, a total's `of` and `instances(tag)` cover every kind carrying the tag (one Upgrade for every tower).
105
+ - Selling refunds a share of everything an instance cost, build plus upgrades (`WorldInstance.paid`); tick `removed` now lists `{ id, kind, label, refund }`.
106
+ - `place(kind, at, { free, built })` seeds a layout without paying or waiting.
107
+ - Kinds and actions take `refuse`, the game's own words when `when`/`whenTarget` doesn't hold.
108
+ - A timed action already queued on a target refuses `busy` instead of queueing twice.
109
+ - Hook calls carry `target`: the instance an action landed on or that just became ready.
110
+ - `defineGameSpec` adds `onRestored` (every built instance on the first frame and after a save loads) and `accepting` (refuse all commands with a message, e.g. "The park is closed"); `<id>.remove` refuses when there is nothing to remove.
111
+ - UI kits (`@jgengine/react/uiKit`, `uiKitRoot`, `uiKits`): `defineGame({ uiKit: "ink" })` picks one of eight complete visual languages — `ink`, `paper`, `parchment`, `terminal`, `holo`, `broadcast`, `arcade`, `signal` — each with its own shapes, type, motion and UI cue sounds. Every kit ships the same seven parts (`KitButton`, `KitPanel`, `KitMeter`, `KitPrompt`, `KitToast`, `KitMenu`, `KitMinimapFrame`), so switching kits never breaks a game. `customizeKit(base, { tokens, components, css, motion, sounds })` reshapes one per game.
112
+ - With a kit, `GameMenu`/`GamePauseMenu` draw their verbs and detail pane in it, the engine settings, saves and credits panes take the kit's colours and type, the shell's notice stack renders kit toasts, and the kit's cue sounds replace the default UI sounds (a game's `audio.uiCues` still wins).
113
+ - `KitActionPrompt`, `useActionGlyph` and `useInputDevice`/`actionGlyph` (`@jgengine/react/inputDevice`) show the bound key, pad button or a tap target for the device the player is using.
114
+ - `KitObjective`, `KitResultScreen` and `KitBanner` (`@jgengine/react/kitFlow`): the next-goal card with distance, a direction arrow, countdown warnings and an idle hint; a run/day result screen; a phase-change banner.
115
+ - `NoticeStack` `maxVisible` (default 3): older notices wait instead of piling up.
116
+ - Preview fixtures `UiKitsPreview` (all eight kits), `UiKit-<name>` and `UiKitMenu-<name>` (a kit's pause menu with the engine settings pane).
117
+
118
+ ### Changed
119
+
120
+ - A game with no `look` now renders `"cinematic"`: ACES tone mapping, restrained bloom (strength 0.22, threshold 0.95), AO, contact shadows and antialiasing, lit by its `mood`. Write `look: "neutral"` for the old unstyled output.
121
+ - Post chains antialias with TAA on high and ultra and SMAA on low and medium when `aa` is unset. The graphics setting "Anti-aliasing (SMAA)" is now "Anti-aliasing" and gates both.
122
+ - The default sky dome shrinks to fit inside a short camera far plane instead of clipping to black.
123
+ - The prototype-look check no longer flags a game for leaving `look` unset.
124
+ - Settings slider rows draw a `GameSlider` instead of the browser's range input.
125
+ - `GameFrame` draws its rim, cast shadow and glow as clipped plates under the face instead of CSS `filter: drop-shadow(…)`. Over a live 3D scene a filtered element is re-rendered every frame: Odd Orbit's 16 HUD frames at 1600x900 ran at 16–17 fps with the old filter rims and 60 fps with plates. The rim looks the same, and the soft cast shadow is now a hard plate 4px below the frame. `gameFrameVars()` now sets `--jg-gf-rim`, `--jg-gf-rim-w`, `--jg-gf-face-cut` and `--jg-gf-glow`, and hand-dressed elements add `data-shadow` or `data-glow` for the shadow or glow plate.
126
+ - Hover, press, focus and disabled on game buttons light or dim the face with an overlay instead of `brightness()` and `grayscale()` filters.
127
+ - The paper and arcade UI kits, `GameCursor`, `GameSlider`, the game menu, the save list, `ActionHud`, `SelectionHud`, notices, `HudFrame`, the interaction prompt, the map, `MenuScreen`'s `blur` scrim, waypoint markers and the modal backdrop no longer use `filter` or `backdrop-filter`. Panels that had a backdrop blur use a denser fill so text stays readable.
128
+ - A free placement (`place(kind, at, { free: true })`) is still worth its price: selling it refunds the kind's share; cancelling it mid-build returns nothing.
129
+ - `GameSpecDecl.onRemoved` receives the removed view instead of an id.
130
+ - `GameMenu` stacks its verbs above the detail pane on a portrait phone instead of squeezing two columns.
131
+
132
+ ### Fixed
133
+
134
+ - A retained loop started without a position is no longer occlusion-tested from where it began, so a world-wide bed is not muffled when the player walks behind a wall.
135
+ - `ctx.game.audio.intensity(amount)` (event `audio.intensity`) lets a game with no avatar of its own (a tower defense, an RTS) raise the adaptive soundtrack: a released wave, raiders on the field.
136
+ - `audio/enclosure` probe shows the ceiling and wall distances behind the automatic reverb choice.
137
+ - World model triggers now also check at the start of a stepped tick, so a value an event or `set` pushed to a threshold between ticks (a grip knocked to exactly 0) fires its trigger before calming nudges it back.
138
+ - `HudCanvas` lane blocks reach the real right and bottom edges on compact displays. The lane frame and block sizes were measured in on-screen pixels and then placed inside the canvas zoom, so with `compactScale={0.86}` blocks stopped about 116 px short of the right edge at 844x390 and 52 px short on a 390x844 phone. They are now measured in the HUD's own pixels.
139
+ - Quantized glTF meshes (meshopt / `KHR_mesh_quantization`) no longer collapse into boxes when merged or baked into crowds: attributes are converted to float before transforms.
140
+ - Crowd bakes no longer render people black: Rocketbox pulls drop the unused vertex colours and the crowd material turns `vertexColors` off.
141
+ - Khronos pulls bake transmission glass into plain alpha glass and zero the emissive factor when the emissive map is removed (no more white-glowing tyres).
142
+ - Road collision follows live road sources: `resolveEnvironmentField` rebuilds the road drape when the roads change, so ground under re-laid streets stays right.
143
+ - `bun run drive <game> --game-dir <path> --flow <name>` reads the flow from the served game's own `proofs/flows/`, not the engine checkout's `Games/<game>`.
144
+ - Shots in chase, orbit, top-down, RTS and side-view games now aim at what is under the cursor (tested against the collision world, 0.5-2 ms per shot instead of a 40 ms full-scene raycast); first-person, shoulder and pointer-locked play keep the crosshair. `pointer.aim` still overrides.
145
+ - Tracers start at the shooter's gun hand (or a `muzzle` node) instead of the body centre, and at the first-person gun in first person. Tracers last 200 ms and are thicker so they read.
146
+ - A cursor (ray) aim now strays inside the gun's catalog spread like a look aim; it was always laser-straight. A ray aim passes `spread: 0` for a precise shot. `Aim.spread` is allowed on ray aims.
147
+ - The shell's settings dialog now draws inside the game's HUD theme and UI kit. It takes the kit's colours and type, or the game's `hudTheme`, and it sits above notices and the result screen. The selected tab, toggles and hairlines read on light kits.
148
+ - Continue and Saves panes on a kit:
149
+ - The save name, eyebrow, saved time and Resume button read on every kit, using the kit's accent ink.
150
+ - On a short pane the picture shrinks first, so Resume stays on screen.
151
+ - Save cards grow instead of clipping their Load button.
152
+ - Long chips trim on phone cards.
153
+ - The pane scrolls inside the kit panel, and its heading stays put.
154
+ - Ink and paper panel heads keep the aside (distance, count) on the title's row. A long eyebrow pushes the title under it, and titles wrap only between words.
155
+ - Settings rows and tabs on a kit pane take the kit's corners and display type: square on ink, arcade and terminal, soft on paper. Every kit gets a `radius` token, and the slider readout never runs off a phone-width row.
156
+ - The `KitObjective` direction arrow has a shaft, so it reads as pointing where it points.
157
+ - `defineGameSpec` reports `onReady` for a build that stood before the first frame when the game has no `onRestored`.
158
+ - Clicking a HUD button with the mouse or a finger no longer leaves it focused, so gameplay keys (pause, movement, hotkeys) keep working after the click. Dialogs, menus and forms keep their focus.
159
+
160
+ ## 0.39.0
161
+
162
+ ### Migrate
163
+
164
+ - **Bump lockstep SDK packages to `^0.39.0`:** `@jgengine/{core,rapier,react,ws,node,sql,convex,shell,editor,assets,navbake}`. CLI `jgengine` is `0.36.0`; `@jgengine/github` is `0.26.0`.
165
+ - `scene.entity.spawn` now defaults `onExisting` to `"keep"`: spawning a live id adopts the body already there and returns its id, instead of throwing. A re-stream, a restore or a far teleport can no longer make a second body for one resident. Pass `onExisting: "throw"` where a duplicate is a bug, or `"replace"` to respawn fresh.
166
+
167
+ ### Added
168
+
169
+ - `createPerception` (`@jgengine/core/world`) takes `solids` — the world collision set, normally `ctx.world.solids` — and a sighting then needs a clear line against it, so an agent no longer sees a target through a building. `maxSightRaysPerPass` (default 64) bounds the wall tests one agent's tick may spend; a pair the budget leaves unchecked is not sighted, and `PerceptionStats.occlusionDropped` counts those. Omitting `solids` keeps the cheap cone.
170
+ - `sightLineBlocked(solids, from, to)` (`@jgengine/core/world`) is the standalone "is anything solid between these two points" test those rays use.
171
+ - `mood` on `defineGame` (`Mood` from `@jgengine/core/render/lookMood`): one art-direction field for the light — `"day"`, `"bright"`, `"golden"`, `"dusk"`, `"overcast"` or `"night"`, or `{ preset, sun, skyColor, horizonColor, groundColor, fog, exposure, saturation, contrast, shadows }` to move a few tokens. It sets a cascaded shadow-casting key sun, a bounce opposite it, hemisphere fill, ambient, the dome's colours and sun (keeping the world sky's radius, clouds and day cycle), the haze range and the grade tint, all in agreement. `look` stays the rendering style; the two compose.
172
+ - `resolveMood` and `MOOD_PRESETS` (`@jgengine/core/render/lookMood`).
173
+ - `sky()` now passes `sunColor`, `fogColor`, `moonIntensity` and `moonColor` through.
174
+ - `road({ materials: { road, sidewalk, curb } })` takes scanned PBR surfaces per layer, e.g. `{ road: librarySurfaceScan("asphalt/dry"), sidewalk: librarySurfaceScan("concrete/smooth", { tint: 0 }) }`. The carriageway and its welded junctions tile one world-XZ scan with a second rotated sample against visible repeats; sidewalks and gutters tile along the kerb. Colour (or, at `tint` 0, grain over the authored colour), roughness around the authored value, the scan's normal map and AO all land; the tyre wear, patching and edge grime still layer on top. A map that fails to load, or the `low` tier, keeps the procedural surface.
175
+ - `material.scan` on any model override (`ModelMaterialOverride.scan`): a scanned surface projected triplanar from the model's own geometry in metres, so a kit model whose UVs only address a palette atlas takes real plaster, concrete or wood grain. `tint` 0 keeps the model's colours. Works on entity models, scattered and static-batched props; off on the `low` tier and until the maps load.
176
+ - `librarySurfaceScan(name, options)` (`@jgengine/assets/catalogs/materialLibrary`) names a library scan for terrain, roads or models; `libraryTerrainMaterial` is now this.
177
+ - Library finishes `paint/gloss` (lacquered car paint: clearcoat 1 over a soft base) and `paint/satin`, response-only: `material: { ...librarySurfaceParameters("paint/gloss"), color }`.
178
+ - `road({ sidewalk: { slabs } })`: paving slab length in metres (default 1.5), `false` for none.
179
+ - The dev runner ships ambientCG `Asphalt003` and `Concrete006` (CC0) beside the existing packs.
180
+ - `bun run release --wait` finishes a release instead of handing it back: it commits the cut, hands the PR to `land` (push, open or update, arm auto-merge), waits for the merge, fast-forwards `main`, waits for the Publish run and prints `release: published @jgengine/core@<version> (publish run <id>)`. A failed run prints `release: publish run <id> failed at job "<job>" at step "<step>" <url>` and exits 1. A release lane spent 265 minutes in 24 h in `Start-Sleep 480; gh run view …; npm view …` loops around this step.
181
+ - `bun run release:await [<version>] [--run <id>]` is the wait on its own, for a version that is already cut; it answers from the registry, not from the workflow's own success message, so a run that reported success without the version on npm is a failure that names both.
182
+ - `bun run worktree:reap` removes every worktree whose branch has merged into `origin/beta` and whose tree has no uncommitted changes. Committed work survives because it lives in the branch, not the working copy. `lane:close` runs it after a PR merges and `agent:worktree` runs it before a new lane, so `.claude/worktrees` stops growing by a working copy per lane — 151 GB of them on this machine. A locked, detached, unmerged or dirty tree is kept and named with its reason.
183
+
184
+ ### Changed
185
+
186
+ - `look: "cinematic"` tone-maps with ACES instead of AgX. Under a sunny sky AgX's flat curve read as a milky veil over the whole frame, and all seven shipped games had already overridden it to ACES. Other looks keep AgX.
187
+ - A styled `look` (anything but `"neutral"`/`"flat"`) with no `mood` and no authored ambient/hemisphere/directional light now gets the mood its sky implies (`"day"`, or `"dusk"`/`"night"` under those sky presets) instead of the sky's bare sun and hemisphere. Skies on a day cycle are left alone. Set `lighting` to keep a hand-built rig.
188
+ - The scene document's sky `sunIntensity`/`ambientIntensity` scale a mood's rig, and its sun angles place the mood's key light, so editor lighting controls keep working under a mood. Document lamps (light markers, environment point lights) join the mood's rig.
189
+ - Every `road()` sidewalk is now raised on a real 14 cm kerb with a vertical kerb face and an outer skirt (it was a 4 cm lip that read as paint on the road), and is laid in slabs whose joints run square to the kerb, each slab its own tone. Matches the generated city's kerb step.
190
+ - The gutter strip is poured concrete between kerb and asphalt (a dusty shoulder with no sidewalk), 35–60 cm wide, never darker than the road. It was a half-metre band at 45% of the road's brightness: a cartoon outline along every street.
191
+ - Lane dashes, stop lines and crosswalks are worn paint: 15–30 cm wide (dashes were 3.5% of the road width, half a metre on an avenue), taking the asphalt's grain and wearing through to it in polished patches.
192
+ - The procedural pavement's crack network is sparse hairlines that wander, instead of a 3 m Voronoi grid of 9 cm lines drawn over every road and sidewalk.
193
+ - `bun run land:batch <branch ...>` lands several finished, independent branches as one push: it merges each onto the growing `beta` tip in order, verifies every merge, skips a branch that conflicts or fails with the reason, and pushes the survivors once so CI runs once instead of once per branch.
194
+ - `bun run land:batch --explain` reads the recorded batch and names the commit a red tip most likely came from.
195
+ - `publish.yml` starts when the release commit's CI finishes and refuses a commit CI has not passed, instead of re-running the full test suite on the release commit: one environment-sensitive test can no longer block a release whose PR was green (#2279). A release still builds, type-checks the packed SDK, checks for source artifacts, packs the candidate and compiles a real adopter against those tarballs.
196
+ - `bun scripts/check-release-ci.ts` reads the `ci.yml` verdict for one commit, so a manual publish answers the same question.
197
+ - Measurement note batch1: a prose-only change, the shape every lane ships a note in.
198
+ - Measurement note batch2: a prose-only change, the shape every lane ships a note in.
199
+ - Measurement note batch3: a prose-only change, the shape every lane ships a note in.
200
+ - Measurement note batch4: a prose-only change, the shape every lane ships a note in.
201
+ - Measurement note batch5: a prose-only change, the shape every lane ships a note in.
202
+ - Measurement line batch6.
203
+ - Measurement note explain1: a prose-only change, the shape every lane ships a note in.
204
+ - Measurement note explain2: a prose-only change, the shape every lane ships a note in.
205
+ - Measurement note explain3: a prose-only change, the shape every lane ships a note in.
206
+ - Measurement note explain4: a prose-only change, the shape every lane ships a note in.
207
+ - Measurement note explain5: a prose-only change, the shape every lane ships a note in.
208
+ - Measurement note explain6: a prose-only change, the shape every lane ships a note in.
209
+ - Measurement note one1: a prose-only change, the shape every lane ships a note in.
210
+
211
+ ### Fixed
212
+
213
+ - `downloadArchive` (`@jgengine/assets/download`) is typed as the `ArrayBuffer`-backed `Uint8Array` it returns, so its bytes are accepted where a `fetch` body is expected.
214
+ - Auto-exposure metered nothing: the WebGL2 pack buffer it reads back into was never sized, so every read failed, the meter saw black, and exposure sat pinned at its maximum (2.5x by default) in every game with a post chain. That is the washed-out, milky look. The buffer is now sized before the first read.
215
+ - `defineGame` now passes `look` through to the playable, so `look: "flat"` also turns off the shell's image-based light rig.
216
+ - A push to `beta` no longer cancels the run already in flight: `beta` serialises, so every verdict the branch reports was reached. On 24 Oct 11 of 44 pushes to `beta` were cancelled, 122 runner-minutes went on runs killed after they started, and "is beta green" was answered by a run that never finished.
217
+ - GitHub still keeps only the newest pending run when a burst arrives; what changes is that no beta run is killed once it starts.
218
+ - `main` is unchanged (never cancelled); a pull request still cancels its own superseded run.
219
+ - `check:consumer-types` installs the candidate it packed. It packed only the `@jgengine/*` packages the games catalog names, so a packed package's own dependency range (`@jgengine/editor` on `@jgengine/navbake@^0.38.4`, `@jgengine/assets` on `@jgengine/core@^0.38.4`) went to the registry, where the version does not exist until after the publish step. The install failed with `No version matching "^0.38.4"` and stopped 0.38.4's first publish run before anything was published. Every published package is now packed, and each one is written into the adopter's `overrides` as well as its catalog, so a transitive range resolves to the tarball.
220
+ - `check-game-front-end` counts `CreditsPane` as a game's credits surface. 0.38.4's games pin reported `Games/deepward - no credits` for a game that renders the credits in the `GameMenu` the engine ships (`CreditsPane` in a detail slot), because the pattern only knew `CreditsScreen`, `CreditsPanel` and the generated asset credits. Harbor Heat's baselined `#credits` entry no longer applies at the current pin, so the baseline shrinks from 8 entries to 7; Brightway Park's stays until the games pin that ships its menu.
221
+
222
+
223
+ - The ci aggregate job accepts a smoke run that actually ran and passed when the path map says no game surface changed. A script under scripts/** is in the heavy set but not in the game map, so GAMES=none with SMOKE=success is a real combination; the job asserted on skipped and failed an otherwise green run, which is what blocked this PR.
224
+ - `check:scaffold-launch` now fails on a second Chrome launch path: only `scripts/browser-lib.ts` and the scaffold template may name the debugger flag, and every capture script must reach the browser through `scripts/browser-lib.ts` (#2132).
225
+ - `bun run ci:status` answers a branch with no PR with one line on stdout, `ci:status: no open PR for <branch> on <owner/name> - nothing to answer`, and still exits 3. The line used to go to stderr only, so a lane that reads stdout saw silence and fell back to polling.
226
+ - `GameMenu` keeps its text readable on a bright scene. The detail pane now carries its own scrim (a dark gradient by default; pass `detailStyle={{ background: "none" }}` for no surface), the surface tint is stronger on the right, and the dim-text default rose from 0.66–0.68 alpha to 0.84–0.88.
227
+ - The selected verb is a filled rail with a 3px accent bar and a ring instead of a thin border, so the current row reads at a glance on any background. `--jg-accent-glow` still colours the fill.
228
+ - `createSessionLoop` and `createRunLoop` take a `persist` seam: the loop writes its own live state on each carry (or every N), on open, on choose and on close, and reads it back at construction. A process killed mid-session now comes back carrying what it had instead of zero. A failing write is ignored so a full disk cannot end a session.
229
+
29
230
  ## 0.38.4
30
231
 
31
232
  ### Migrate
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jgengine/ws",
3
- "version": "0.38.4",
3
+ "version": "0.40.0",
4
4
  "description": "Browser-safe WebSocket client backend for JGengine: protocol codec, createWsBackend, and HTTP read helpers.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -31,7 +31,7 @@
31
31
  "test": "bun test src"
32
32
  },
33
33
  "dependencies": {
34
- "@jgengine/core": "^0.38.4"
34
+ "@jgengine/core": "^0.40.0"
35
35
  },
36
36
  "publishConfig": {
37
37
  "access": "public"
@@ -6,11 +6,11 @@
6
6
 
7
7
  - `CHANGELOG` (const): const CHANGELOG: Record<string, ChangelogEntry> — Per-version engine changelog keyed by semver string (e.g. `"0.10.0"`).
8
8
  - `ChangelogEntry` (interface): interface ChangelogEntry — One release's migrate steps plus added/changed/removed notes (typed mirror of CHANGELOG.md).
9
- - `VERSION` (const): const VERSION: "0.38.4" — Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating.
9
+ - `VERSION` (const): const VERSION: "0.40.0" — Installed `@jgengine/core` semver - compare against {@link CHANGELOG} keys when migrating.
10
10
 
11
11
  ## @jgengine/core/authoring
12
12
 
13
- - `ActionCodesMap` (type): type ActionCodesMap<TAction extends string = string, TCode extends string = string> = Record< TAction, ActionCodes<TCode> > — Maps each game action name to the input codes (hold/toggle keys, repeat rate) that trigger it. In a `defineGame({ input })`, pressing an action runs the same-named command with `{ yaw, pitch, aim }`; `repeatMs` re-fires it while held (automatic weapons).
13
+ - `ActionCodesMap` (type): type ActionCodesMap<TAction extends string = string, TCode extends string = string> = Record< TAction, ActionCodes<TCode> > — Maps each game action name to the input codes (hold/toggle keys, repeat rate) that trigger it. In a `defineGame({ input })`, pressing an action runs the same-named command with `{ yaw, pitch, aim, held }`; `repeatMs` re-fires it while held (automatic weapons), with `held: true` on those re-fires so one binding can drive a single-shot pistol (ignore `held`) and an automatic rifle (accept it).
14
14
  - `AuthoredProvenance` (interface): interface AuthoredProvenance — A first-class entry in the scene document — the editor fully owns and persists it.
15
15
  - `Drop` (interface): interface Drop — A resolved loot outcome — one item or currency grant with its rolled count.
16
16
  - `GameDefinition` (interface): interface GameDefinition<TAssetRef extends ModelAssetRef = ModelAssetRef, TMultiplayer = unknown> — Fully-resolved game description produced by {@link defineGameDefinition} — assets, scene, and opted-in subsystems.
@@ -285,7 +285,7 @@
285
285
 
286
286
  - `CHANGELOG` (const): const CHANGELOG: Record<string, ChangelogEntry> — Per-version engine changelog keyed by semver string (e.g. `"0.10.0"`).
287
287
  - `ChangelogEntry` (interface): interface ChangelogEntry — One release's migrate steps plus added/changed/removed notes (typed mirror of CHANGELOG.md).
288
- - `VERSION` (const): const VERSION: "0.38.4" — Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating.
288
+ - `VERSION` (const): const VERSION: "0.40.0" — Installed `@jgengine/core` semver - compare against {@link CHANGELOG} keys when migrating.
289
289
 
290
290
  ## @jgengine/core/runtime/adapter
291
291
 
@@ -56,7 +56,7 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
56
56
 
57
57
  - `EditorCatalogDefinition` (interface) · `import { EditorCatalogDefinition } from "@jgengine/shell/gameKit"`
58
58
 
59
- ## fire-input — bind a key or mouse button to a named action that runs the same-named command with the aim; repeatMs auto-fires while held
59
+ ## fire-input — bind a key or mouse button to a named action that runs the same-named command with the aim; repeatMs auto-fires while held, `held` tells a click from a held trigger
60
60
 
61
61
  - `ActionCodesMap` (type) · `import { ActionCodesMap } from "@jgengine/core/authoring"`
62
62