@jgengine/convex 0.54.0 → 0.55.1

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,232 @@ 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.55.1
30
+
31
+ ### Migrate
32
+
33
+ - **Bump lockstep SDK packages to `^0.55.1`:** `@jgengine/{core,rapier,react,ws,node,sql,convex,shell,editor,assets,navbake}`. CLI `jgengine` is `0.52.1`; `@jgengine/github` is `0.42.1`.
34
+
35
+ ### Fixed
36
+
37
+ - a save whose stored environment (wet surfaces, fires, flocks) no longer fits the game's authored world loads the rest of the save and starts the environment fresh, with a degraded: console line. On 0.55.0 it threw, so Harbor Heat showed a runtime error instead of its menu for anyone holding a save from 0.54.0.
38
+
39
+ ## 0.55.0
40
+
41
+ ### Migrate
42
+
43
+ - **Bump lockstep SDK packages to `^0.55.0`:** `@jgengine/{core,rapier,react,ws,node,sql,convex,shell,editor,assets,navbake}`. CLI `jgengine` is `0.52.0`; `@jgengine/github` is `0.42.0`.
44
+ - HudFade watch and hold, and useHudAwake, take getters read every frame (watch={() => [air, suit]}, hold={() => alarm})
45
+ - useGameStoreLive is removed; use useGameStore (now shallow by default) or LiveText/useFrameBind for a value that changes every frame
46
+ - useStore and useGameStore compare selections shallowly by default; a selector returning a fresh object or array no longer re-renders every tick
47
+
48
+ ### Added
49
+
50
+ - `FlatWalkRenderer` (`@jgengine/core/render/flatSceneWalk.draw`): The renderer parts the flat walk wraps: three's render lists and shadow pass, and its render counter.
51
+ - `SceneWalkStarts` (`@jgengine/core/visibility/culledTransforms.draw`): The walk starts the scene's last matrix pass found; `serial` changes with every pass.
52
+ - `IdSet` (`@jgengine/core/visibility/visibilitySystem`): A read-only set of ids: membership, count and iteration. Also `RenderableSource`.
53
+ - `STEP_AHEAD_MAX_FRAME_SHARE` (`@jgengine/core/runtime/simAhead`): Frames shorter than this share of a fixed step get their next step run ahead, between frames. Also `IdleTask`, `RecordedTime`, `createIdleTask`, `createRecordedTime`, `shouldStepAhead`.
54
+ - Grass style `detail` (follows realism): up to six times the blades at 1 and half at 0, each narrower or wider by the same factor, so the ground a field covers holds. A bunchgrass puts the extra blades into its clumps; a sward spreads them over more tufts. Flowers per square metre hold at every detail
55
+ - Grass style `exaggeration` (follows 1 - realism): toward cartoon, even heights, deeper curls and round tips; toward natural, uneven heights and blades leaning every way, with mown blades getting torn, pale tips instead
56
+ - The turf under a grass patch follows `detail`: a flat painted colour at 0, its grain as before at 0.5, finer and stronger at 1
57
+ - A page's `?realism=` sets every grass patch's realism, so any scene can be shot or profiled at any realism (`--param realism=1`)
58
+ - `scrambledSobol2` (`@jgengine/core/random/scrambledSobol`): an even 2D spread where every prefix is even and none falls into rows
59
+ - Grass style `shading` (follows realism): toward 0 the sun falls into three flat cel bands, the gloss goes matt and a rim of sky light outlines each blade; toward 1 the blade glosses and light shines through more of the leaf
60
+ - Grass style `colourSteps`: snaps each blade's root-to-tip colour to three flat tones and quiets the blade-to-blade noise, on toward cartoon
61
+ - Grass style `saturation` and `contrast`: brighter, purer colour and a stronger root-to-tip gradient toward cartoon, the species' own colour and a softer gradient toward photoreal
62
+ - Grass style `outline`: dark ink along each blade's edges so clumps read as outlined shapes, on toward cartoon and settable by hand anywhere
63
+ - bun run perf ab answers whether a branch's frames are faster or slower than its base per game with a 99% interval in minutes of perf lock: both load once in a Chrome of their own and take turns in 2 s windows with the other side's renderers suspended, each base window paired with the branch window after it, stopping as soon as the interval answers; a planted 5% was caught in 4-24 pairs and same-code no-ops never read changed
64
+ - bun run perf post-audit redraws each changed post draw three's way on live frames and counts differing texels; bun run check postfx-exact checks the half-float rounding on the GPU
65
+ - bun run perf post-audit redraws each changed post draw three's way on live frames and counts differing texels, sharing shadow-audit's lock-first per-game loop; bun run check postfx-exact checks the half-float rounding on the GPU
66
+ - `PostAuditStats` (`@jgengine/core/postfx/postAudit.draw`): What the changed draws did and, with the audit on, what comparing them with three's draws found.
67
+ - perf names the phase a game's measurement is in (load, warmup, frame window, draw-timed GPU window, profile), so a session killed at its deadline says which one hung
68
+ - `GpuTiming` (`@jgengine/core/devtools/frameBreakdown`): How the breakdown times the GPU.
69
+ - `ShadowPassPlan` (`@jgengine/core/lighting/shadowCache`): Same-image shadow caching, the renderer-agnostic half: what one light's depth pass draws this frame.
70
+ - useSceneSwap and useSceneSwapping change place without a frozen frame, building the new place in the background behind a travel card
71
+ - prepareBeforeLink patches a lazily patched material (wet ground) before its first link, so a model that streams in mid-play compiles once
72
+ - `HitchScript` (`@jgengine/core/devtools/frameBreakdown`): One script the browser ran inside a slow frame, as its long-animation-frame timing names it. Also `HitchSession`.
73
+ - `installRenderCounter` (`@jgengine/core/devtools/renderCounter`): Install the counter as React's devtools hook on `scope`. Also `ComponentRenders`, `RenderCounter`, `RenderCounts`, `renderCounter`.
74
+ - `useSceneSwap` (`@jgengine/core/render/sceneSwap.draw`): Change place (a dive, a lift, a district, a level) without a frozen frame.
75
+ - `LiveTextTag` (`@jgengine/core/ui/liveBind.view`): Inline and block elements a live text can be.
76
+ - `sceneSwapping` (`@jgengine/core/ui/sceneSwap.view`): True while a scene swap's card is up, outside React (the frame breakdown marks stalls drawn behind it). Also `useSceneSwapping`.
77
+ - `shallowEqual` (`@jgengine/react/selectSnapshot`): Whether two selections show the same thing: the same value, or arrays or plain objects whose entries are each the same value.
78
+ - `prepareEngineRenderer` (`@jgengine/core/webgpu/engineRenderer.draw`): Do the asynchronous part of starting `backend` (on WebGPU: the shader translator and one device) and return the synchronous factory for one canvas. Also `EngineRendererFactory`, `useEngineRendererFactory`.
79
+ - `requestWebGpuDevice` (`@jgengine/core/webgpu/webgpuGl.draw`): Request the WebGPU device, or `null` when this browser has no usable WebGPU adapter. Also `WebGpuDevice`.
80
+ - perf runs queue machine-wide. A queued run prints 'perf lock: waiting for pid <n> (<branch>: <run>, since <hh:mm>) …' once, waiters go in arrival order, its guard does not count the wait, and its record says the seconds it waited
81
+ - perf, drive and shoot, and the Chrome they start, run below normal priority so Jon's game and editor come first. Only the Chrome or desktop app a mode measures goes back to normal (decided per mode in scripts/perf.ts), and each record says the priority its Chrome drew at. verify:fast, lane:close, check and test stay at normal: at below normal, starved tests failed four landings in a row
82
+ - a capture's Chrome dies with its launcher. A detached watchdog (scripts/chrome-watchdog.ts) ends Chrome's tree and removes its profile within about a second of the launcher being force-killed, and logs every outcome to jg-chrome-watchdog.log in the temp folder
83
+ - bun run perf <game> --production, perf ab --production and perf gate --production time the build players get (the runner as the web deploy builds it: minified, bundled, production React, built once per tree and served locally); the dev server stays the default, and the gate's budgets and baselines are unchanged
84
+ - every perf and perf ab report leads with a scorecard: frame p50, p95, p99 and the worst frame, frames over 16.7 and 33 ms, main-thread ms per frame with the share inside React and react-three-fiber, MB allocated per second and GC time, the same frames with the CPU slowed 4x, load time and download size; --tally <group> records it in the local tally store
85
+ - `GlanceDriver` (`@jgengine/core/anim/characterGlance.draw`): The default head glance without React: resolves the rig once, then {@link pose} turns the head toward the local player each frame. Also `GlanceDriverOptions`.
86
+ - `drawCharacterMotion` (`@jgengine/core/anim/characterMotion.draw`): The resolved parts of the default character motion for draw code. Also `noteGameCharacterMotion`.
87
+ - `CorpseRagdolls` (`@jgengine/core/anim/CorpseRagdolls.draw`): ⚠ undocumented Also `CorpseRagdollsDrawProps`.
88
+ - `ModelAnimationDriver` (`@jgengine/core/anim/modelAnimationDriver.draw`): The engine's model animation driver without React: the same mixer `useModelAnimation` runs — `"auto"` derivation, speed-driven crossfades, one-shots from game events, held poses, death clamp, graph playback with root motion, and character-LOD pose pacing. Also `GraphPose`, `ModelAnimationDiagnostic`, `ModelAnimationDriverOptions`, `ModelMotionStep`, `createGraphPose`, `diagnoseModelAnimation`, `readModelAnimationParams`.
89
+ - `skinnedInstancesDraw` (`@jgengine/core/anim/SkinnedInstances.draw`): Draw-side form of {@link SkinnedInstances}: loads the rig through the shared per-url GLTF cache, bakes its clips to the bone texture once per (url, clips, once, fps) signature, and rebuilds the instanced mesh when capacity changes or rewrites its buffers when the instance list does — the same boundaries the old memos and layout effect had.
90
+ - `FootIkDriver` (`@jgengine/core/anim/useFootIk.draw`): Foot IK without React: resolves the rig once, then {@link pose} applies one frame of correction after the animation mixer. Also `FootIkDriverOptions`.
91
+ - `buildingKitBatchDraw` (`@jgengine/core/buildings/BuildingKitBatch.draw`): The draw-side form of {@link BuildingKitBatch}: loads the GLB once through the shared per-url cache, harvests instanceable sources, and mounts chunk draws per style group.
92
+ - `buildingChunkBudgetScratch` (`@jgengine/core/buildings/BuildingRenderBudget.draw`): ⚠ undocumented
93
+ - `instancedBuildingsDraw` (`@jgengine/core/buildings/GeneratedBuilding.draw`): The draw-side form of {@link InstancedBuildings}: buckets the placements once per prop-identity change, then mounts one draw per box family and per kit model URL.
94
+ - `interiorDressingDraw` (`@jgengine/core/buildings/InteriorDressing.draw`): The drawn side of a planned interior as one draw: trims, pipes, fittings and lamp housings as static instance batches, every cable as one merged tube, and every stain style as one instanced decal mesh. Also `practicalLightsDraw`.
95
+ - `ActiveCameraControls` (`@jgengine/core/camera/activeCameraControls.draw`): What a tool may do to the camera controls that own a scene's view: retarget them and switch them off for a drag.
96
+ - `chaseRigDraw` (`@jgengine/core/camera/cameraRigs.draw`): Vehicle chase rig that trails behind the followed entity's heading. Also `CameraRigEnv`, `cinematicRigDraw`, `lockOnRigDraw`, `observerRigDraw`, `rtsRigDraw`, `shoulderRigDraw`, `sideScrollRigDraw`, `topDownRigDraw`.
97
+ - `firstPersonBodyInstance` (`@jgengine/core/camera/firstPersonBody.draw`): The entity whose body the first-person camera sits in, for draw code. Also `noteFirstPersonBody`.
98
+ - `GameFirstPersonCameraDrawProps` (`@jgengine/core/camera/GameFirstPersonCamera.draw`): Everything the first-person draw reads, resolved by the React wrapper.
99
+ - `GameOrbitCameraDrawProps` (`@jgengine/core/camera/GameOrbitCamera.draw`): Everything the orbit draw reads each frame, resolved by the React wrapper.
100
+ - `SplitScreenRendererProps` (`@jgengine/core/camera/Viewports.draw`): ⚠ undocumented
101
+ - `ImpactFeedbackDrawProps` (`@jgengine/core/combat/ImpactFeedback.draw`): Props the impact draw reads; resolved by the React wrapper each commit.
102
+ - `HitMarkersDrawProps` (`@jgengine/core/combat/ShotFeedback.draw`): ⚠ undocumented Also `MuzzleFlashesDrawProps`.
103
+ - `dataObjectsDraw` (`@jgengine/core/data/DataObjects.draw`): The instanced-box path as an {@link EngineDraw}; `renderItem` stays on the React wrapper.
104
+ - `groundPadDraw` (`@jgengine/core/environment/GroundPad.draw`): A flat circular or rectangular pad resting on a terrain field.
105
+ - `TerrainGroundDrawProps` (`@jgengine/core/environment/terrainGround.draw`): Props for {@link terrainGroundDraw}: the terrain descriptor, live field, optional island center and sculpt snapshot.
106
+ - `fireLandscapeDraw` (`@jgengine/core/fire/FireLandscape.draw`): Draw a {@link FireLandscape}: a ground overlay of flaming, smouldering, charred and ashen cells, flame billboards as tall as each cell's flame length, and smoke puffs rising from burning cells and drifting downwind.
107
+ - `flushAfterRenderTasks` (`@jgengine/core/frame/afterRenderBoard.draw`): Run every task joined with {@link joinAfterRenderTask}, in join order. Also `flushBeforeRenderTasks`, `joinAfterRenderTask`, `joinBeforeRenderTask`.
108
+ - `EngineCanvas` (`@jgengine/core/frame/engineCanvas.draw`): The engine-owned replacement for r3f's `<Canvas>`: one canvas, one {@link createEngineLoop}, and an {@link EngineHostProvider} so every draw wrapper underneath mounts through `useEngineHost`/`useEngineDraw` exactly as it did under `R3FEngineHost`. Also `EngineCanvasProps`.
109
+ - `EngineHost` (`@jgengine/core/frame/engineHost.draw`): The render host a React subtree draws through: the engine loop's provider.
110
+ - `childFrameState` (`@jgengine/core/frame/engineLoop.draw`): A frame state scoped to a subtree: draws mounted through this add their objects under `parent` instead of the scene root. Also `EngineDraw`, `EngineFrameBoard`, `EngineFrameState`, `EngineFrameloop`, `EngineLoop`, `EngineLoopOptions`, `createEngineLoop`, `groupDraw`.
111
+ - `bindFrameSource` (`@jgengine/core/frame/frameBoard.draw`): Drive the shared board from `next` by hand (a test, a headless harness): it becomes the primary whatever was bound before. Also `FrameOwner`, `FrameSource`, `FrameTask`, `FrameTaskRef`, `joinFrameTask`.
112
+ - `clearLoadDrawCache` (`@jgengine/core/frame/loadDraw.draw`): Drop every cached load (tests, scene teardown). Also `LoadDrawOptions`, `loadDraw`, `loadDrawSystem`.
113
+ - `createTestEngineHost` (`@jgengine/core/frame/testEngineHost.draw`): ⚠ undocumented Also `MountedTestDraws`.
114
+ - `disposeObjectTree` (`@jgengine/core/frame/useEngineDraw.draw`): Dispose every geometry and material under `root` — for demo/preview object trees built outside draws. Also `DrawTransform`.
115
+ - `generatorMeshInstanceDraw` (`@jgengine/core/generator/GeneratorMeshInstance.draw`): One placed generator instance drawn through the mesh builder registered for its output family (`registerGeneratorMesh`), regrown from its params and seed.
116
+ - `grassCoverDraw` (`@jgengine/core/grass/GrassCover.draw`): The turf surface under one authored `grass_field` patch: a ground-draped, edge-feathered mat that turns a bare pavement footprint into grass the blade field stands in.
117
+ - `grassPatchDraw` (`@jgengine/core/grass/GrassPatch.draw`): The engine's one grass renderer: a camera-streamed blade field over a turf mat.
118
+ - `AnchoredOverlay` (`@jgengine/core/hud/hudOverlay.draw`): A world-anchored DOM overlay: drei's `<Html position center>` while r3f owns the canvas; under the engine loop a div projected to the anchor every frame, with `distanceFactor` scaling matched to drei's `distanceFactor / (2·tan(fov/2)·distance)`. Also `HudOverlay`.
119
+ - `staticShapeBatchDraw` (`@jgengine/core/instancing/staticShapeInstances.draw`): One instanced batch as an {@link EngineDraw}.
120
+ - `worldItemMarkerDraw` (`@jgengine/core/item/WorldItems.draw`): Draw-side form of the marker's meshes: the ground ring and bobbing gem are added under the caller's pose group, which the frame writes the entity pose onto.
121
+ - `backdropFogDraw` (`@jgengine/core/lighting/SceneLighting.draw`): Draw-side form of {@link BackdropFog}: writes the backdrop's linear or exponential fog onto the real scene (a scoped mount group's `scene` is not the scene fog lives on); clears it on dispose. Also `configuredLightingDraw`.
122
+ - `lightBeamDraw` (`@jgengine/core/map/LightBeam.draw`): A glowing column of light rising from where it is placed, for finding something from afar: a fallen body, a supply crate, an objective.
123
+ - `markerAccess` (`@jgengine/core/map/map.view`): Non-hook access to a marker collection's subscribe/snapshot pair, for draw factories. Also `MarkerAccess`.
124
+ - `mapMarkerBeaconsDraw` (`@jgengine/core/map/MapMarkerBeacons.draw`): World-space beacons for map markers (the visible in-world side of a ping): a floating diamond over a soft light beam, colored by marker kind.
125
+ - `hairCardsDraw` (`@jgengine/core/material/hairCards.draw`): ⚠ undocumented
126
+ - `leasedMaterialMeshDraw` (`@jgengine/core/material/materialAsset.draw`): An {@link EngineDraw} mesh whose material arrives asynchronously: mounts with `fallback` (or a plain standard material), swaps in the leased material when `load` resolves, and disposes the lease with the draw. Also `materialAssetMaterial`, `materialAssetSwatchDraw`.
127
+ - `surfaceMaterialPreviewDraw` (`@jgengine/core/material/surfaceMaterialPreview.draw`): ⚠ undocumented
128
+ - `trackSurfaceScan` (`@jgengine/core/material/surfaceScanTextures.draw`): Acquire a scan for draw code: the first holder starts the decode, `onChange` fires once the maps are ready or have failed, and the last release frees the GPU textures. Also `SurfaceScanTrack`.
129
+ - `triplanarAssetMaterial` (`@jgengine/core/material/triplanarMaterial.draw`): The draw-side form of the old `useTriplanarAssetMaterial`: resolves the asset's maps, applies world-space triplanar projection and returns an owned lease.
130
+ - `entityModelDraw` (`@jgengine/core/model/entityModelDraw.draw`): Draws an entity's model without React: loading, cloning, materials, paint, attachments, parts, animation, foot IK, glance, bounds and collision measurement — the `EntityModel` contract on the engine frame loop. Also `EntityModelDrawOptions`, `MeasureTarget`, `ModelTextureEntries`, `entitySpriteDraw`, `isolatedEntityModelDraw`, `isolatedModelUsesVariants`, `loadModelMaterialTextures`, `singleVariantModelDraw`.
131
+ - `measuredBoundsDraw` (`@jgengine/core/model/measureBounds.draw`): Wraps custom-rendered actor content (`renderEntity` / `renderObject`) and reports its measured entity-local bounds into collider resolution, so the hitbox wraps what is actually on screen instead of the fixed-size fallback box.
132
+ - `modelInstanceDraw` (`@jgengine/core/model/modelInstanceDraw.draw`): The draw-side {@link ModelInstanceConfig} renderer: loads the shared GLTF, clones and places the model through the same build path entity draws use, then poses it every frame. Also `ModelInstanceConfig`, `ModelInstanceDrawOptions`, `ModelInstanceOptions`, `ModelInstanceReady`.
133
+ - `naturePreviewDraw` (`@jgengine/core/nature/naturePreview.draw`): ⚠ undocumented
134
+ - `instancedJointsDraw` (`@jgengine/core/physics/InstancedJoints.draw`): Debug overlay drawing a PhysicsWorld's joints (suspension, ragdoll links, carry tethers) as one LineSegments batch.
135
+ - `placementGhostDraw` (`@jgengine/core/placement/PlacementGhost.draw`): Cursor-following build ghost: valid/invalid tint from a placement controller preview.
136
+ - `postProcessingDraw` (`@jgengine/core/postfx/PostProcessing.draw`): Draw form of the old `PostProcessing` component: owns the EffectComposer graph, the devtools audit probe, tone-mapping override and the cover-gated effect reveal. Also `PostProcessingProps`.
137
+ - `spriteBatchDraw` (`@jgengine/core/render/SpriteBatch.draw`): Renders a sprite sheet / tile atlas as a single InstancedMesh — one draw call for the whole batch.
138
+ - `tileLayerRendererDraw` (`@jgengine/core/render/TileLayerRenderer.draw`): Renders a tilemap as one instanced quad mesh with atlas UV rectangles.
139
+ - `RoadJunctionsProps` (`@jgengine/core/roads/RoadJunctions.draw`): Props for {@link RoadJunctions}.
140
+ - `roadRibbonsDraw` (`@jgengine/core/roads/RoadRibbons.draw`): Renders every road that looks like `road` (equal {@link roadLookKey}) from their trimmed centerline sub-paths (cut back at every junction by {@link buildAuthoredRoadNetwork}): an asphalt ribbon, a curb/edge strip along each border, an optional dashed centerline, and optional sidewalks. Also `RoadRibbonsProps`, `roadsDraw`.
141
+ - `scannedRibbonDraw` (`@jgengine/core/roads/surfaceRibbonMaterial.draw`): One ribbon mesh whose material follows a scanned surface: hidden while the scan decodes (so the shader warmup links only the programs the first frame draws), rebuilt when the maps land or the spec changes, and swapped for the flat fallback whenever the ribbon material is absent. Also `ScannedRibbonSpec`.
142
+ - `instancedScatterDraw` (`@jgengine/core/scatter/InstancedScatter.draw`): Renders resolved {@link ScatterInstance}s grouped into per-chunk, per-species GPU-instanced draws that frustum-cull independently.
143
+ - `ScatterImpostorFieldDrawOptions` (`@jgengine/core/scatter/ScatterImpostorField.draw`): ⚠ undocumented
144
+ - `ProxyPolesSpec` (`@jgengine/core/scene/PoleLineRenderer.draw`): ⚠ undocumented
145
+ - `sceneCaptureBindingDraw` (`@jgengine/core/scene/sceneCapture.draw`): In-canvas binder that registers the live renderer for `requestSceneCapture` and `requestSceneThumbnail` (a photo-mode Capture button, a save slot's thumbnail), which wait for this mount and the first painted frame.
146
+ - `SceneWarmupGateProps` (`@jgengine/core/scene/SceneWarmupGate.draw`): ⚠ undocumented
147
+ - `studioStageDraw` (`@jgengine/core/scene/StudioStage.draw`): The stage's lights, floor and backdrop as an {@link EngineDraw}; `getSpin` resolves the group the React children render under so turntable/faceCamera still spin them.
148
+ - `skyDomeDraw` (`@jgengine/core/sky/Daylight.draw`): Draw-side form of {@link SkyDome}: an owned shader-material sphere that follows the camera on x/z, converts its zenith/horizon through the display-color writers each frame, and rebuilds its material and geometry only when the resolved inputs change — the same memo boundaries the hooks had. Also `SkyFogDrawProps`, `SunLightDrawProps`, `skyDraw`, `skyFogDraw`, `sunLightDraw`.
149
+ - `CameraPoseDraw` (`@jgengine/core/stage/stageDraw.draw`): Mount a {@link CameraPoseSpec} as one draw. Also `CameraPoseSpec`, `OrbitControlsDraw`, `OrbitControlsSpec`, `StageDraw`, `StageLight`, `StageSpec`, `cameraPoseDraw`, `orbitControlsDraw` and 1 more.
150
+ - `carvedTerrainDraw` (`@jgengine/core/terrain/CarvedTerrain.draw`): Renders a `TerrainField` as a deformed ground mesh — the crater/mound view for destructible terrain.
151
+ - `editableGroundDraw` (`@jgengine/core/terrain/EditableGround.draw`): Mesh an editable terrain inside its bounds, coloured by each point's painted surface; bump `version` after an edit.
152
+ - `proceduralGroundDraw` (`@jgengine/core/terrain/ProceduralGround.draw`): A seeded noise-height ground mesh coloured from low to high ground.
153
+ - `RequiredTerrainMaterialInput` (`@jgengine/core/terrain/requiredTerrainMaterial.draw`): ⚠ undocumented
154
+ - `terraformBrushCursorDraw` (`@jgengine/core/terrain/TerraformBrushCursor.draw`): A ground ring showing where a terraform brush will act, coloured by its mode.
155
+ - `noteTerrainQuality` (`@jgengine/core/terrain/terrainQuality.draw`): The graphics tier for draw code, which has no React context. Also `terrainQuality`.
156
+ - `TerrainSurfacesInput` (`@jgengine/core/terrain/terrainSurfacesDriver.draw`): What a terrain surface bake needs: the resolved surface roles, paint-layer scans and graphics tier. Also `TerrainSurfacesSnapshot`.
157
+ - `TolerantTerrainMaterialInput` (`@jgengine/core/terrain/tolerantTerrainMaterial.draw`): ⚠ undocumented
158
+ - `RuneUi` (`@jgengine/core/ui/runeUi.view`): One mounted Rune UI layer. Also `RuneUiEvent`, `RuneUiFrame`, `RuneUiNode`, `RuneUiOptions`.
159
+ - `particleFieldDraw` (`@jgengine/core/vfx/ParticleField.draw`): One pooled simulation drawn as a point cloud or instanced quads, as an {@link EngineDraw}. Also `ParticleFieldDrawOptions`.
160
+ - `CullingProviderDrawProps` (`@jgengine/core/visibility/CullingProvider.draw`): ⚠ undocumented
161
+ - `oceanDraw` (`@jgengine/core/water/Ocean.draw`): An animated Gerstner-wave water surface lit by the scene's sky: it reflects the sky's colours, takes the sun's and moon's light, clears over shallows and foams at the shore wherever `bedAt` puts the bed, and follows the camera to the horizon when `config.follow` is set. Also `OceanDrawProps`.
162
+ - `dustFieldDraw` (`@jgengine/core/weather/DustField.draw`): Renders wind-borne particulate as camera-facing motes.
163
+ - `birdsDraw` (`@jgengine/core/weather/LivingWorldLayer.draw`): Draw-side form of `Birds`: an instanced gull flock orbiting the camera's look point, fading in only in fair daylight. Also `BirdsDrawProps`, `LitterDrawProps`, `LivingWorldProps`, `SteamDrawProps`, `litterDraw`, `livingWorldDriverDraw`, `livingWorldVisualsDraw`, `steamDraw`.
164
+ - `rainFieldDraw` (`@jgengine/core/weather/RainField.draw`): GPU-animated rain streaks around the camera, sheltered under authored roofs.
165
+ - `rainImpactFieldDraw` (`@jgengine/core/weather/RainImpactField.draw`): Bounded ground ripples sharing precipitation intensity and authoritative animation time.
166
+ - `snowFieldDraw` (`@jgengine/core/weather/SnowField.draw`): GPU-animated drifting snowflakes around the camera, sheltered under authored roofs.
167
+ - `markerAccess` (`@jgengine/react`): Non-hook access to a marker collection's subscribe/snapshot pair, for draw factories. Also `MarkerAccess`.
168
+ - WebGPU programs keep camera and light uniforms in a persistent buffer, uploading only the spans that changed each frame
169
+ - `WgslUniformBlock` (`@jgengine/core/webgpu/glslToWgsl.draw`): Where one of three's uniform names lives: `shared` is the program-wide block (binding 0, persistent across frames), `object` the per-object block (binding 1, restaged per draw batch).
170
+
171
+ ### Changed
172
+
173
+ - Background jobs started through Git Bash now write their output to the job log
174
+ - A scene that sets no style draws the same grass as before, with tufts in new places (the placement fix above)
175
+ - A scene that sets no style draws the same grass as before: every new slider sits on today's look at realism 0.5, and all of them are shader uniforms, so moving one never compiles a new shader
176
+ - lane:close merges its own PR once its verify:fast receipt covers the pushed head; no landing queue and no PR CI
177
+ - A passing verify:fast --landing of the same tree is reused: lane:close skips preflight and verify in one line
178
+ - Timed runs share one machine-wide perf lock (scripts/perf-lock.ts), so two never overlap
179
+ - A release publishes even when beta moves while it waits
180
+ - Shadow cache: static casters draw once into a per-light depth layer and each pass redraws only the casters that changed, bit-identical to a full redraw (0 differing texels on all 9 frame games, walking and standing). Closes #3018
181
+ - Tally before/after benchmarks ride every lane close into the PR body
182
+ - WebGPU: correct boot and shadows, cheaper passes, MSAA resolved in its own pass, perf tooling (part of #3066)
183
+ - Before/after (WebGPU p50 ms on the same frozen moment, beta -> branch, run back to back): Fernwild 35.3 -> 13.1, Scrap Signal 17.1 -> 12-15, Harbor Heat 14.8 -> 10.3, Ember Command 11.3 -> 7.6, Odd Orbit 7.6 -> 3.7, Beacon Bastion 5.5 -> 4.0. WebGPU/WebGL went from 1.2-3.2x to 0.8-1.2x. Drift Foundry and Deepward were fast on beta only because they drew less (a missing program, a black frame). Full table and per-run ratios: https://github.com/Noisemaker111/jgengine/issues/3066#issuecomment-6722682320
184
+ - Unequal coverage: there is no paired perf desktop --all run on the final head, because C: had 1-4 GB free and a build set needs 2.6 GB plus the 2 GB capture floor. The last desktop run is at 9c03ac0fe. Repeats per game range from 2 to 5.
185
+ - Cut per-frame engine garbage in hot paths: pooled frame-breakdown timers and draw keys, scene-walk stacks and starts, particle, flock, grass, terrain and visibility scratch, single world-model fact merge
186
+ - # one-pr-split: first PR landed before the goal was met
187
+ - The game-flow system defines its commands from a list with one entry each, with its own comment; a new command adds an entry, and the test lists one command name per line
188
+ - Fernwild's game.config.ts lists every import one name per line, and its prompt sources, systems and movement settings one per line
189
+ - Verified: depth-run passed 135 steps in the browser dev build and in the WebView2 desktop build; the model cannot view images, so screens were checked through probe data and shot.json pixel signatures.
190
+ - Owns lock-before-dev-server/browser for fps-gap, audits, desktop: every perf script that takes the lock (play, gate, fps-gap, shadow-audit and the audits that share its loop, network, desktop, native, timing, drive --hitches/--trace) takes it before it asks for a dev server or launches a browser or app; #3083 and #3087 drop their copies when they rebase
191
+ - The repo's AGENTS.md perf rule and the perf docs say a claim about what players experience comes from a --production run and quotes the scorecard. The dev-against-production comparison was not run (Jon, 2026-11-09)
192
+ - Measured on the Linux host, dev server started the way the broker starts it, deepward then beacon-bastion loaded headless with perf's URL, vite process read from /proc, two paired runs each: after both games resident memory 1,081-1,098 MB before vs 706-839 MB after, peak 1,574-1,600 MB vs 745-872 MB, CPU 26-32 s vs 6.3-6.7 s. Proof on code not edited by hand: a game freshly generated by jgengine create builds its stylesheet in 0.28 s CPU at 383 MB; with the old template line the same file took 11.25 s CPU and 803 MB. bun run perf deepward ran end to end through the broker with the server at 470 MB.
193
+ - The class: every agent worktree runs its own dev server, so any per-server cost that grows with the repo's assets rather than the game being served is multiplied by every running thread. Chosen layer: the engine's own config and templates make the cheap scan the default, and a test keeps it, so the next game (template) and the next script (all use apps/dev's config) get it without anyone remembering. Rejected: one shared dev server per machine keyed by worktree already exists (scripts/devBroker.ts, one per checkout, stopped after 10 idle minutes) and cannot share module graphs between checkouts whose code differs, so it does not cut per-copy cost; moving apps/dev/public out of the Vite root covers only the runner, not a game's own public/ when it runs standalone, and moves 1,200 tracked files; capping glibc malloc arenas (MALLOC_ARENA_MAX=2) was measured and changed little, and it is Linux-only. Checked and left alone: the texture-compression pass each server start spawns costs 0.6 s CPU and 130 MB once the store is filled; the remaining ~700 MB is Vite's module graph for ~3,700 served modules (JS heap 175 MB after two games).
194
+ - Unverified: the Windows numbers were not re-measured (needs a PC thread: start a runner with bun run shoot <game> and read the vite process's private memory); the scan is file reading, not file watching, so nothing here is Windows-specific.
195
+ - lane:close runs its gate (preflight and verify:fast --landing) on the boat host when it is up, so landings started on the PC stop loading it; when the host cannot run it, the gate runs on the PC after a degraded: line
196
+ - Capture tools report a dead browser daemon as a failure, restart it once, and the broker no longer dies with a fairshare slot
197
+ - Boat host timings, alone: dry throat 2650 ms -> 522 ms of a 20 s limit; understeer 40% 3459 ms -> 2310 ms and 60% 3182 ms -> 1967 ms of a 30 s limit, with the measured-vs-analytic gap unchanged (0.0315 and 0.0507 deg/g against the 0.08 tolerance)
198
+ - lane:close resolves a rebase conflict in a game's suite report by merging its rows by proof file, then regenerates the README/PLAN score
199
+ - lane:close keeps both sides when two lanes only added lines at the same place (the base's first); an edit to a line the other side changed stays a real conflict, and the rebased tree is verified before it merges
200
+ - A session that starts on a lane whose AGENTS.md changed on beta since it branched is told to read beta's rules
201
+
202
+ ### Fixed
203
+
204
+ - Runs no longer lose their dev server and browser mid-flow because an older checkout started the shared dev broker inside its own fairshare slot. The broker protocol is now 2, meaning "runs outside its starter's job". A run passes by a busy protocol 1 broker with its own servers, and replaces an idle one. On 2026-11-08 the broker died silently 15 times in 3 hours, each time right after a run from a checkout without #3056
205
+ - frames cost less CPU in every game with the same image: culling walks only changed stores and marks its sets on each object's record, static prop batches re-gather only when their own members entered or left the view and run their sources only after a change, terrain height samples are about 40% faster with identical heights, grass bakes are faster and keep their tufts, foot IK no longer recomposes every ancestor, and three's render and shadow walks start from the nodes they draw
206
+ - the stray browser sweep counts a kill by whether the browser exited, so a busy PC's slow taskkill no longer leaves a dead browser's profile behind
207
+ - A far grass field no longer draws faint rows like a planted crop: tuft roots follow a scrambled Sobol spread instead of R2, whose short prefixes (what a far field keeps) were a near-regular lattice
208
+ - Grass patches share one GPU copy of the blade template instead of uploading their own each, so walking into new grass costs less, most at realism 1
209
+ - Dev telemetry no longer turns off at random on a busy PC: the page waits 30 s, not 1.5 s, for the dev server's report route, and says so in its log when it gives up (perf records had come back without a frame breakdown)
210
+ - `bun run lane:close` on a lane branched before beta's current verify:fast receipts stops in seconds with "Merge beta into this branch (git merge origin/beta) and close again". Before, it ran the whole verify:fast (40 minutes on 2026-11-08), then refused to merge with a misleading "the files changed while verify:fast ran"
211
+ - A lane that changes `scripts/verify-receipt.ts`, which lane:close reads receipts with, now lands with its own landing code, like one that changes lane-close or land
212
+ - the post chain draws three fewer full-screen copies (exposure meter, GTAO blend, atmosphere apply), one draw for the beauty copy, AO and contact shadows, and bloom without its twelve clears, with every frame bit-identical (perf post-audit: 0 differing texels in all 9 games); GPU post time dropped in every game measured
213
+ - perf fps-gap and the perf audits take the perf lock before starting a game's dev server and browser
214
+ - the core API-surface test's budget fits a whole-package type walk on a loaded machine
215
+ - the subcommand test still expected perf gate's old 1500 s guard after the gate's guard became 5400 s
216
+ - the sky dome draws after the opaque scene, pinned to the far plane, so its depth test shades only the open sky instead of every pixel the scene then paints over (0.12-0.18 ms of GPU a frame in Harbor Heat and Fernwild, about 0.03 ms after), and a large sky radius no longer depth-clips to black
217
+ - draw-timed GPU work brackets only the context's draw, clear and blit calls, because a timer opened before three binds uniforms and textures also counts the GPU idle during that setup (Harbor Heat's scene pass read 6.6 ms that way against about 2 ms of draws)
218
+ - baked crowds (cars, planes, buses, people) and scatter proxies keep their index buffers instead of flattening every part to loose triangles, so each shared vertex is shaded once rather than once per triangle corner: the same triangles in the same order, so the same pixels, with about a third of the vertex shading in every pass that draws them (Harbor Heat's 37k-triangle car batch drew 783k triangles a frame at three vertex runs each)
219
+ - perf gate and fps-gap give each game's browser session a deadline that covers the capture's two readiness windows, its warmup and its windows, instead of a fixed 300 s that killed a busy PC's page right after the capture granted it more time; their run guards back that up at 5400 s
220
+ - the frame breakdown can time every draw and clear on its own (gpuTiming "draws"), because a pass timer also counts the GPU idle while the CPU submits; perf fps-gap now reports that GPU work per game and pass beside the old span (Harbor Heat's scene pass: 2.75 ms of work in a 7.7 ms span)
221
+ - Deepward's half-second-plus stalls on dives, lifts and stratum changes, Harbor Heat's freeze getting into a car in the rain, and HUDs that re-rendered every panel every tick (Deepward React renders 8,192/s to 131/s; worst stall in browser long-play 8.2 s to 491 ms)
222
+ - the WebGPU backend draws every game as WebGL does and is now about level with it on the same frozen frame (up to 3.2x slower on beta). WebGL stays the default because WebGPU wins on no game on every run
223
+ - shadow checks run only on frames a pass can serve from the cache, churned and always-dynamic casters skip dead snapshot work, far scatter bands stop casting shadows past 250m, and Fernwild adopts hlod with tighter mesh LODs
224
+ - the fernwild dry-throat test proves food outlasts water at the measured drain rates again
225
+ - lane:close keeps both of two blocks lanes added at one place whole when they open or close with the same lines
226
+ - lane:close keeps one copy of lines a lane carries from another unlanded lane it branched from, instead of a duplicate import name that does not compile
227
+ - a perf run no longer fails on ERR_CONNECTION_REFUSED after a long lock wait; a test fails any script that takes the perf lock and fetches a dev server outside it
228
+ - verify:fast reruns once the test files of a step whose every failure is a timeout (a starved run on a busy PC) and prints a degraded line; a real hang still fails. Browser tests that symlink into a fixed scratch folder clear it first, so a rerun after a killed run does not fail on EEXIST. Also: a failing browser-process listing prints a degraded line, and the capture-readiness, headRevision, guard, harness-parity and land.test limits no longer depend on a fast, idle PC
229
+ - a production build of the runner bundled every game's tests and standalone main.tsx (the glob exclusions only held in the dev server), so opening a game ran its tests and mounted a second React root that crashed the first; jgengine.com's current build has the same 480 test chunks and 9 game entries and needs a release to get the fix
230
+ - the dev runner (apps/dev) holds about a third less memory and spends a quarter of the CPU per server: every Tailwind stylesheet named no source folder, so Tailwind scanned from the Vite root and read all 341 MB of GLB models in apps/dev/public for class names, about 12 CPU-seconds and 700 MB per stylesheet, twice per game load (the runner's stylesheet and the game's)
231
+ - each canvas on a page runs its own frame tasks, so model previews and second viewports no longer draw with another canvas's camera
232
+ - a scene group keeps one group across renders, and a draw rebuilt on every render warns once
233
+ - the fernwild dry-throat test and the reference-car understeer tests no longer sit near their time limits
234
+ - the WebGPU pass recorder and vertex attribute copies stopped allocating per frame
235
+
236
+ ### Removed
237
+
238
+ - `@jgengine/core/ui/liveBind.view` no longer exports `frameBindSubscriberCount`.
239
+ - `@jgengine/react` no longer exports `useGameStoreLive`.
240
+ - `@jgengine/react/hooks` no longer exports `useGameStoreLive`.
241
+ - `@jgengine/core/webgpu/engineRenderer.draw` no longer exports `engineRendererFactory`.
242
+ - `@jgengine/core/lighting/CascadedShadows.draw` no longer exports `CASCADE_DEPTH_STEP_TEXELS`.
243
+ - boat.dev machines and the fairshare slot from verify:fast and lane:close. Checks and landings now run every step on whatever machine the session runs on (T3 Code decides where sessions run), so there is no boat/local branch, no exit 75, and no JG_VERIFY_BOAT, BOAT_EXE, JG_BOAT_TIMEOUT or JGENGINE_SUITE_WRITE.
244
+ - react-three-fiber and drei. The 3D layer runs on plain three.js under an engine-owned frame loop (createEngineLoop, EngineCanvas, useEngineDraw); bun run check r3f-free keeps them out
245
+ - `@jgengine/core/anim/PartMotion.draw` no longer exports `PartMotionRig`.
246
+ - `@jgengine/core/buildings/GeneratedBuilding.draw` no longer exports `BuildingBlock`, `BuildingBlockProps`.
247
+ - `@jgengine/core/material/authoredSurfaceMaterial.draw` no longer exports `AuthoredSurfaceMaterial`, `useAuthoredSurfaceMaterial`.
248
+ - `@jgengine/core/material/materialAsset.draw` no longer exports `MaterialAssetSurface`, `useMaterialAssetMaterial`.
249
+ - `@jgengine/core/material/surfaceScanTextures.draw` no longer exports `useSurfaceScan`.
250
+ - `@jgengine/core/material/triplanarMaterial.draw` no longer exports `TriplanarAssetMaterial`, `useTriplanarAssetMaterial`.
251
+ - `@jgengine/core/model/SceneModels.draw` no longer exports `MeasureTarget`.
252
+ - `@jgengine/core/model/useModelInstance.draw` no longer exports `ModelInstance`, `ModelInstanceConfig`, `ModelInstanceOptions`, `useModelInstance`.
253
+ - `@jgengine/core/scene/useDecalTexture.draw` no longer exports `useDecalTexture`.
254
+
29
255
  ## 0.54.0
30
256
 
31
257
  ### Migrate
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jgengine/convex",
3
- "version": "0.54.0",
3
+ "version": "0.55.1",
4
4
  "description": "Convex adapters for JGengine: game transport, presence transport, and backend wiring over @jgengine/core.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -30,7 +30,7 @@
30
30
  "check-types": "tsgo --noEmit -p tsconfig.json"
31
31
  },
32
32
  "dependencies": {
33
- "@jgengine/core": "^0.54.0"
33
+ "@jgengine/core": "^0.55.1"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "convex": "^1.31.2",
@@ -6,7 +6,7 @@
6
6
 
7
7
  - `CHANGELOG` (const): const CHANGELOG: Record<string, ChangelogEntry> — Per-version engine changelog keyed by semver string (e.g. `"0.10.0"`).
8
8
  - `ChangelogEntry` (interface): interface ChangelogEntry — One release's migrate steps plus added/changed/removed notes (typed mirror of CHANGELOG.md).
9
- - `VERSION` (const): const VERSION: "0.54.0" — Installed `@jgengine/core` semver - compare against {@link CHANGELOG} keys when migrating.
9
+ - `VERSION` (const): const VERSION: "0.55.1" — Installed `@jgengine/core` semver - compare against {@link CHANGELOG} keys when migrating.
10
10
 
11
11
  ## @jgengine/core/authoring
12
12
 
@@ -143,6 +143,7 @@
143
143
  - `AgentBridgeRequest` (type): type AgentBridgeRequest = { method: string } & Record<string, unknown> — One RPC call into the agent bridge: a verb name plus its verb-specific fields.
144
144
  - `AgentBridgeResponse` (type): type AgentBridgeResponse = | { ok: true; [key: string]: unknown } | { ok: false; error: string } — Result of an agent bridge call: `ok: true` with verb payload, or `ok: false` with a reason.
145
145
  - `CAPTURE_COMMAND_INPUT` (const): const CAPTURE_COMMAND_INPUT: { readonly yaw: 0; readonly pitch: 0; readonly aim: { readonly yaw: 0; readonly pitch: 0; }; } — The input a capture command named by a bare string runs with: a neutral look, so a command that reads an aim or heading gets one. Shared by the dev runner's capture mode and `capture_run`.
146
+ - `applyTimeScale` (function): function applyTimeScale(ctx: Pick<GameContext, "time">, scale: unknown): AgentBridgeResponse — ⚠ undocumented
146
147
 
147
148
  ## @jgengine/core/devtools/collisionDebug
148
149
 
@@ -285,8 +286,12 @@
285
286
  - `FrameBreakdown` (interface): interface FrameBreakdown — A running breakdown, as `installFrameBreakdown` returns it and `window.__jgFrameBreakdown` holds it.
286
287
  - `FrameBreakdownRecord` (interface): interface FrameBreakdownRecord — One frame, as the hooks saw it. GPU times arrive a few frames late; `gpu` is null until they do.
287
288
  - `FrameBreakdownSummary` (interface): interface FrameBreakdownSummary — A window of frames, summarized for a person or a diff.
289
+ - `GpuTiming` (type): type GpuTiming = "span" | "draws" — How the breakdown times the GPU. - `span`: one timer around each pass. Cheap, but it also counts the time the GPU sat idle waiting for the CPU to submit the pass's commands, so a pass the CPU submits slowly reads as GPU time. - `draws`: one timer around every draw and clear, summed into its pass. That is the work the GPU did; the timers themselves cost CPU, so frame times in such a window are not the player's.
288
290
  - `HITCH_MS` (const): const HITCH_MS: 33 — Where each frame goes: the record of one frame (CPU per system, GPU per render pass, draws, shader compiles, uploads, heap) and the summary of a window of them (percentiles, the costliest systems, passes, objects and materials, and every hitch with what caused it). This file is the arithmetic; `frameBreakdown.draw.ts` fills the records from a live renderer.
289
291
  - `Hitch` (interface): interface Hitch — A frame over {@link HITCH_MS}, with the cause read from what happened inside it.
292
+ - `HitchScript` (interface): interface HitchScript — One script the browser ran inside a slow frame, as its long-animation-frame timing names it.
293
+ - `HitchSession` (interface): interface HitchSession — Every hitch of a play session, not only the frames still in the breakdown's window.
294
+ - `LayoutAttribute` (interface): interface LayoutAttribute — A vertex attribute as three keeps it: plain, or interleaved (its array on `data`).
290
295
  - `NamedCost` (interface): interface NamedCost — A named cost over the window: its average per frame and its 95th percentile.
291
296
  - `Spread` (interface): interface Spread — Percentiles of one series.
292
297
 
@@ -300,6 +305,7 @@
300
305
  - `FrameSampler` (interface): interface FrameSampler — The installed sampler: ask it for a report, reset its window, or tear it down.
301
306
  - `FrameTelemetrySources` (interface): interface FrameTelemetrySources — What the shell hands the sampler: live getters, called only when a report is built.
302
307
  - `GlCounterTotals` (interface): interface GlCounterTotals — GPU-object creation counts, the fields the budget gate compares.
308
+ - `REPORT_PROBE_TIMEOUT_MS` (const): const REPORT_PROBE_TIMEOUT_MS: 30000 — How long the page waits for the dev server's report route before running without telemetry.
303
309
  - `REPORT_PUSH_MS` (const): const REPORT_PUSH_MS: 1000 — Cadence of the push to the dev server, in milliseconds.
304
310
  - `TELEMETRY_FLAG` (const): const TELEMETRY_FLAG: "telemetry" — In-page frame telemetry: the game reports on itself instead of needing a second process to attach a profiler. Frame timings come from the devtools frame ring, renderer counts from the live renderer, GPU-object creation from wrapped GL calls, and per-system numbers from `devtools.probes` — all read on demand, so nothing here runs on the hot path.
305
311
  - `TELEMETRY_WARMUP_MS` (const): const TELEMETRY_WARMUP_MS: 5000 — How long after the sampler starts the GL counters get their post-warmup baseline.
@@ -342,6 +348,14 @@
342
348
  - `PerfOverlay` (function): function PerfOverlay({ sample }: { sample: PerfOverlaySample }): ReactElement — The F3 performance overlay every game gets: an FPS graph of the last frames, draws, triangles, scene nodes, linked programs and the costliest subsystems from the devtools frame breakdown. Pair it with {@link PerfOverlayProbe} inside the Canvas, sharing one sample.
343
349
  - `PerfOverlaySample` (interface): interface PerfOverlaySample — What the overlay draws, filled in place by {@link PerfOverlayProbe} and read by {@link PerfOverlay}.
344
350
 
351
+ ## @jgengine/core/devtools/renderCounter
352
+
353
+ - `ComponentRenders` (interface): interface ComponentRenders — Which React components rendered, and how often: the evidence that a changing value re-renders only its own leaf. It stands in for React DevTools' global hook, so it has to be installed before React loads: a page gets it as a script ahead of its own (`bun run drive --renders`), and every `bun test` gets it from the test preload, so a test can count renders of a real game HUD.
354
+ - `RenderCounter` (interface): interface RenderCounter — The installed counter, at `globalThis.__jgRenders`.
355
+ - `RenderCounts` (interface): interface RenderCounts — Every commit and component render since the counter was installed or reset.
356
+ - `installRenderCounter` (function): function installRenderCounter(scope: { __REACT_DEVTOOLS_GLOBAL_HOOK__?: unknown; __jgRenders?: RenderCounter | undefined }): RenderCounter | null — Install the counter as React's devtools hook on `scope`. A component fiber rendered in a commit when it mounted or carries React's PerformedWork flag; a subtree whose child pointer did not change bailed out whole and is not walked, the same test React DevTools uses. Returns null when another devtools hook was there first. Self-contained, so a harness can ship its source to a page.
357
+ - `renderCounter` (function): function renderCounter(): RenderCounter | null — The counter the test preload or the page script installed, or null when React loaded first.
358
+
345
359
  ## @jgengine/core/devtools/report
346
360
 
347
361
  - `FrameWindow` (interface): interface FrameWindow — Frame timings over the measured window, as the player sees them.
@@ -447,7 +461,7 @@
447
461
 
448
462
  - `CHANGELOG` (const): const CHANGELOG: Record<string, ChangelogEntry> — Per-version engine changelog keyed by semver string (e.g. `"0.10.0"`).
449
463
  - `ChangelogEntry` (interface): interface ChangelogEntry — One release's migrate steps plus added/changed/removed notes (typed mirror of CHANGELOG.md).
450
- - `VERSION` (const): const VERSION: "0.54.0" — Installed `@jgengine/core` semver - compare against {@link CHANGELOG} keys when migrating.
464
+ - `VERSION` (const): const VERSION: "0.55.1" — Installed `@jgengine/core` semver - compare against {@link CHANGELOG} keys when migrating.
451
465
 
452
466
  ## @jgengine/core/runtime/adapter
453
467
 
@@ -717,6 +731,15 @@
717
731
  - `PredictionStep` (type): type PredictionStep<S> = (state: S, input: InputFrame, dt: number) => S — Deterministic state transition used to advance one predicted input frame.
718
732
  - `createPredictionBuffer` (function): function createPredictionBuffer<S>(config: { initial: S; step: PredictionStep<S>; maxTicks: number; startTick?: number; dt?: number; /** Error above which a caller should snap its rendered pose to the reconciled result. */ snapThreshold?: number; }): PredictionBuffer<S> — Keeps local input predictions ahead of the last server confirmation. Reconciliation replaces the confirmed state and deterministically replays unconfirmed inputs through the supplied step function. `maxTicks` bounds retained work and memory; dropped inputs are folded into the confirmed baseline.
719
733
 
734
+ ## @jgengine/core/runtime/simAhead
735
+
736
+ - `IdleTask` (interface): interface IdleTask — A task that runs once as its own macrotask after the current one, however often it is requested before then.
737
+ - `RecordedTime` (interface): interface RecordedTime — Frame times for a replay of a loop that steps ahead. A step run between frames is recorded as a frame of its own whose time makes the step fall due; the frames after it give that time back, so a plain loop fed the recorded times runs every step in the same recorded frame as the live one did.
738
+ - `STEP_AHEAD_MAX_FRAME_SHARE` (const): const STEP_AHEAD_MAX_FRAME_SHARE: 0.5 — Frames shorter than this share of a fixed step get their next step run ahead, between frames. Above it (under 120 fps at 60 Hz) most frames carry a step anyway, so running one early would only sample input sooner without taking a spike off any frame.
739
+ - `createIdleTask` (function): function createIdleTask(run: () => void): IdleTask — Run `run` in its own task right after the current one: after a frame's callbacks and its commit, before the next frame's. A `MessageChannel` message is used where one exists, since timers are clamped and throttled.
740
+ - `createRecordedTime` (function): function createRecordedTime(): RecordedTime — Create the {@link RecordedTime} ledger for one recording.
741
+ - `shouldStepAhead` (function): function shouldStepAhead(loop: Pick<SimLoop, "untilStep" | "stepSeconds">, frameDt: number): boolean — Whether the frame after one that took `frameDt` real seconds will owe a fixed step, so the driver can run that step now, in the time the main thread waits for the next frame, instead of inside it. The prediction is that the next frame takes as long as this one; when it is shorter, {@link SimLoop.advance} still counts the early step and owes the difference, so the world a frame shows is at most that jitter early.
742
+
720
743
  ## @jgengine/core/runtime/simContext
721
744
 
722
745
  - `SimContext` (interface): interface SimContext — `ctx.sim`: the simulation stepper shared by every driver plus the render-side interpolation it enables. Drivers call {@link advance} with real elapsed seconds and do their per-step work inside the callback; renderers call {@link renderPose} each frame.
@@ -838,7 +861,7 @@
838
861
 
839
862
  ## @jgengine/shell/gameKit
840
863
 
841
- - `Clock` (function): function Clock({ format = "24h", showDay = true, controls = false, style, className, }: { format?: "24h" | "12h"; showDay?: boolean; controls?: boolean; style?: CSSProperties; className?: string; }): React.JSX.Element — A time-of-day clock reading the sim calendar — `Day N · HH:MM`, 24h or 12h. `controls` adds pause + the game's speed multipliers as clickable pills (the "fast-forward" bar), off by default so a game opts into letting the player scrub time.
864
+ - `Clock` (const): const Clock: React.NamedExoticComponent<{ format?: "24h" | "12h" | undefined; showDay?: boolean | undefined; controls?: boolean | undefined; style?: React.CSSProperties | undefined; className?: string | undefined; }> — A time-of-day clock reading the sim calendar — `Day N · HH:MM`, 24h or 12h. `controls` adds pause + the game's speed multipliers as clickable pills (the "fast-forward" bar), off by default so a game opts into letting the player scrub time.
842
865
  - `Coins` (function): function Coins({ currencyId, icon = "🪙", style, className, }: { currencyId: string; icon?: ReactNode; style?: CSSProperties; className?: string; }): React.JSX.Element — A currency counter — an icon (emoji/char, default a coin) plus the live amount for `currencyId`. *
843
866
  - `DEFAULT_DRIVE_CODES` (const): const DEFAULT_DRIVE_CODES: { readonly throttle: readonly ["KeyW", "ArrowUp"]; readonly brake: readonly ["KeyS", "ArrowDown"]; readonly steerLeft: readonly ["KeyA", "ArrowLeft"]; readonly steerRight: readonly ["KeyD", "ArrowRight"]; readonly handbrake: readonly ["Space"]; } — Stock keyboard codes for driving a vehicle: WASD or the arrows for throttle, brake and steering, Space for the handbrake. Spread it as the base of a racing map and override what the game uses differently (`input: { ...DEFAULT_DRIVE_CODES, handbrake: ["KeyX"], hop: ["Space"] }`).
844
867
  - `DEFAULT_WALK_CODES` (const): const DEFAULT_WALK_CODES: { readonly moveForward: readonly ["KeyW"]; readonly moveBack: readonly ["KeyS"]; readonly moveLeft: readonly ["KeyA"]; readonly moveRight: readonly ["KeyD"]; readonly jump: readonly ["Space"]; } — Stock keyboard codes for the shell walk controller: WASD to move, Space to jump. Spread it as the base of a game's map (`input: { ...DEFAULT_WALK_CODES, sprint: ["ShiftLeft"], interact: ["KeyE"] }`) instead of re-typing the same five bindings in every game.
@@ -851,10 +874,10 @@
851
874
  - `GameHost` (function): function GameHost({ playable, gameId, wsUrl, multiplayer, resolveMultiplayer, editor, creator, creatorOpen = false, onCreatorOpenChange }: GameHostProps): React.JSX.Element — The one documented mount: resolves multiplayer for the playable and renders the shell. With the `editor` loader prop it also owns the whole editor summon (F2+E, `?mode=editor`, dev save endpoint).
852
875
  - `GameHostProps` (interface): interface GameHostProps — Props for {@link GameHost}: the playable, optional multiplayer overrides, and the optional editor loader that enables the engine-owned F2+E summon.
853
876
  - `GameLoop` (interface): interface GameLoop<TContext = unknown> — Lifecycle hooks a game implements to drive init, per-tick simulation, and player join/leave.
854
- - `Hotbar` (function): function Hotbar({ inventoryId, activeSlot, keys, slotSize = 46, itemIcon, style, className, }: { inventoryId: string; activeSlot?: number; keys?: readonly string[]; slotSize?: number; /** Caller-supplied item id → icon registry; return null/undefined to fall back to the default glyph. */ itemIcon?: … — A numbered hotbar bound to an inventory — painted iconed slots (a `GameIcon` glyph over a school-keyed gradient with a count badge, #1035), an active-slot highlight, and a keycap per slot. Place it and pass the `inventoryId`; `activeSlot` highlights the equipped one. Supply `itemIcon` to map your item ids to your own glyphs — the default resolves a `GameIcon` from the item id.
855
- - `HudCanvas` (function): function HudCanvas({ layout, editChord, showDuring, className, style, children, }: { layout: HudLayoutStore; editChord?: HudEditChord | false; /** Opt-in play-phase gate: render the HUD only during these phases (e.g. `["playing"]`). Omit for always-visible (default). */ showDuring?: readonly GamePha… — Full-viewport HUD surface. Each `HudPanel` names an anchor and a priority; the canvas measures every panel and places them all at once so none lands on another, at any screen size. Panels sharing an anchor stack outward from that edge; a panel that would cover a neighbour slides along its edge to the nearest free spot; when the screen is too small, the lowest-priority panels fold into a toggle, and only then does the whole HUD shrink. Device safe areas and every touch control (any region registered as kind `control` or `system`) are kept clear. Games declare panels, never place them per size.
856
- - `HudFade` (function): function HudFade({ watch, idleMs = 3500, dimTo = 0.18, hold = false, children, className, style }: HudFadeProps): ReactNode — A HUD group that steps back when nothing is happening: it fades after a quiet spell and comes back the moment a watched value changes, so the world fills the screen and the HUD speaks only when it has news. Hovering it also brings it back.
857
- - `HudPanel` (function): function HudPanel({ id, anchor = "top-left", order, chip, interactive, inset, locked, showDuring, priority, mobileBehavior, allowOverlapWith, collisionGroup, region = true, width, height, type, className, style, children, }: { id: string; anchor?: HudAnchor; /** Stack position within the anchor, asc… — A HUD block anchored to a corner, edge or the centre. `HudCanvas` places every panel together: panels sharing an anchor stack outward from the screen edge in ascending `order`, and a panel that would cover another slides to the nearest free spot. When the screen is too small, lower-`priority` panels fold into a toggle first. On fine pointers panels stay draggable through the edit chord; a dragged panel leaves the flow and keeps its custom placement.
877
+ - `Hotbar` (const): const Hotbar: React.NamedExoticComponent<{ inventoryId: string; activeSlot?: number | undefined; keys?: readonly string[] | undefined; slotSize?: number | undefined; itemIcon?: ((itemId: string) => React.ReactNode) | undefined; style?: React.CSSProperties | undefined; className?: string | undefined;… — A numbered hotbar bound to an inventory — painted iconed slots (a `GameIcon` glyph over a school-keyed gradient with a count badge, #1035), an active-slot highlight, and a keycap per slot. Place it and pass the `inventoryId`; `activeSlot` highlights the equipped one. Supply `itemIcon` to map your item ids to your own glyphs — the default resolves a `GameIcon` from the item id.
878
+ - `HudCanvas` (const): const HudCanvas: React.NamedExoticComponent<{ layout: HudLayoutStore; editChord?: false | HudEditChord | undefined; showDuring?: readonly GamePhase[] | undefined; className?: string | undefined; style?: React.CSSProperties | undefined; children?: React.ReactNode; }> — Full-viewport HUD surface. Each `HudPanel` names an anchor and a priority; the canvas measures every panel and places them all at once so none lands on another, at any screen size. Panels sharing an anchor stack outward from that edge; a panel that would cover a neighbour slides along its edge to the nearest free spot; when the screen is too small, the lowest-priority panels fold into a toggle, and only then does the whole HUD shrink. Device safe areas and every touch control (any region registered as kind `control` or `system`) are kept clear. Games declare panels, never place them per size.
879
+ - `HudFade` (const): const HudFade: React.NamedExoticComponent<HudFadeProps> — A HUD group that steps back when nothing is happening: it fades after a quiet spell and comes back the moment a watched value changes, so the world fills the screen and the HUD speaks only when it has news. Hovering it also brings it back. It reads `watch` and `hold` every frame and sets its own opacity, so it never re-renders, and neither do its children, for a value it watches.
880
+ - `HudPanel` (const): const HudPanel: React.NamedExoticComponent<{ id: string; anchor?: HudAnchor | undefined; order?: number | undefined; chip?: string | undefined; interactive?: boolean | undefined; inset?: { x: number; y: number; } | undefined; locked?: boolean | undefined; showDuring?: readonly GamePhase[] | undefine… — A HUD block anchored to a corner, edge or the centre. `HudCanvas` places every panel together: panels sharing an anchor stack outward from the screen edge in ascending `order`, and a panel that would cover another slides to the nearest free spot. When the screen is too small, lower-`priority` panels fold into a toggle first. On fine pointers panels stay draggable through the edit chord; a dragged panel leaves the flow and keeps its custom placement.
858
881
  - `HudSlots` (function): function HudSlots({ storageKey, slots = {}, panels = {}, overlay, showDuring = DEFAULT_SLOT_PHASES, className }: HudSlotsProps): ReactNode — The play HUD in one line: a few small panels pinned to the screen's edges, scaled from a 1600x900 design to any window and movable by the player (the layout is remembered per `storageKey`). Keep it to a handful of slots; the world is the picture.
859
882
  - `PlayableGame` (type): type PlayableGame<TProbe extends ProbeReadings = ProbeReadings> = PlayableGameContract< ComponentType, ComponentType, RenderEntity, RenderObject, ComponentType<ViewmodelProps>, ComponentType<WorldOverlayProps>, TProbe, ComponentType<RemotePlayersProps> > & { /** HUD theme the shell applies at the `G… — The concrete React-bound playable a shell `defineGame` returns and `GameHost` mounts — the one PlayableGame games handle. `TProbe` is what the game's `capture.probe` returns, so a proof flow over `typeof game` names only real probes.
860
883
  - `StoreHandle` (interface): interface StoreHandle<T> — A typed, cast-free handle onto one slot of the reactive game store (`ctx.game.store`). The single type parameter is fixed at definition; `read`/`write`/`update` never widen to `unknown`, so no call site repeats `store.get(key) as T`. Writes flow through the engine store, so they bump `ctx.version()`, serialize into a {@link WorldSnapshot}, and replay under host authority — the same guarantees a hand-rolled `store.get`/`store.set` pair gives up the moment it forks run state into a module-level singleton.
@@ -110,11 +110,11 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
110
110
 
111
111
  ## hud-canvas — the HUD root surface — design-resolution scaling plus live panel-placement editing (F2+C)
112
112
 
113
- - `HudCanvas` (function) · `import { HudCanvas } from "@jgengine/shell/gameKit"`
113
+ - `HudCanvas` (const) · `import { HudCanvas } from "@jgengine/shell/gameKit"`
114
114
 
115
115
  ## hud-clock — in-game time-of-day readout bound to the sim clock
116
116
 
117
- - `Clock` (function) · `import { Clock } from "@jgengine/shell/gameKit"`
117
+ - `Clock` (const) · `import { Clock } from "@jgengine/shell/gameKit"`
118
118
 
119
119
  ## hud-coins — currency counter widget bound to a currency id
120
120
 
@@ -122,11 +122,11 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
122
122
 
123
123
  ## hud-fade — HUD group that fades when idle and returns when its values change
124
124
 
125
- - `HudFade` (function) · `import { HudFade } from "@jgengine/shell/gameKit"`
125
+ - `HudFade` (const) · `import { HudFade } from "@jgengine/shell/gameKit"`
126
126
 
127
127
  ## hud-hotbar — optional inventory hotbar building block — games own slot chrome
128
128
 
129
- - `Hotbar` (function) · `import { Hotbar } from "@jgengine/shell/gameKit"`
129
+ - `Hotbar` (const) · `import { Hotbar } from "@jgengine/shell/gameKit"`
130
130
 
131
131
  ## hud-layout — persistent, live-editable HUD panel layout state for `HudCanvas`
132
132
 
@@ -134,7 +134,7 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
134
134
 
135
135
  ## hud-panel — anchored HUD slot — drop any widget in a named, player-movable panel
136
136
 
137
- - `HudPanel` (function) · `import { HudPanel } from "@jgengine/shell/gameKit"`
137
+ - `HudPanel` (const) · `import { HudPanel } from "@jgengine/shell/gameKit"`
138
138
 
139
139
  ## hud-slots — a play HUD of edge-pinned, player-movable panels in one component
140
140