@jgengine/convex 0.53.0 → 0.55.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,491 @@ 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.0
|
|
30
|
+
|
|
31
|
+
### Migrate
|
|
32
|
+
|
|
33
|
+
- **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`.
|
|
34
|
+
- HudFade watch and hold, and useHudAwake, take getters read every frame (watch={() => [air, suit]}, hold={() => alarm})
|
|
35
|
+
- useGameStoreLive is removed; use useGameStore (now shallow by default) or LiveText/useFrameBind for a value that changes every frame
|
|
36
|
+
- useStore and useGameStore compare selections shallowly by default; a selector returning a fresh object or array no longer re-renders every tick
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
- `FlatWalkRenderer` (`@jgengine/core/render/flatSceneWalk.draw`): The renderer parts the flat walk wraps: three's render lists and shadow pass, and its render counter.
|
|
41
|
+
- `SceneWalkStarts` (`@jgengine/core/visibility/culledTransforms.draw`): The walk starts the scene's last matrix pass found; `serial` changes with every pass.
|
|
42
|
+
- `IdSet` (`@jgengine/core/visibility/visibilitySystem`): A read-only set of ids: membership, count and iteration. Also `RenderableSource`.
|
|
43
|
+
- `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`.
|
|
44
|
+
- 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
|
|
45
|
+
- 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
|
|
46
|
+
- 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
|
|
47
|
+
- A page's `?realism=` sets every grass patch's realism, so any scene can be shot or profiled at any realism (`--param realism=1`)
|
|
48
|
+
- `scrambledSobol2` (`@jgengine/core/random/scrambledSobol`): an even 2D spread where every prefix is even and none falls into rows
|
|
49
|
+
- 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
|
|
50
|
+
- 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
|
|
51
|
+
- 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
|
|
52
|
+
- Grass style `outline`: dark ink along each blade's edges so clumps read as outlined shapes, on toward cartoon and settable by hand anywhere
|
|
53
|
+
- 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
|
|
54
|
+
- 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
|
|
55
|
+
- 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
|
|
56
|
+
- `PostAuditStats` (`@jgengine/core/postfx/postAudit.draw`): What the changed draws did and, with the audit on, what comparing them with three's draws found.
|
|
57
|
+
- 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
|
|
58
|
+
- `GpuTiming` (`@jgengine/core/devtools/frameBreakdown`): How the breakdown times the GPU.
|
|
59
|
+
- `ShadowPassPlan` (`@jgengine/core/lighting/shadowCache`): Same-image shadow caching, the renderer-agnostic half: what one light's depth pass draws this frame.
|
|
60
|
+
- useSceneSwap and useSceneSwapping change place without a frozen frame, building the new place in the background behind a travel card
|
|
61
|
+
- prepareBeforeLink patches a lazily patched material (wet ground) before its first link, so a model that streams in mid-play compiles once
|
|
62
|
+
- `HitchScript` (`@jgengine/core/devtools/frameBreakdown`): One script the browser ran inside a slow frame, as its long-animation-frame timing names it. Also `HitchSession`.
|
|
63
|
+
- `installRenderCounter` (`@jgengine/core/devtools/renderCounter`): Install the counter as React's devtools hook on `scope`. Also `ComponentRenders`, `RenderCounter`, `RenderCounts`, `renderCounter`.
|
|
64
|
+
- `useSceneSwap` (`@jgengine/core/render/sceneSwap.draw`): Change place (a dive, a lift, a district, a level) without a frozen frame.
|
|
65
|
+
- `LiveTextTag` (`@jgengine/core/ui/liveBind.view`): Inline and block elements a live text can be.
|
|
66
|
+
- `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`.
|
|
67
|
+
- `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.
|
|
68
|
+
- `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`.
|
|
69
|
+
- `requestWebGpuDevice` (`@jgengine/core/webgpu/webgpuGl.draw`): Request the WebGPU device, or `null` when this browser has no usable WebGPU adapter. Also `WebGpuDevice`.
|
|
70
|
+
- 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
|
|
71
|
+
- 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
|
|
72
|
+
- 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
|
|
73
|
+
- 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
|
|
74
|
+
- 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
|
|
75
|
+
- `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`.
|
|
76
|
+
- `drawCharacterMotion` (`@jgengine/core/anim/characterMotion.draw`): The resolved parts of the default character motion for draw code. Also `noteGameCharacterMotion`.
|
|
77
|
+
- `CorpseRagdolls` (`@jgengine/core/anim/CorpseRagdolls.draw`): ⚠ undocumented Also `CorpseRagdollsDrawProps`.
|
|
78
|
+
- `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`.
|
|
79
|
+
- `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.
|
|
80
|
+
- `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`.
|
|
81
|
+
- `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.
|
|
82
|
+
- `buildingChunkBudgetScratch` (`@jgengine/core/buildings/BuildingRenderBudget.draw`): ⚠ undocumented
|
|
83
|
+
- `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.
|
|
84
|
+
- `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`.
|
|
85
|
+
- `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.
|
|
86
|
+
- `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`.
|
|
87
|
+
- `firstPersonBodyInstance` (`@jgengine/core/camera/firstPersonBody.draw`): The entity whose body the first-person camera sits in, for draw code. Also `noteFirstPersonBody`.
|
|
88
|
+
- `GameFirstPersonCameraDrawProps` (`@jgengine/core/camera/GameFirstPersonCamera.draw`): Everything the first-person draw reads, resolved by the React wrapper.
|
|
89
|
+
- `GameOrbitCameraDrawProps` (`@jgengine/core/camera/GameOrbitCamera.draw`): Everything the orbit draw reads each frame, resolved by the React wrapper.
|
|
90
|
+
- `SplitScreenRendererProps` (`@jgengine/core/camera/Viewports.draw`): ⚠ undocumented
|
|
91
|
+
- `ImpactFeedbackDrawProps` (`@jgengine/core/combat/ImpactFeedback.draw`): Props the impact draw reads; resolved by the React wrapper each commit.
|
|
92
|
+
- `HitMarkersDrawProps` (`@jgengine/core/combat/ShotFeedback.draw`): ⚠ undocumented Also `MuzzleFlashesDrawProps`.
|
|
93
|
+
- `dataObjectsDraw` (`@jgengine/core/data/DataObjects.draw`): The instanced-box path as an {@link EngineDraw}; `renderItem` stays on the React wrapper.
|
|
94
|
+
- `groundPadDraw` (`@jgengine/core/environment/GroundPad.draw`): A flat circular or rectangular pad resting on a terrain field.
|
|
95
|
+
- `TerrainGroundDrawProps` (`@jgengine/core/environment/terrainGround.draw`): Props for {@link terrainGroundDraw}: the terrain descriptor, live field, optional island center and sculpt snapshot.
|
|
96
|
+
- `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.
|
|
97
|
+
- `flushAfterRenderTasks` (`@jgengine/core/frame/afterRenderBoard.draw`): Run every task joined with {@link joinAfterRenderTask}, in join order. Also `flushBeforeRenderTasks`, `joinAfterRenderTask`, `joinBeforeRenderTask`.
|
|
98
|
+
- `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`.
|
|
99
|
+
- `EngineHost` (`@jgengine/core/frame/engineHost.draw`): The render host a React subtree draws through: the engine loop's provider.
|
|
100
|
+
- `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`.
|
|
101
|
+
- `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`.
|
|
102
|
+
- `clearLoadDrawCache` (`@jgengine/core/frame/loadDraw.draw`): Drop every cached load (tests, scene teardown). Also `LoadDrawOptions`, `loadDraw`, `loadDrawSystem`.
|
|
103
|
+
- `createTestEngineHost` (`@jgengine/core/frame/testEngineHost.draw`): ⚠ undocumented Also `MountedTestDraws`.
|
|
104
|
+
- `disposeObjectTree` (`@jgengine/core/frame/useEngineDraw.draw`): Dispose every geometry and material under `root` — for demo/preview object trees built outside draws. Also `DrawTransform`.
|
|
105
|
+
- `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.
|
|
106
|
+
- `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.
|
|
107
|
+
- `grassPatchDraw` (`@jgengine/core/grass/GrassPatch.draw`): The engine's one grass renderer: a camera-streamed blade field over a turf mat.
|
|
108
|
+
- `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`.
|
|
109
|
+
- `staticShapeBatchDraw` (`@jgengine/core/instancing/staticShapeInstances.draw`): One instanced batch as an {@link EngineDraw}.
|
|
110
|
+
- `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.
|
|
111
|
+
- `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`.
|
|
112
|
+
- `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.
|
|
113
|
+
- `markerAccess` (`@jgengine/core/map/map.view`): Non-hook access to a marker collection's subscribe/snapshot pair, for draw factories. Also `MarkerAccess`.
|
|
114
|
+
- `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.
|
|
115
|
+
- `hairCardsDraw` (`@jgengine/core/material/hairCards.draw`): ⚠ undocumented
|
|
116
|
+
- `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`.
|
|
117
|
+
- `surfaceMaterialPreviewDraw` (`@jgengine/core/material/surfaceMaterialPreview.draw`): ⚠ undocumented
|
|
118
|
+
- `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`.
|
|
119
|
+
- `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.
|
|
120
|
+
- `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`.
|
|
121
|
+
- `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.
|
|
122
|
+
- `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`.
|
|
123
|
+
- `naturePreviewDraw` (`@jgengine/core/nature/naturePreview.draw`): ⚠ undocumented
|
|
124
|
+
- `instancedJointsDraw` (`@jgengine/core/physics/InstancedJoints.draw`): Debug overlay drawing a PhysicsWorld's joints (suspension, ragdoll links, carry tethers) as one LineSegments batch.
|
|
125
|
+
- `placementGhostDraw` (`@jgengine/core/placement/PlacementGhost.draw`): Cursor-following build ghost: valid/invalid tint from a placement controller preview.
|
|
126
|
+
- `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`.
|
|
127
|
+
- `spriteBatchDraw` (`@jgengine/core/render/SpriteBatch.draw`): Renders a sprite sheet / tile atlas as a single InstancedMesh — one draw call for the whole batch.
|
|
128
|
+
- `tileLayerRendererDraw` (`@jgengine/core/render/TileLayerRenderer.draw`): Renders a tilemap as one instanced quad mesh with atlas UV rectangles.
|
|
129
|
+
- `RoadJunctionsProps` (`@jgengine/core/roads/RoadJunctions.draw`): Props for {@link RoadJunctions}.
|
|
130
|
+
- `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`.
|
|
131
|
+
- `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`.
|
|
132
|
+
- `instancedScatterDraw` (`@jgengine/core/scatter/InstancedScatter.draw`): Renders resolved {@link ScatterInstance}s grouped into per-chunk, per-species GPU-instanced draws that frustum-cull independently.
|
|
133
|
+
- `ScatterImpostorFieldDrawOptions` (`@jgengine/core/scatter/ScatterImpostorField.draw`): ⚠ undocumented
|
|
134
|
+
- `ProxyPolesSpec` (`@jgengine/core/scene/PoleLineRenderer.draw`): ⚠ undocumented
|
|
135
|
+
- `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.
|
|
136
|
+
- `SceneWarmupGateProps` (`@jgengine/core/scene/SceneWarmupGate.draw`): ⚠ undocumented
|
|
137
|
+
- `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.
|
|
138
|
+
- `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`.
|
|
139
|
+
- `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.
|
|
140
|
+
- `carvedTerrainDraw` (`@jgengine/core/terrain/CarvedTerrain.draw`): Renders a `TerrainField` as a deformed ground mesh — the crater/mound view for destructible terrain.
|
|
141
|
+
- `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.
|
|
142
|
+
- `proceduralGroundDraw` (`@jgengine/core/terrain/ProceduralGround.draw`): A seeded noise-height ground mesh coloured from low to high ground.
|
|
143
|
+
- `RequiredTerrainMaterialInput` (`@jgengine/core/terrain/requiredTerrainMaterial.draw`): ⚠ undocumented
|
|
144
|
+
- `terraformBrushCursorDraw` (`@jgengine/core/terrain/TerraformBrushCursor.draw`): A ground ring showing where a terraform brush will act, coloured by its mode.
|
|
145
|
+
- `noteTerrainQuality` (`@jgengine/core/terrain/terrainQuality.draw`): The graphics tier for draw code, which has no React context. Also `terrainQuality`.
|
|
146
|
+
- `TerrainSurfacesInput` (`@jgengine/core/terrain/terrainSurfacesDriver.draw`): What a terrain surface bake needs: the resolved surface roles, paint-layer scans and graphics tier. Also `TerrainSurfacesSnapshot`.
|
|
147
|
+
- `TolerantTerrainMaterialInput` (`@jgengine/core/terrain/tolerantTerrainMaterial.draw`): ⚠ undocumented
|
|
148
|
+
- `RuneUi` (`@jgengine/core/ui/runeUi.view`): One mounted Rune UI layer. Also `RuneUiEvent`, `RuneUiFrame`, `RuneUiNode`, `RuneUiOptions`.
|
|
149
|
+
- `particleFieldDraw` (`@jgengine/core/vfx/ParticleField.draw`): One pooled simulation drawn as a point cloud or instanced quads, as an {@link EngineDraw}. Also `ParticleFieldDrawOptions`.
|
|
150
|
+
- `CullingProviderDrawProps` (`@jgengine/core/visibility/CullingProvider.draw`): ⚠ undocumented
|
|
151
|
+
- `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`.
|
|
152
|
+
- `dustFieldDraw` (`@jgengine/core/weather/DustField.draw`): Renders wind-borne particulate as camera-facing motes.
|
|
153
|
+
- `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`.
|
|
154
|
+
- `rainFieldDraw` (`@jgengine/core/weather/RainField.draw`): GPU-animated rain streaks around the camera, sheltered under authored roofs.
|
|
155
|
+
- `rainImpactFieldDraw` (`@jgengine/core/weather/RainImpactField.draw`): Bounded ground ripples sharing precipitation intensity and authoritative animation time.
|
|
156
|
+
- `snowFieldDraw` (`@jgengine/core/weather/SnowField.draw`): GPU-animated drifting snowflakes around the camera, sheltered under authored roofs.
|
|
157
|
+
- `markerAccess` (`@jgengine/react`): Non-hook access to a marker collection's subscribe/snapshot pair, for draw factories. Also `MarkerAccess`.
|
|
158
|
+
- WebGPU programs keep camera and light uniforms in a persistent buffer, uploading only the spans that changed each frame
|
|
159
|
+
- `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).
|
|
160
|
+
|
|
161
|
+
### Changed
|
|
162
|
+
|
|
163
|
+
- Background jobs started through Git Bash now write their output to the job log
|
|
164
|
+
- A scene that sets no style draws the same grass as before, with tufts in new places (the placement fix above)
|
|
165
|
+
- 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
|
|
166
|
+
- lane:close merges its own PR once its verify:fast receipt covers the pushed head; no landing queue and no PR CI
|
|
167
|
+
- A passing verify:fast --landing of the same tree is reused: lane:close skips preflight and verify in one line
|
|
168
|
+
- Timed runs share one machine-wide perf lock (scripts/perf-lock.ts), so two never overlap
|
|
169
|
+
- A release publishes even when beta moves while it waits
|
|
170
|
+
- 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
|
|
171
|
+
- Tally before/after benchmarks ride every lane close into the PR body
|
|
172
|
+
- WebGPU: correct boot and shadows, cheaper passes, MSAA resolved in its own pass, perf tooling (part of #3066)
|
|
173
|
+
- 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
|
|
174
|
+
- 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.
|
|
175
|
+
- 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
|
|
176
|
+
- # one-pr-split: first PR landed before the goal was met
|
|
177
|
+
- 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
|
|
178
|
+
- Fernwild's game.config.ts lists every import one name per line, and its prompt sources, systems and movement settings one per line
|
|
179
|
+
- 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.
|
|
180
|
+
- 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
|
|
181
|
+
- 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)
|
|
182
|
+
- 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.
|
|
183
|
+
- 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).
|
|
184
|
+
- 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.
|
|
185
|
+
- 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
|
|
186
|
+
- Capture tools report a dead browser daemon as a failure, restart it once, and the broker no longer dies with a fairshare slot
|
|
187
|
+
- 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)
|
|
188
|
+
- 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
|
|
189
|
+
- 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
|
|
190
|
+
- A session that starts on a lane whose AGENTS.md changed on beta since it branched is told to read beta's rules
|
|
191
|
+
|
|
192
|
+
### Fixed
|
|
193
|
+
|
|
194
|
+
- 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
|
|
195
|
+
- 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
|
|
196
|
+
- 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
|
|
197
|
+
- 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
|
|
198
|
+
- 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
|
|
199
|
+
- 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)
|
|
200
|
+
- `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"
|
|
201
|
+
- 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
|
|
202
|
+
- 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
|
|
203
|
+
- perf fps-gap and the perf audits take the perf lock before starting a game's dev server and browser
|
|
204
|
+
- the core API-surface test's budget fits a whole-package type walk on a loaded machine
|
|
205
|
+
- the subcommand test still expected perf gate's old 1500 s guard after the gate's guard became 5400 s
|
|
206
|
+
- 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
|
|
207
|
+
- 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)
|
|
208
|
+
- 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)
|
|
209
|
+
- 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
|
|
210
|
+
- 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)
|
|
211
|
+
- 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)
|
|
212
|
+
- 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
|
|
213
|
+
- 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
|
|
214
|
+
- the fernwild dry-throat test proves food outlasts water at the measured drain rates again
|
|
215
|
+
- lane:close keeps both of two blocks lanes added at one place whole when they open or close with the same lines
|
|
216
|
+
- 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
|
|
217
|
+
- 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
|
|
218
|
+
- 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
|
|
219
|
+
- 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
|
|
220
|
+
- 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)
|
|
221
|
+
- each canvas on a page runs its own frame tasks, so model previews and second viewports no longer draw with another canvas's camera
|
|
222
|
+
- a scene group keeps one group across renders, and a draw rebuilt on every render warns once
|
|
223
|
+
- the fernwild dry-throat test and the reference-car understeer tests no longer sit near their time limits
|
|
224
|
+
- the WebGPU pass recorder and vertex attribute copies stopped allocating per frame
|
|
225
|
+
|
|
226
|
+
### Removed
|
|
227
|
+
|
|
228
|
+
- `@jgengine/core/ui/liveBind.view` no longer exports `frameBindSubscriberCount`.
|
|
229
|
+
- `@jgengine/react` no longer exports `useGameStoreLive`.
|
|
230
|
+
- `@jgengine/react/hooks` no longer exports `useGameStoreLive`.
|
|
231
|
+
- `@jgengine/core/webgpu/engineRenderer.draw` no longer exports `engineRendererFactory`.
|
|
232
|
+
- `@jgengine/core/lighting/CascadedShadows.draw` no longer exports `CASCADE_DEPTH_STEP_TEXELS`.
|
|
233
|
+
- 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.
|
|
234
|
+
- 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
|
|
235
|
+
- `@jgengine/core/anim/PartMotion.draw` no longer exports `PartMotionRig`.
|
|
236
|
+
- `@jgengine/core/buildings/GeneratedBuilding.draw` no longer exports `BuildingBlock`, `BuildingBlockProps`.
|
|
237
|
+
- `@jgengine/core/material/authoredSurfaceMaterial.draw` no longer exports `AuthoredSurfaceMaterial`, `useAuthoredSurfaceMaterial`.
|
|
238
|
+
- `@jgengine/core/material/materialAsset.draw` no longer exports `MaterialAssetSurface`, `useMaterialAssetMaterial`.
|
|
239
|
+
- `@jgengine/core/material/surfaceScanTextures.draw` no longer exports `useSurfaceScan`.
|
|
240
|
+
- `@jgengine/core/material/triplanarMaterial.draw` no longer exports `TriplanarAssetMaterial`, `useTriplanarAssetMaterial`.
|
|
241
|
+
- `@jgengine/core/model/SceneModels.draw` no longer exports `MeasureTarget`.
|
|
242
|
+
- `@jgengine/core/model/useModelInstance.draw` no longer exports `ModelInstance`, `ModelInstanceConfig`, `ModelInstanceOptions`, `useModelInstance`.
|
|
243
|
+
- `@jgengine/core/scene/useDecalTexture.draw` no longer exports `useDecalTexture`.
|
|
244
|
+
|
|
245
|
+
## 0.54.0
|
|
246
|
+
|
|
247
|
+
### Migrate
|
|
248
|
+
|
|
249
|
+
- **Bump lockstep SDK packages to `^0.54.0`:** `@jgengine/{core,rapier,react,ws,node,sql,convex,shell,editor,assets,navbake}`. CLI `jgengine` is `0.51.0`; `@jgengine/github` is `0.41.0`.
|
|
250
|
+
- the grass meta group species (second species) is now mix; GrassCover's stripeAt prop is now shadeAt.
|
|
251
|
+
|
|
252
|
+
### Added
|
|
253
|
+
|
|
254
|
+
- `createDoseTable` (`@jgengine/core/stats/doseTable`): What each source does to each kind of receiver, as {@link VitalDose}s: a raptor's foods and how much taming, food and ineffectiveness each gives, a sim's meals and the needs they fill, a shooter's potions and the shield each pours in up to its cap, a dart's torpor by the body it lands in. Also `DoseTable`, `DoseTableOptions`, `DoseTableRows`, `hasRoomFor`.
|
|
255
|
+
- `createVitals` (`@jgengine/core/stats/vitals`): Survival vitals for one body — health, stamina, food, water, oxygen, torpor or anything the game names. Also `VitalDef`, `VitalDose`, `VitalFlag`, `VitalFlagChange`, `VitalLink`, `VitalRateModifier`, `Vitals`, `VitalsState` and 1 more.
|
|
256
|
+
- `importedPackNames` (`@jgengine/assets`): The original export names imported from `generated/packs`, including imports renamed locally.
|
|
257
|
+
- `importedPackNames` (`@jgengine/assets/manifest`): The original export names imported from `generated/packs`, including imports renamed locally.
|
|
258
|
+
- `LINGER_MS` (`@jgengine/core/run/lingering`): How long a resource whose last user left waits before it is freed. Also `Lingering`.
|
|
259
|
+
- `nextFrame` (`@jgengine/core/run/nextFrame.draw`): Resolves on the next animation frame, or a zero-delay timer in tests and SSR.
|
|
260
|
+
- `DECAL_CELL` (`@jgengine/core/scene/DecalRenderer.draw`): Edge in metres of the world cell one merged decal draw covers, so an unseen cell still culls whole.
|
|
261
|
+
- bun run perf <game> --profile also prints what allocates per second of play, the source of garbage collection pauses
|
|
262
|
+
- `SCENE_PROBE_REFRESH_MS` (`@jgengine/core/devtools/discover`): How often a probe that walks the whole scene walks it again; in between it answers its last reading.
|
|
263
|
+
- generator-grown scatter species bake the generator's coarser levels and switch to them by size on screen through visibility.meshLods (Fernwild 28M to 12.5M triangles a frame)
|
|
264
|
+
- `RoadPiece` (`@jgengine/core/roads/RoadRibbons.draw`): One road's share of a merged draw: its trimmed centreline sub-paths and the base height they drape at.
|
|
265
|
+
- Grass reference boards: `bun run shoot --board` shoots six credited photos beside our matching views, our games' stylised art beside our grass, the 18 grass game views and every species, with `--diff` to compare two boards and `--resume`/`--timeout` to finish a long board
|
|
266
|
+
- `--look-from` takes a fourth number, the ground clearance, so a capture can sit centimetres above the turf
|
|
267
|
+
- 128 named grass species (GRASS_SPECIES in @jgengine/core/grass/grassSpecies) across eleven habitats, from putting greens and prairie to reed beds, cereal crops, ornamentals, moss cushions and stylised fantasy grass. A grass_field or grass() names one with species and any knob it also sets wins.
|
|
268
|
+
- grass knobs for blade twist, tip droop, V-fold, tuft splay, moss cushion domes, variegated margins and cross bands, spike seed heads (headShape) and a resting wind lean.
|
|
269
|
+
- a param schema can name a presetKey; the meta id there backs every unset field (presetBackedMeta), and the editor inspector groups presets under headings and shows the current one.
|
|
270
|
+
- `presetBackedMeta` (`@jgengine/core/generator/paramSchema`): A `meta` bag with its {@link ParamSchema.presetKey} preset filled in under it: the preset's values, then the meta's own keys on top.
|
|
271
|
+
- `GrassSpeciesKnobs` (`@jgengine/core/grass/grassKind`): A knob set a species writes: any knobs, never the seed.
|
|
272
|
+
- `GRASS_SPECIES` (`@jgengine/core/grass/grassSpecies`): Every shipped grass species, grouped by habitat. Also `GrassSpecies`, `GrassSpeciesId`.
|
|
273
|
+
- Grass style: a species says what grows, a style says how it is drawn. `realism` (0 cartoon, 0.5 animated, 1 photoreal) moves every style slider along its own curve; the first slider is `normalLift`. A world sets one style (`environment.grassStyle`, the Lighting panel, or `environment({ grassStyle })`) and a patch may override it (`style` in a `grass_field` meta or a `grass()` config)
|
|
274
|
+
- The inspector shows a scene kind's nested settings with the values in effect and a Clear button; grass and ground cover use it for the patch style
|
|
275
|
+
- Grass look fingerprints: a test pins every look the repo draws (128 species, every game volume and band, demo and reference looks) to the values from before the split
|
|
276
|
+
- `MENU_PANE_STORE_KEY` (`@jgengine/core/game/gameFlow`): ⚠ undocumented Also `MenuPane`, `menuPane`, `showMenuPane`.
|
|
277
|
+
- swimming can dive: movement.swim.dive makes crouch swim down to the bed and jump swim up, with telemetry diveDepth
|
|
278
|
+
- Fernwild breath: oxygen drains while the head is under water, refills at the surface, and drowning takes health (survival.oxygen-swim)
|
|
279
|
+
- `SwimDiveConfig` (`@jgengine/core/movement/playerMovement`): Metres a second a diver swims down, swims up, and drifts up with no input.
|
|
280
|
+
- bun run perf fps-gap measures every game at the frame gate's conditions and prints each against 360 fps (2.78 ms) with its costs grouped into ranked engine work items; a report, not a gate
|
|
281
|
+
- a desktop build runs without the display's vsync with <Game>.exe --uncapped on Windows (WebView2); without the flag it keeps vsync
|
|
282
|
+
- bun run perf desktop --launch <app.exe> [--uncapped] measures a production desktop build in the game's scene at the gate's conditions
|
|
283
|
+
- the capture_run agent-bridge verb runs a game's capture.play or a capture state in a shipped build, skipping dev-only helpers on request
|
|
284
|
+
- `CAPTURE_COMMAND_INPUT` (`@jgengine/core/devtools/agentBridge.draw`): 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.
|
|
285
|
+
- `bun run perf crowd` times one crowd tick (neighbours within 1.5 m, separation, speed clamp, move, bounds; 0.5 agents/m²) written against createEntityStore + createSpatialIndex the way game code is, beside the same step over Float32Array columns and a counting-sort grid. Engine and flat ticks alternate so machine load hits both alike.
|
|
286
|
+
- useGameStoreLive re-renders a HUD only when what it draws changes and writes per-frame values through refs
|
|
287
|
+
- `MinimapChromeHandle` (`@jgengine/core/map/map.view`): Moves an already-drawn `MinimapChrome`'s markers in place.
|
|
288
|
+
- `useGameStoreLive` (`@jgengine/react`): Select from the game store at two speeds. Also `MinimapChromeHandle`.
|
|
289
|
+
- `useGameStoreLive` (`@jgengine/react/hooks`): Select from the game store at two speeds.
|
|
290
|
+
- bun run perf server, a repeatable load benchmark for the multiplayer host: N players in one room on the real createGameServerHost and createHostRouter, each subscribed to presence and posing every 100 ms of a fake clock that also drives the router's timers; one JSON line per room size (wall and CPU ms per game second, MB and messages out per game second, average message bytes), and a stderr note when a size cannot keep up in real time
|
|
291
|
+
- a run measures up to 100 steps and stops early after 5 s of measuring once it has 5, sizes stop at 256 players, and --max-seconds (default 60, warmup included) is the hard cap on one run; documented in README, the verify drive reference and the multiplayer skill; not a CI gate
|
|
292
|
+
- `store.columns`, the entity store's fast path for engine systems: resolve an id to an integer handle once (`columns.handleOf`), read pose from Float64Array position, velocity and rotation columns, and write by handle (`columns.setPosition`, `columns.setVelocity`) with the same constraint, derived velocity and notifications as `setPose`/`setVelocity`. Every write, by id or by handle, keeps the columns equal to the entity objects; despawned handles are reused with a bumped generation. Games keep using ids and never need it.
|
|
293
|
+
- `createPointGrid` (`@jgengine/core/spatial/pointGrid`), a per-tick counting-sort XZ neighbour grid over a typed-array position column that allocates nothing once warm and returns candidates in a fixed z-row, x, input order.
|
|
294
|
+
- `EntityColumns` (`@jgengine/core/scene/entityStore`): The store's fast path for engine systems that touch many entities per tick (crowds, flocks, pose interpolation, a server kernel): resolve an id to an integer handle once, then read pose straight from typed-array columns and write it by handle.
|
|
295
|
+
- `createPointGrid` (`@jgengine/core/spatial/pointGrid`): Build a {@link PointGrid}. Also `PointGrid`, `PointGridOptions`.
|
|
296
|
+
- `bindingOverrides` (`@jgengine/core/game/controlGate`): The player's rebinds in this game context: action to the codes that now drive it. Also `rebindAction`, `reboundActionCodes`, `setBindingOverrides`.
|
|
297
|
+
- `saveBindingOverrides` (`@jgengine/core/gameplay`): Store a game's whole rebind map (an empty one clears it), as the shell does whenever the game's rebinds change.
|
|
298
|
+
- `saveBindingOverrides` (`@jgengine/core/input/bindingOverrides`): Store a game's whole rebind map (an empty one clears it), as the shell does whenever the game's rebinds change.
|
|
299
|
+
- `solidEnclosure` (`@jgengine/core/world/overhead`): How shut in a point is: the share of the ways round it that meet a wall within reach, and 0 under open sky. Also `EnclosureOptions`.
|
|
300
|
+
- jgengine desktop --steam lays out a SteamPipe build beside the app (steam/content, steam/scripts with app_build_<appid>.vdf and depot_build_<depotid>.vdf, steam/output); the app id, depot id and live branch come from package.json "jgengine": { "steam": { ... } } or --steam-app, --steam-depot and --steam-branch, and a game without an app id still builds its content and is told the id is missing
|
|
301
|
+
- jgengine desktop --steamworks links the Steamworks API and a transparent surface Steam's overlay can draw into, ships steam_api64.dll in the depot, and forwards Shift+Tab from the page to the overlay
|
|
302
|
+
- jgengine desktop --no-installer builds only the game executable
|
|
303
|
+
- a desktop build's Frame rate limit setting offers Match display (V-Sync) and Uncapped; the choice is saved per player and applies on the next launch, and --uncapped still forces uncapped
|
|
304
|
+
- a desktop build's graphics settings gain Display mode (Windowed or Fullscreen): native borderless fullscreen that survives alt-tab, applied at once and remembered for the next launch
|
|
305
|
+
- `readDesktopShell`, `invokeDesktop`, `saveDesktopDisplayMode`, `saveDesktopFrameRate`, `desktopPresentMode`, `syncDesktopSettings`, `DISPLAY_MODE_OPTIONS` (`@jgengine/core/settings/desktopShell`): a desktop build's launch state and its commands; the settings store follows what the shell launched with
|
|
306
|
+
- `FRAME_RATE_LIMIT_UNCAPPED`, `frameRateLimitOptions` (`@jgengine/core/settings/frameRateLimit`): the frame-rate choices, with the desktop-only Uncapped option
|
|
307
|
+
- `SETTING_IDS.graphicsDisplayMode` (`@jgengine/core/settings/settingsModel`)
|
|
308
|
+
- bun run perf desktop --all [--uncapped] builds every game's production desktop app, measures each in turn and prints the 360 fps table; a report, not a gate
|
|
309
|
+
- every tame has a food meter; it eats what its diet takes from its own inventory and starves to death without (creatures.feed-starve)
|
|
310
|
+
- eatWhileRoom and DoseSupply in stats/doseTable, the shared 'eat from what you hold while there is room' step
|
|
311
|
+
- `eatWhileRoom` (`@jgengine/core/stats/doseTable`): Eat from `supply` while there is room: each time, the receiver's most preferred source it holds whose gain to `vital` fits under the max is taken and its doses go into `body`. Also `DoseSupply`.
|
|
312
|
+
- `CompanionStance` (`@jgengine/core/scene/companion`): What a tamed companion fights, wherever its {@link CompanionCommand} keeps it: - `passive` — never engages, even when struck (a pacifist escort).
|
|
313
|
+
- the creature panel says when a downed creature will wake
|
|
314
|
+
- `SaveStore.switchSlot(slot, { flush: false })` opens a slot without writing the current state first.
|
|
315
|
+
- Beacon's HUD says when a defense is not being saved, and its pause menu offers Withdraw without saving for it.
|
|
316
|
+
- The `reload-mid-run` Beacon flow raises a keep from the title, builds, starts wave 1, reloads the page and continues into the same defense.
|
|
317
|
+
- `HeadlessKeys` (`@jgengine/core/play/headlessKeys.draw`): Keys held against a headless {@link PlaySession}, mapped through the game's own bindings.
|
|
318
|
+
- `ContextMap` (`@jgengine/core/runtime/contextIdentity`): A `WeakMap` keyed by context identity that treats a wrapper of a context as the context itself ({@link aliasContext}). Also `contextOf`.
|
|
319
|
+
- `auditSuiteContent` (`@jgengine/core/suite/contentAudit`): The generic content check behind the suite's data entries: every item has a source and a use, every source is usable, every placeable item places, every firing weapon has obtainable ammo, and unlocks run steadily to the progression cap. Also `SuiteContent`, `SuiteContentItem`, `SuiteContentUse`, `SuiteUnlock`, `placedFact`, `recipeContent`.
|
|
320
|
+
- `expandSuiteGenres` (`@jgengine/core/suite/gameSuite`): The genres a declaration reaches, its includes followed. Also `GameSuiteConfig`, `GameSuiteScore`, `SuiteDataResult`, `SuiteProofResult`, `SuiteReport`, `SuiteScoreRow`, `SuiteStatus`, `parseSuiteReport` and 9 more.
|
|
321
|
+
- `runGameSuite` (`@jgengine/core/suite/runSuiteProof.draw`): Run a game's whole suite: every proof, then the content check. Also `LoadedSuiteProof`, `SuiteProofRun`, `SuiteProofRunOptions`, `runSuiteProof`.
|
|
322
|
+
- `SetupClosedError` (`@jgengine/core/suite/setupWindow`): Thrown when code touches a {@link SetupWindow} view after the window closed. Also `SetupWindow`, `openSetupWindow`.
|
|
323
|
+
- `SUITE_ENTRIES` (`@jgengine/core/suite/suiteCatalog`): Every entry, universal first, then by genre. Also `SUITE_GENRES`, `SUITE_UNIVERSAL`, `SuiteDepth`, `SuiteEntry`, `SuiteGenre`, `SuiteProofBy`.
|
|
324
|
+
- `CHEAT_COMMAND_NAME` (`@jgengine/core/suite/suiteProof`): Command names a proof treats as cheats even when the game forgot `cheat: true`. Also `ProofPlay`, `ProofSetup`, `SuiteProof`, `SuiteProofSpec`, `defineProof`.
|
|
325
|
+
- `STAND_ENVELOPE` (`@jgengine/core/plants/species/speciesReference`): How far a stand-grown tree may sit from its tables and still pass, at 20, 50 and 100 years in {@link REFERENCE_STAND}: height within 15% of its Carmean or FIA curve, DBH within 25% of its FIA curve, crown width inside its Tallo band.
|
|
326
|
+
- `FRAXINUS_NIGRA` (`@jgengine/core/plants/species/data/fraxinusNigra`): Black ash.
|
|
327
|
+
- `PRUNUS_SEROTINA` (`@jgengine/core/plants/species/data/prunusSerotina`): Black cherry.
|
|
328
|
+
- `ROBINIA_PSEUDOACACIA` (`@jgengine/core/plants/species/data/robiniaPseudoacacia`): Black locust.
|
|
329
|
+
|
|
330
|
+
### Changed
|
|
331
|
+
|
|
332
|
+
- A ridden flyer lifts a smaller creature in its talons
|
|
333
|
+
- The creature-survival suite asks for depth the research says matters: favourite and crafted taming food, waking loses the tame, gather roles at twice the best tool, lighter loads, airlift, feeding and starving, behavior modes, flight over terrain, encumbrance, farming, refining, fuel, preserving, tool wear, boss unlocks
|
|
334
|
+
- Fernwild: the slingshot throws stones and a new bow fires stone and tranquilizer arrows, so a charging raptor is knocked out from 25 m on two arrows; Fernwild scores 20/67
|
|
335
|
+
- Fernwild proof: the beach and the snow fill with their own animals
|
|
336
|
+
- The grass board notices every code edit
|
|
337
|
+
- Boat out of starts no longer runs the Linux suite on this PC: a start window that frees within the hour is waited for, one that frees later stops verify:fast with exit 75 and the time it frees, and lane:close lands nothing and says to close again then
|
|
338
|
+
- Boat runs hold a shared fairshare lease so every repo on the PC keeps to two boat machines; machines are named to fairshare so ones left by killed runs still count. Root cause: check-then-start let three machines run at once on 2026-11-07, since a machine is missing from boat list while it is created.
|
|
339
|
+
- Bolas, and every weapon hit is a dose list
|
|
340
|
+
- Summon the Broodmother, beat her and make brood greaves round her trophy
|
|
341
|
+
- A bred creature's screen shows which parent each stat came from and which a mutation raised
|
|
342
|
+
- Fernwild proof: the Broodmother calls her brood when wounded past two thirds
|
|
343
|
+
- Browser parity tests wait for their fixture before calling it
|
|
344
|
+
- Fernwild proof: fists dent a thatch wall but not a wooden pillar
|
|
345
|
+
- Cave air: swamp caves choke and sicken, lava caves burn hot, snow caves freeze
|
|
346
|
+
- Hostile caves: swamp gas burns breath and health, the lava cave is searing, the snow cave freezing
|
|
347
|
+
- Taming is built from general parts now: a body's meters (vitals, moved to @jgengine/core/stats/vitals) take doses that land now or over seconds up to a cap, and a dose table says what each source gives each kind of receiver, in preference order. The same two pieces describe a dino's foods, a sim's meals and shield potions that fill only to 50
|
|
348
|
+
- Fernwild's game-local subdual engine is deleted: torpor, belly, affinity and ineffectiveness are vitals, the diet is a dose table, knockout and the tame are flags. New proofs show waking loses the tame and a clean tame gives more levels; Fernwild scores 24/67
|
|
349
|
+
- Migration: import createVitals and its types from @jgengine/core/stats/vitals (was survival/vitals); VitalsState gains doses, and tick() also reports flags a change raised between ticks
|
|
350
|
+
- Fernwild proof: beach animals roll low levels and the far snow's roll high
|
|
351
|
+
- Fernwild proof: carrying slows the survivor and past the limit stops them
|
|
352
|
+
- Validation: the complete nine-game 1600x900 high moving-player gate measured zero shader links and framebuffers during play. Eight games failed the unchanged 16.7 ms frame limit while Wardogs used 26-31% GPU and whole-machine CPU reached 97%. The empty exception file and handling remain pending a clean all-game pass. An earlier quiet matched pair measured Harbor Heat 19.2 to 98.2 fps (102.5 to 12.5 ms p95) and Drift Foundry 62.8 to 113.4 fps (21.7 to 11.1 ms p95). Baseline Ember Command failed readiness twice and is not a matched comparison.
|
|
353
|
+
- 
|
|
354
|
+
- 
|
|
355
|
+
- 
|
|
356
|
+
- [Drift corridor movement proof](https://raw.githubusercontent.com/Noisemaker111/jgengine/pr-shots/pr-shots/agent/every-game-60fps-13/drift-foundry-sol-corridor.mp4): all arrival and movement checks passed; representative clip frames were inspected.
|
|
357
|
+
- Fernwild: a Small Crop Plot grows mejoberries from a seed, with water and fertilizer
|
|
358
|
+
- Following the Survivor's path through the beach uses every core feature first
|
|
359
|
+
- Fernwild proof: a tame follows when whistled and stays when told
|
|
360
|
+
- `bun run shoot --board --views` shoots six game views, one per grass game, the way a player sees them: the strategy games from their start camera, the others with the player placed in a grass patch (`--mode play --spawn`). They replace 18 close-up views from a detached camera 1 to 4 m off the ground, an angle the top-down games never show, three of which showed no grass
|
|
361
|
+
- beacon-bastion, brightway-park, ember-command, fernwild, harbor-heat and scrap-signal grass fields now name a species picked for their setting, and the grass-looks demo lays out every species.
|
|
362
|
+
- the engine bundle budget rises to 1880 KiB for the grass species table every grass field reads.
|
|
363
|
+
- A scene that sets no style draws exactly as before: every curve passes through today's look at realism 0.5
|
|
364
|
+
- Fernwild: a tame hauls what the survivor cannot, and an overloaded survivor can turn to it
|
|
365
|
+
- Fernwild: care raises a baby imprint, and the aim picks a baby standing in its parent
|
|
366
|
+
- A lane's landing wait runs in the harness background: lane:close, the Stop hook, the Monitor ban and AGENTS.md stop teaching a foreground background await --timeout 540 in slices, and a background await marks itself as the task's awaiter so the Stop hook lets a correctly waiting turn end
|
|
367
|
+
- Late gear from level 62 to the cap gives every Fernwild item a use
|
|
368
|
+
- The title and pause menus show their Controls, Settings and Credits panes through the menu.pane command
|
|
369
|
+
- Native Rust experiment for 360 fps: three.js frame export, wgpu replay with a pixel diff against three, Rune HUD, Deepward's tick in embedded V8 and in Rust, a wasm/WebGPU build, and bun run perf native for the four-way table. Verdict in native/README.md and #3041: stay on TS/three.js; the speedup Harbor Heat shows comes from WebGPU, in the browser too.
|
|
370
|
+
- Fernwild proof: a picked-clean berry bush grows back
|
|
371
|
+
- Diving and breath: crouch swims down, breath drains under water, drowning hurts
|
|
372
|
+
- Passive taming: an Ovis, Equus or Castoroides tames awake, hand-fed while hungry
|
|
373
|
+
- Cells live in an open-addressing table with their coordinates inline and their members in one pooled Int32Array; a query skips ids it already returned with a typed-array stamp instead of a Map. Cells keep creation order and each cell keeps its ids' order, so snapshot/restore still replay query results exactly. Public API unchanged.
|
|
374
|
+
- Measured in one process, old and new interleaved tick by tick (Bun 1.4.4, shared 24-core PC). Index alone, one querySphere + one update per id: 1k 9.8 → 1.4 ms (7.1x), 10k 207 → 24 ms (8.6x), 100k 4,199 → 881 ms (4.8x). Whole crowd tick through today's store: 1k 13.5 → 4.7 ms (2.9x), 10k 403 → 186 ms (2.2x), 100k 8,477 → 4,442 ms (1.9x); flat-array reference 0.19 / 2.0 / 38 ms.
|
|
375
|
+
- harbor-heat is unchanged, and was expected to be: the spatial index was ~1% of its frame and rendering dominates. Paired runs in both orders: old index 20.3 / 25.3 ms average frame, new index 26.6 ms (and one 53.7 ms run under machine load whose engine JS was not slower); the index's own profile share fell from 0.8-0.85% to 0.17-0.5%. This layout is the foundation for large crowds, big multiplayer worlds and the Rust multi-core kernel in #2999; the remaining crowd gap is string-keyed store lookups, addressed by the store fast path in a follow-up PR.
|
|
376
|
+
- Verified on a boat.dev Linux machine from a clean clone of head f602128fd: preflight (install, gen, type-checks, gates) and verify:fast (1001 test files, 31 type-checks, 6 gates, harbor-heat boot) passed.
|
|
377
|
+
- Regenerated the jgengine-world api.md for the spatial index's updated docs
|
|
378
|
+
- 128 players in one room, every player posing every 100 ms: 15 ms of host time per game second instead of 12 to 30 s on JSON (8.6 ms instead of 30 s on binary), and 25 MB/s out instead of 3.2 GB/s. The wire format is unchanged; #2990 covers binary delta rosters and interest-limited rows
|
|
379
|
+
- Measured: `bun run perf crowd` gains a by-handle row (store.columns plus the point grid), the three sides interleaved on the shared 24-core PC, median ms per tick: 1k by id 1.37, by handle 0.18, flat 0.12 (1.5x of flat); 10k by id 33.4, by handle 2.40, flat 1.41 (1.7x); 100k by id 1,629, by handle 41.3, flat 22.9 (1.8x). Under heavier machine load earlier the 100k ratio was 2.2x. The 3x target is met at both sizes on the handle path; the string-id path stays bounded by string-keyed lookups. This column layout is the one the Rust multi-core kernel in #2999 would read.
|
|
380
|
+
- harbor-heat is unchanged; this PR is the foundation for big crowds and multiplayer worlds, not a frame-time change for a game with tens of entities. Six alternating runs (branch, base, branch, base, then profiled) swung 17-28 ms average frame on both sides with other heavy jobs on the PC, and every system moved together, including GPU time and render passes this PR does not touch. Inside harbor-heat's own CPU profiles the changed files took 92 ms on the branch against 117 and 141 ms on base: pose history 14 ms against 28-34, the keyed store 3 against 9-10, scene spatial 11 against 21-25.
|
|
381
|
+
- Fernwild: a Preserving Bin keeps food four times as long
|
|
382
|
+
- Tests that read this PC's models pass in a fresh worktree
|
|
383
|
+
- A key moved in Settings takes effect in play: the game itself keeps the player's rebinds
|
|
384
|
+
- A refining forge smelts metal ore into ingots, and metal tools break stone
|
|
385
|
+
- Fernwild proof: a cleared beach herd comes back after the refill
|
|
386
|
+
- Flying: ride a Pteranodon or Argentavis, climb, cross and land; stamina out drops it
|
|
387
|
+
- A ridden Sarco swims through the sea at its own speed
|
|
388
|
+
- A save made in play survives quitting: Continue on the title brings it back
|
|
389
|
+
- Rebase conflicts inside a game's suite score resolve themselves
|
|
390
|
+
- Stop the served-game port test failing once in 483 runs
|
|
391
|
+
- A closed hut holds off the cold, and a survivor can walk up into one
|
|
392
|
+
- Fernwild: a hide sleeping bag places, and a dead survivor wakes on it once
|
|
393
|
+
- Fuelled stations: a campfire or pot burns wood, thatch or sparkpowder and cooks and warms only while lit
|
|
394
|
+
- Fernwild proof: sparkpowder crafts only beside a placed mortar
|
|
395
|
+
- Fernwild: the Storage Box places and keeps fifteen stacks
|
|
396
|
+
- a baby's hunger is the same meter at a faster pace; the breeding card no longer carries food
|
|
397
|
+
- Tames take a stance apart from follow or stay: passive, neutral or aggressive
|
|
398
|
+
- Tames at work: specialists gather their resource with a ridden bite and carry it lighter
|
|
399
|
+
- Tames gather what they're good at and carry it lighter
|
|
400
|
+
- Fernwild: kibble. Wild egg-layers drop eggs sized to their bodies, a cooking pot cooks five tiers of kibble from them, and each species tames about five times faster and cleaner on its preferred tier; proven side by side against raw meat and prime meat, Fernwild scores 22/67
|
|
401
|
+
- Fernwild proof: the snow at night takes health, and a lit campfire slows it
|
|
402
|
+
- Fernwild: tools wear with each landed swing or shot, break at zero, and repair with materials
|
|
403
|
+
- lane:close and verify:fast run every Linux-capable step on one boat.dev machine; game boots, model-reading and Windows-only tests stay on this PC
|
|
404
|
+
- The suite score generator writes into hand-edited READMEs and plans, so it names no outputs and reads only each game's suite files
|
|
405
|
+
- runStages test knows game-suite writes into hand-edited READMEs and PLANs
|
|
406
|
+
- jgengine suite scores the run it just made, so a regression reads red instead of the committed green report
|
|
407
|
+
- Game READMEs and plans lead with the suite score
|
|
408
|
+
- Brightway Park: headless suite proofs and the content check; Credits is not simulatable (#2967)
|
|
409
|
+
- Per-context state resolves a wrapped context to the real one, so a proof's setup seeds the game's own model
|
|
410
|
+
- perContext and the new ContextMap key by contextOf(ctx); the suite runner aliases its setup view to the
|
|
411
|
+
- session's context. A game helper handed the setup view used to build a second model for the wrapper.
|
|
412
|
+
- Scrap Signal: headless suite proofs; dev capture commands are marked cheat
|
|
413
|
+
- Fernwild: 18 headless suite proofs and the content check
|
|
414
|
+
- Games declare their suite genres; jgengine suite prints the score and gen writes it to README and PLAN
|
|
415
|
+
- Suite proofs: game code may keep the setup context, a command setup defines is a cheat, and the score renders by area
|
|
416
|
+
- The setup window lets the game's own ticks, commands and probe through after it closes, so a store
|
|
417
|
+
- that registered through the setup context keeps working; proof code that kept it is still refused.
|
|
418
|
+
- Game suite: a catalog of what players must be able to do, headless proofs that refuse cheats, and a content check
|
|
419
|
+
- Landing always runs beta's current landing code: lane:close fetches beta, unpacks its scripts/ once per version into .git/jg-landing/, and runs lane-close, land, the queue and the batch from there against the lane, so a landing fix reaches lanes made before it
|
|
420
|
+
- Breaking: the exports FRAXINUS_NIGRA, PRUNUS_SEROTINA and ROBINIA_PSEUDOACACIA and their plants/species/data subpaths are removed, because those species cannot yet grow a decurrent crown inside the forestry envelope; use another species from PLANT_LIBRARY
|
|
421
|
+
|
|
422
|
+
### Fixed
|
|
423
|
+
|
|
424
|
+
- `bun run shoot --board` no longer runs for hours under one 90-minute guard that killed full boards part way (292 captures at 40 s to 2 min each). One run shoots for `--budget` seconds (default 6 min), records each shot in the board folder's `board.json` as it lands and exits 3 while shots remain; the same command carries on, and the run that takes the last shot lays out the sheets. A killed run loses only the capture in flight, a failed capture is named and keeps the board unfinished (no sheets with gaps) until it lands, and shots taken with other code are retaken (`--resume` keeps them)
|
|
425
|
+
- A board run killed by its guard names the shot that hung instead of "hung before it reported a step"
|
|
426
|
+
- a lane's game suite reports come back from the boat machine and their scores are written before the commit, so new proofs count in each game's score
|
|
427
|
+
- grass and terrain clearing math stops calling Math.hypot per zone and per vertex, so streaming grass in ember-command allocates about a third less
|
|
428
|
+
- Scattered plants and rocks draw through the shared static batches, so every chunk, variant and detail level with the same material is one draw per area instead of one draw each (Fernwild: 738 draws down to about 370, frame p95 24.8 ms to 15.6 ms).
|
|
429
|
+
- Unique near plants and rocks (scatter heroes) join the same shared batches instead of drawing every part of every hero on its own in the main pass and each shadow cascade.
|
|
430
|
+
- World-projected (triplanar) materials now follow each placement's own rotation, so instanced and batched rocks blend their texture by their real facing, and they can share batched draws.
|
|
431
|
+
- A shared batch picks up textures its source material swaps in after the batch was made, such as generated rock surfaces whose bake lands after the first rock is placed.
|
|
432
|
+
- Clicking a building piece or other batched scenery that names no object no longer reports an empty object id.
|
|
433
|
+
- Harbor Heat no longer relinks its car shaders every few seconds or stalls when traffic comes and goes; its p95 frame went from 22.6 ms to 18.2 ms.
|
|
434
|
+
- the landing queue retries a failed GitHub read instead of giving up, and says what GitHub answered.
|
|
435
|
+
- landing retries a GitHub server error on a read or a push instead of giving up, and says what GitHub answered.
|
|
436
|
+
- landing retries a GitHub server error on a read, a push or a merge instead of giving up, and says what GitHub answered.
|
|
437
|
+
- flocks of birds draw as one instanced mesh per species instead of one skinned draw per bird
|
|
438
|
+
- placed props cost nothing per frame while nothing moves and the view shows the same set
|
|
439
|
+
- Harbor Heat holds 60 fps on the reference GPU (p95 16.2 ms, was 22.6 ms) and leaves the known-failing frame list
|
|
440
|
+
- shared scenery draws and impostor bakes survive temporary membership changes without rebuilding shaders in play.
|
|
441
|
+
- static props, buildings, character uploads, particles and the Drift Foundry HUD avoid repeated work while keeping their authored materials and detail.
|
|
442
|
+
- shadow and particle programs warm before play, and scatter loading holds readiness until the complete models mount.
|
|
443
|
+
- provisioning includes named asset-pack imports, and every game is measured moving at high quality with automatic quality changes disabled.
|
|
444
|
+
- the shader-ready check after load rides on the renderer's own draws instead of walking the whole visible scene before every render, so its cost follows what the camera draws, not the size of the world (Fernwild: 285 ms to 54 ms per 6 s)
|
|
445
|
+
- dev probes that walk the scene (triangles, shadow casters, static batch draws) walk it at most every 2 s; every slow frame and every telemetry report used to walk it again, which made slow games slower in dev and in perf runs
|
|
446
|
+
- the HUD overflow check measures panels at most twice a second instead of on every style change, which forced a page layout each frame in a HUD that moves bars or markers
|
|
447
|
+
- perf and capture wait for a slow load to finish instead of giving up at 90 s while the shader warmup still had 30 s left (Ember Command on a GPU short of memory); a perf batch's deadline grows with its games
|
|
448
|
+
- ambient fish, insects and motes reuse the ground height they last read until they move half a metre or near the edge of their height band, instead of every agent reading the sculpted ground every frame; they land exactly where they did on ground up to about 65°, and read it 5 to 15 times less often (Fernwild spent about 2.5 ms a frame there)
|
|
449
|
+
- small scatter species (shrubs, ferns, rocks) pick a detail level per plant by its own distance instead of per 200 m chunk, and stop drawing past their draw distance one plant at a time
|
|
450
|
+
- a scatter field keeps a mesh in the scene only while it has plants to draw; Fernwild kept about 11,900 empty meshes that three walked on every render (scene 13,315 to 4,817 nodes)
|
|
451
|
+
- the small-shadow cull stops watching a node once it leaves the scene, so meshes added and dropped as the camera moves are not held for the whole session
|
|
452
|
+
- verify:fast type-checks every package, app and game whose program imports a changed engine file, as CI's types job does; it checked only the games, so an engine change the shell's stricter settings reject passed locally and failed in CI
|
|
453
|
+
- the frame gate (bun run perf gate), CI's perf matrix and perf network --all measure every game under games/ with a web entry, read from disk, instead of a hand-kept list of 8 that left Fernwild unjudged; a new game is measured the day it lands
|
|
454
|
+
- the capture test that checks --reuse-storage refuses without a warm browser allows 30 s per start instead of 5; the script loads much of the engine first, which ran past 5 s on a cold CI runner and failed an unrelated PR
|
|
455
|
+
- props with a see-through part (street-lamp glass, foliage cards) now batch like other scenery instead of drawing one by one; each batch draws its copies farthest first, so blending looks as before. Harbor Heat drew its 28 street lamps and 28 bushes one by one; its scene draws drop from 440 to 336 a frame.
|
|
456
|
+
- Roads that look alike draw as one draw per layer (asphalt, gutter, lane paint, sidewalk, kerb), and every road junction of one colour is one draw: a street grid no longer costs five draws per street.
|
|
457
|
+
- Crowd characters that differ only in paint colour (traffic resprayed four ways) share one instanced draw per material, each drawing its own colour.
|
|
458
|
+
- Shared static-prop batches are culled whole by their cell instead of re-culling, re-sorting and re-uploading every instance in every render pass and shadow cascade.
|
|
459
|
+
- Foliage sway ramps over the model's own measured height, so one plant placed at several sizes sways alike and shares one batched draw.
|
|
460
|
+
- Auto exposure reads its light meter back at the start of a frame instead of after the scene, so it no longer stalls the frame for 7-118 ms while the browser finishes drawing.
|
|
461
|
+
- When the camera pans, at most one far shadow cascade redraws per frame (the longest waiting first) instead of all of them on the same frame.
|
|
462
|
+
- City ground layers with an edge fade draw in one pass instead of re-flagging their material twice every frame.
|
|
463
|
+
- Ember Command's HUD no longer re-renders every frame for a mission clock that changes once a second, and its minimap fog is built once per fog change as one rectangle per run of cells.
|
|
464
|
+
- The perf breakdown's own per-draw bookkeeping no longer allocates, so the garbage it reports is the game's.
|
|
465
|
+
- Characters out of view are no longer animated: they draw nothing and cast no shadow, so their skeletons are not posed until they come back into view (Scrap Signal: 3,508 bone writes a frame down to 32).
|
|
466
|
+
- Building kit pieces join the shared static batches, so walls and roofs of every building that share a material draw together per area instead of one draw per building chunk.
|
|
467
|
+
- Props with texture-map overrides (panel and scrap-metal crates, rocks) now batch like any other scenery instead of drawing one at a time; Scrap Signal's frame went from 25.7 ms to 16.5 ms at p95.
|
|
468
|
+
- grass no longer reads as a spotted patch. Tuft roots are laid on an even low-discrepancy pattern instead of random scatter, which left piles and bare gaps, and they stay even when distance thins them.
|
|
469
|
+
- each tuft's blades now spread far enough to meet the next tuft (at least three quarters of the tuft spacing), so a field is one sward instead of fountain islands on the turf mat.
|
|
470
|
+
- grass blades seen from behind are no longer near black. three.js flipped the whole normal on back faces and pointed the blade's sky lift into the ground. Both sides of a blade now light from above, and the lower blade sits in an even shade so a dense field still reads deep.
|
|
471
|
+
- clumping only pulls tufts into clumps when a look asks for strong clumping (or cushions), so ordinary lawns and meadows stop forming bald patches.
|
|
472
|
+
- the stray-browser sweep test no longer opens a Chrome window that takes the foreground; its planted browser now runs headless
|
|
473
|
+
- the home and /why pages' 2D canvas demos (car, keys, saves and the rest) stop drawing while offscreen or in a background tab, like the three.js demos already did
|
|
474
|
+
- traced code panels stop re-reading their lit lines every 90 ms while offscreen or in a background tab
|
|
475
|
+
- a production build's frame breakdown names its post passes instead of their minified class names
|
|
476
|
+
- perf play, perf gate and jgengine desktop link the game's asset packs first, so a fresh checkout no longer measures or ships placeholder boxes
|
|
477
|
+
- jgengine desktop --diagnostics passes --sourcemap through a game's own build script, and names the app after defineGame's own name
|
|
478
|
+
- createSpatialIndex keys cells by number and ids by integer handle, so moving ids and querying allocate nothing.
|
|
479
|
+
- Harbor Heat's chart stops re-rendering every frame; it pans, turns and places its markers through MinimapChrome's new place handle
|
|
480
|
+
- static prop batches with the same material share one material copy across cells, so the main pass stops re-uploading the same uniforms at each cell's batch
|
|
481
|
+
- a ws room sends at most one presence roster per pose interval (poseRules.minIntervalMs, 100 ms by default) instead of one per accepted pose; the first move in a quiet room, a player's first pose and a leave still go out at once, and later moves and appearance changes ride the roster that closes the window
|
|
482
|
+
- presence, chat and voice broadcasts encode each message once per codec and send the same frame to every subscriber
|
|
483
|
+
- `stepFlock` finds flockmates through the point grid instead of a per-call Map of arrays. Results are bit-identical over 120 steps at 50 and 500 agents; a step at 1k agents went 1.13 to 1.00 ms and at 10k 18.5 to 13.9 ms (8 agents: 5.8 µs either way).
|
|
484
|
+
- pose history captures each step from the store's columns into one flat array by handle. `beginStep` at 10k entities went 1.10 to 0.11 ms and at 100k 27.2 to 0.58 ms; sampling every entity at 100k went 19.5 to 14.6 ms. A handle reused by a new entity, or a body respawned under the same id, now renders live until the next step instead of gliding from the old body's pose.
|
|
485
|
+
- pose writes by id are cheaper, with no API change. setPose, setVelocity, update and resetToSpawn find the entity through the handle table (one id lookup where store.get then store.set took three) and notify through a new `store.touch`; the behaviour-change check skips its lookup when nothing is pending; a move that stays inside its scene-spatial cell skips the index bookkeeping. setPose plus setVelocity on a bare store, columns mirrored, beta and branch interleaved in one process: 10k 106 to 80 ns, 100k 210 to 195 ns. Through the game context's wiring (store onChange to the scene spatial index, one batch per frame), setPose with dt, median of three runs: 10k 271 to 259 ns, 100k 667 to 574 ns. A position given as {x, y, z} and a dt-derived velocity are still new tuples, each with the reason in code. Entity objects carry no handle: a hidden property made spreading them 8x slower in Bun.
|
|
486
|
+
- the land queue rebases a PR the base moved into conflict instead of giving up
|
|
487
|
+
- the demo nudge ring and arrow pause while their demo is off screen or the tab is hidden, so a page scrolled past a demo stops restyling it every frame
|
|
488
|
+
- a knocked-out creature now provably stays out until its torpor, plus any narcotic still landing, wears off; the wake proof fails if it wakes any sooner
|
|
489
|
+
- editor capture waits for its viewport and finished loading
|
|
490
|
+
- editor and asset provisioning help reject invalid options before changing files
|
|
491
|
+
- Keep weathering finite under deep cover
|
|
492
|
+
- Sort river sediment using its concentration near the bed
|
|
493
|
+
- Include hillslope creep in outlet volume
|
|
494
|
+
- Attribute test failures to the right file
|
|
495
|
+
- grass streaming bounds its work per frame and resumes it on the next, growing the nearest leaf first so the first frame still shows grass
|
|
496
|
+
- grass rebuilds when the terrain under it changes
|
|
497
|
+
- every grass patch on a canvas shares one 3 ms frame budget, the 8 ms fill budget ends once the first view has filled, and the first fill counts as loading so play never starts on half-grown grass
|
|
498
|
+
- captures wait out slow model loads for the whole settle budget instead of failing after one 15 s wait
|
|
499
|
+
- bun run shoot --load runs headless by default, so no jgengine capture opens a Chrome window unless --headed is passed; headless Chrome on Windows still renders on the real GPU.
|
|
500
|
+
- A Beacon defense saves itself once a second while playing, so a page reload mid-run continues into the same towers, purse, wave and raiders (about 0.2 ms and 12 KB per write on a busy defense).
|
|
501
|
+
- Opening or raising another Beacon keep never overwrites the keep left behind, and a keep changed in another window is left alone instead of overwritten.
|
|
502
|
+
- A HUD gated by `showDuring` shows its panels when play starts from a title menu.
|
|
503
|
+
- A failed capture is retried from its evidence instead of stopping after two attempts.
|
|
504
|
+
- black ash, black cherry and black locust are back in PLANT_LIBRARY, and FRAXINUS_NIGRA, PRUNUS_SEROTINA and ROBINIA_PSEUDOACACIA with their plants/species/data subpaths are exported again (#2959 removed them); each meets the tree envelope: stand height within 15% and DBH within 25% of its tables and inside the Tallo band at 20, 50 and 100 years, and a decurrent crown on two of three open-grown seeds. Their refits had stalled in the basins they started in; plants fit now starts a new tree search from the shipped tree of the same crown form that misses least of the tree checks (or one named with --from), and a missed check costs the fit 100 instead of 10
|
|
505
|
+
|
|
506
|
+
### Removed
|
|
507
|
+
|
|
508
|
+
- `@jgengine/core/survival/vitals` no longer exports `VitalDef`, `VitalFlag`, `VitalFlagChange`, `VitalLink`, `VitalRateModifier`, `Vitals`, `VitalsState`, `VitalsTickOptions` and 1 more.
|
|
509
|
+
- PERF_GATE_GAMES and perf-network's ALL_GAMES; FRAME_GAMES in scripts/ci-scope.ts is the one list
|
|
510
|
+
- `@jgengine/core/plants/species/data/fraxinusNigra` no longer exports `FRAXINUS_NIGRA`.
|
|
511
|
+
- `@jgengine/core/plants/species/data/prunusSerotina` no longer exports `PRUNUS_SEROTINA`.
|
|
512
|
+
- `@jgengine/core/plants/species/data/robiniaPseudoacacia` no longer exports `ROBINIA_PSEUDOACACIA`.
|
|
513
|
+
|
|
29
514
|
## 0.53.0
|
|
30
515
|
|
|
31
516
|
### Migrate
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jgengine/convex",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.55.0",
|
|
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.
|
|
33
|
+
"@jgengine/core": "^0.55.0"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"convex": "^1.31.2",
|
package/skills/jgengine/SKILL.md
CHANGED
|
@@ -61,4 +61,4 @@ If nothing fits, name the shared owner first (AGENTS.md, "Reusable capability go
|
|
|
61
61
|
|
|
62
62
|
## Finish
|
|
63
63
|
|
|
64
|
-
Keep scene coordinates in the editor document, state serializable, randomness injected. A game is not done while its entities and props are box markers: run the asset pass in `jgengine-assets`. Prove it with `jgengine-verify`.
|
|
64
|
+
Declare the game's genres in `jgengine.json` (`suite`) and prove each required player verb with a headless proof; `jgengine suite <game>` is the score ([games/AGENTS.md](../../../games/AGENTS.md#game-suite)). Keep scene coordinates in the editor document, state serializable, randomness injected. A game is not done while its entities and props are box markers: run the asset pass in `jgengine-assets`. Prove it with `jgengine-verify`.
|
package/skills/jgengine/api.md
CHANGED
|
@@ -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.
|
|
9
|
+
- `VERSION` (const): const VERSION: "0.55.0" — Installed `@jgengine/core` semver - compare against {@link CHANGELOG} keys when migrating.
|
|
10
10
|
|
|
11
11
|
## @jgengine/core/authoring
|
|
12
12
|
|
|
@@ -83,8 +83,8 @@
|
|
|
83
83
|
|
|
84
84
|
- `CommandDecodeResult` (type): type CommandDecodeResult<TInput> = | { ok: true; value: TInput } | { ok: false; reason: string } — ⚠ undocumented
|
|
85
85
|
- `CommandDecoder` (type): type CommandDecoder<TInput> = (input: unknown) => CommandDecodeResult<TInput> — Parses raw `unknown` transport input into `TInput`, rejecting anything that doesn't match the command's declared shape. Runs before `validate`/`apply`, so a malformed payload never reaches game logic.
|
|
86
|
-
- `CommandDefinition` (interface): interface CommandDefinition<TState, TInput> { decode?: CommandDecoder<TInput>; validate?(state: TState, input: TInput): CommandRejection | null; apply(state: TState, input: TInput): TState | void } — ⚠ undocumented
|
|
87
|
-
- `CommandRegistry` (interface): interface CommandRegistry<TState> { define<TInput>(name: string, definition: CommandDefinition<TState, TInput>): void; has(name: string): boolean; names(): string[]; run(state: TState, name: string, input: unknown): CommandResult<TState> } — ⚠ undocumented
|
|
86
|
+
- `CommandDefinition` (interface): interface CommandDefinition<TState, TInput> { decode?: CommandDecoder<TInput>; validate?(state: TState, input: TInput): CommandRejection | null; apply(state: TState, input: TInput): TState | void; cheat?: boolean } — ⚠ undocumented
|
|
87
|
+
- `CommandRegistry` (interface): interface CommandRegistry<TState> { define<TInput>(name: string, definition: CommandDefinition<TState, TInput>): void; has(name: string): boolean; names(): string[]; isCheat(name: string): boolean; run(state: TState, name: string, input: unknown): CommandResult<TState> } — ⚠ undocumented
|
|
88
88
|
- `CommandRejection` (interface): interface CommandRejection — Why a command refused: `reason` is a stable machine code; `message`, when set, is shown to the player.
|
|
89
89
|
- `CommandResult` (type): type CommandResult<TState> = | { status: "applied"; state: TState } | { status: "rejected"; reason: string; message?: string } | { status: "unknown-command" } — ⚠ undocumented
|
|
90
90
|
- `createCommandRegistry` (function): function createCommandRegistry<TState>(): CommandRegistry<TState> — ⚠ undocumented
|
|
@@ -142,6 +142,8 @@
|
|
|
142
142
|
|
|
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
|
+
- `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
|
|
145
147
|
|
|
146
148
|
## @jgengine/core/devtools/collisionDebug
|
|
147
149
|
|
|
@@ -256,6 +258,7 @@
|
|
|
256
258
|
|
|
257
259
|
- `DiscoverModule` (interface): interface DiscoverModule — Discovery subsystem: the public discover and probes facades plus internal record map and helpers.
|
|
258
260
|
- `DiscoveredRecord` (interface): interface DiscoveredRecord — Internal record for an auto-discovered field: identity, kind, initial value, source object, and get/set accessors.
|
|
261
|
+
- `SCENE_PROBE_REFRESH_MS` (const): const SCENE_PROBE_REFRESH_MS: 2000 — How often a probe that walks the whole scene walks it again; in between it answers its last reading.
|
|
259
262
|
- `createDiscoverModule` (const): const createDiscoverModule: (deps: { signal: ChangeSignal; register: <T>(name: string, initial: T, options?: TunableOptions<T> | undefined) => Tunable<T>; controlRecords: Map<string, ControlRecord>; }) => DiscoverModule — Create the discovery subsystem that scans objects/tables for tunable fields and binds them as controls and probes.
|
|
260
263
|
|
|
261
264
|
## @jgengine/core/devtools/fallbackSeams
|
|
@@ -283,8 +286,12 @@
|
|
|
283
286
|
- `FrameBreakdown` (interface): interface FrameBreakdown — A running breakdown, as `installFrameBreakdown` returns it and `window.__jgFrameBreakdown` holds it.
|
|
284
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.
|
|
285
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.
|
|
286
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.
|
|
287
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`).
|
|
288
295
|
- `NamedCost` (interface): interface NamedCost — A named cost over the window: its average per frame and its 95th percentile.
|
|
289
296
|
- `Spread` (interface): interface Spread — Percentiles of one series.
|
|
290
297
|
|
|
@@ -298,6 +305,7 @@
|
|
|
298
305
|
- `FrameSampler` (interface): interface FrameSampler — The installed sampler: ask it for a report, reset its window, or tear it down.
|
|
299
306
|
- `FrameTelemetrySources` (interface): interface FrameTelemetrySources — What the shell hands the sampler: live getters, called only when a report is built.
|
|
300
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.
|
|
301
309
|
- `REPORT_PUSH_MS` (const): const REPORT_PUSH_MS: 1000 — Cadence of the push to the dev server, in milliseconds.
|
|
302
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.
|
|
303
311
|
- `TELEMETRY_WARMUP_MS` (const): const TELEMETRY_WARMUP_MS: 5000 — How long after the sampler starts the GL counters get their post-warmup baseline.
|
|
@@ -340,6 +348,14 @@
|
|
|
340
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.
|
|
341
349
|
- `PerfOverlaySample` (interface): interface PerfOverlaySample — What the overlay draws, filled in place by {@link PerfOverlayProbe} and read by {@link PerfOverlay}.
|
|
342
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
|
+
|
|
343
359
|
## @jgengine/core/devtools/report
|
|
344
360
|
|
|
345
361
|
- `FrameWindow` (interface): interface FrameWindow — Frame timings over the measured window, as the player sees them.
|
|
@@ -445,7 +461,7 @@
|
|
|
445
461
|
|
|
446
462
|
- `CHANGELOG` (const): const CHANGELOG: Record<string, ChangelogEntry> — Per-version engine changelog keyed by semver string (e.g. `"0.10.0"`).
|
|
447
463
|
- `ChangelogEntry` (interface): interface ChangelogEntry — One release's migrate steps plus added/changed/removed notes (typed mirror of CHANGELOG.md).
|
|
448
|
-
- `VERSION` (const): const VERSION: "0.
|
|
464
|
+
- `VERSION` (const): const VERSION: "0.55.0" — Installed `@jgengine/core` semver - compare against {@link CHANGELOG} keys when migrating.
|
|
449
465
|
|
|
450
466
|
## @jgengine/core/runtime/adapter
|
|
451
467
|
|
|
@@ -497,6 +513,11 @@
|
|
|
497
513
|
- `ObjectSlotTransfer` (interface): interface ObjectSlotTransfer — Which player inventory a transfer moves between, and what it moves.
|
|
498
514
|
- `SceneObjectSlots` (interface): interface SceneObjectSlots — Per-instance container contents for placed objects whose catalog entry declares `slotInventory` — the engine side of "install a GPU into a rack", "put an item in a chest". Contents live on `SceneObject.slots`, so they replicate and save with the object rather than needing a game-owned table keyed by `instanceId`, and every mutation is validated against the declared `accepts` and stack limits.
|
|
499
515
|
|
|
516
|
+
## @jgengine/core/runtime/contextIdentity
|
|
517
|
+
|
|
518
|
+
- `ContextMap` (class): class ContextMap<T> — A `WeakMap` keyed by context identity that treats a wrapper of a context as the context itself ({@link aliasContext}). Use it for per-context state instead of `new WeakMap<GameContext, T>()`.
|
|
519
|
+
- `contextOf` (function): function contextOf(ctx: GameContext): GameContext — The context `ctx` stands for: itself, or the context a wrapper of it was aliased to.
|
|
520
|
+
|
|
500
521
|
## @jgengine/core/runtime/gameContext
|
|
501
522
|
|
|
502
523
|
- `CatalogEntityRole` (type): type CatalogEntityRole = "player" | "enemy" | "hostile" | "npc" | "vehicle" — ⚠ undocumented
|
|
@@ -505,7 +526,7 @@
|
|
|
505
526
|
- `GameAudio` (interface): interface GameAudio — Reachable audio seam on `ctx.game`: `play`, `music`, and `resume` route through the `audio.play`/`audio.music`/`audio.resume` events the shell's audio engine listens on, so game code triggers sound without importing the shell. Retained `loop`/`setLoop`/`stopLoop` add id-keyed loops with live pitch/gain control over `audio.loopStart`/`audio.loopSet`/`audio.loopStop` (#1051).
|
|
506
527
|
- `GameContext` (interface): interface GameContext — The live engine handle a game's loop, systems, commands, and UI read and mutate — entities, objects, the reactive store, opted-in `game.*` subsystems, world queries, and the sim clock. One context = one running world.
|
|
507
528
|
- `GameContextCards` (interface): interface GameContextCards { pile(id: string, config?: CardPileConfig): CardPile } — ⚠ undocumented
|
|
508
|
-
- `GameContextCommands` (interface): interface GameContextCommands { define<TInput>(name: string, definition: CommandDefinition<GameContext, TInput>): void; has(name: string): boolean; names(): string[]; run(name: string, input: unknown): CommandResult<GameContext>; runAs(
|
|
529
|
+
- `GameContextCommands` (interface): interface GameContextCommands { define<TInput>(name: string, definition: CommandDefinition<GameContext, TInput>): void; has(name: string): boolean; names(): string[]; isCheat(name: string): boolean; run(name: string, input: unknown): CommandResult<GameContext>; runAs(ac… — ⚠ undocumented
|
|
509
530
|
- `GameContextContent` (interface): interface GameContextContent { defaultRarity?: string; itemById?(itemId: string): GameContextItemEntry | null | undefined; entityById?(catalogId: string): GameContextEntityEntry | null | undefined; objectById?(catalogId: string): GameContextObjectEntry | null | undefin… — ⚠ undocumented
|
|
510
531
|
- `GameContextEconomy` (interface): interface GameContextEconomy { balance(userId: string, currencyId: string | CurrencyDefinition): number; grant(userId: string, currencyId: string | CurrencyDefinition, amount: number): void; charge(userId: string, currencyId: string | CurrencyDefinition, amount: number… — ⚠ undocumented
|
|
511
532
|
- `GameContextEntityEntry` (interface): interface GameContextEntityEntry { stats?: StatCatalog; receive?: ReceiveMap; onDeath?: OnDeathSpec; movement?: PoseAllowedStates & {walkSpeed?: number}; role?: CatalogEntityRole; verbs?: readonly ContextVerb[]; colliders?: EntityColliderSet; scale?: number } — ⚠ undocumented · used by `entityEntryFromCatalog` (@jgengine/core/editor): Turns an authored entity row into the runtime {@link GameContextEntityEntry} a spawn consumes: looks up `entityId` in the document's {@link …
|
|
@@ -536,7 +557,7 @@
|
|
|
536
557
|
- `GameAudio` (interface): interface GameAudio — Reachable audio seam on `ctx.game`: `play`, `music`, and `resume` route through the `audio.play`/`audio.music`/`audio.resume` events the shell's audio engine listens on, so game code triggers sound without importing the shell. Retained `loop`/`setLoop`/`stopLoop` add id-keyed loops with live pitch/gain control over `audio.loopStart`/`audio.loopSet`/`audio.loopStop` (#1051).
|
|
537
558
|
- `GameContext` (interface): interface GameContext — The live engine handle a game's loop, systems, commands, and UI read and mutate — entities, objects, the reactive store, opted-in `game.*` subsystems, world queries, and the sim clock. One context = one running world.
|
|
538
559
|
- `GameContextCards` (interface): interface GameContextCards { pile(id: string, config?: CardPileConfig): CardPile } — ⚠ undocumented
|
|
539
|
-
- `GameContextCommands` (interface): interface GameContextCommands { define<TInput>(name: string, definition: CommandDefinition<GameContext, TInput>): void; has(name: string): boolean; names(): string[]; run(name: string, input: unknown): CommandResult<GameContext>; runAs(
|
|
560
|
+
- `GameContextCommands` (interface): interface GameContextCommands { define<TInput>(name: string, definition: CommandDefinition<GameContext, TInput>): void; has(name: string): boolean; names(): string[]; isCheat(name: string): boolean; run(name: string, input: unknown): CommandResult<GameContext>; runAs(ac… — ⚠ undocumented
|
|
540
561
|
- `GameContextContent` (interface): interface GameContextContent { defaultRarity?: string; itemById?(itemId: string): GameContextItemEntry | null | undefined; entityById?(catalogId: string): GameContextEntityEntry | null | undefined; objectById?(catalogId: string): GameContextObjectEntry | null | undefin… — ⚠ undocumented
|
|
541
562
|
- `GameContextEconomy` (interface): interface GameContextEconomy { balance(userId: string, currencyId: string | CurrencyDefinition): number; grant(userId: string, currencyId: string | CurrencyDefinition, amount: number): void; charge(userId: string, currencyId: string | CurrencyDefinition, amount: number… — ⚠ undocumented
|
|
542
563
|
- `GameContextEntityEntry` (interface): interface GameContextEntityEntry { stats?: StatCatalog; receive?: ReceiveMap; onDeath?: OnDeathSpec; movement?: PoseAllowedStates & {walkSpeed?: number}; role?: CatalogEntityRole; verbs?: readonly ContextVerb[]; colliders?: EntityColliderSet; scale?: number } — ⚠ undocumented · used by `entityEntryFromCatalog` (@jgengine/core/editor): Turns an authored entity row into the runtime {@link GameContextEntityEntry} a spawn consumes: looks up `entityId` in the document's {@link …
|
|
@@ -699,7 +720,7 @@
|
|
|
699
720
|
- `PoseHistory` (interface): interface PoseHistory — Previous-step pose buffer: capture at each fixed step, sample between steps for rendering.
|
|
700
721
|
- `PoseHistoryState` (interface): interface PoseHistoryState — Serializable previous-step poses keyed by entity id.
|
|
701
722
|
- `RenderPose` (type): type RenderPose = [x: number, y: number, z: number, rotationY: number] — A render pose sampled between two simulation steps: `[x, y, z, rotationY]`.
|
|
702
|
-
- `createPoseHistory` (function): function createPoseHistory(options?: { snapDistance?: number }): PoseHistory — Previous-step pose buffer for render interpolation under a fixed-step loop.
|
|
723
|
+
- `createPoseHistory` (function): function createPoseHistory(options?: { snapDistance?: number }): PoseHistory — Previous-step pose buffer for render interpolation under a fixed-step loop. Reads poses straight from the store's {@link EntityStore.columns} into one flat array indexed by handle, so a step over N entities allocates nothing and does no id lookups. Each captured pose remembers its handle's generation: a handle reused by a new entity, or a body respawned under the same id, renders live until the next step.
|
|
703
724
|
- `lerpAngle` (function): function lerpAngle(from: number, to: number, t: number): number — Shortest-arc interpolation between two yaw angles.
|
|
704
725
|
|
|
705
726
|
## @jgengine/core/runtime/prediction
|
|
@@ -710,6 +731,15 @@
|
|
|
710
731
|
- `PredictionStep` (type): type PredictionStep<S> = (state: S, input: InputFrame, dt: number) => S — Deterministic state transition used to advance one predicted input frame.
|
|
711
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.
|
|
712
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
|
+
|
|
713
743
|
## @jgengine/core/runtime/simContext
|
|
714
744
|
|
|
715
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.
|
|
@@ -831,7 +861,7 @@
|
|
|
831
861
|
|
|
832
862
|
## @jgengine/shell/gameKit
|
|
833
863
|
|
|
834
|
-
- `Clock` (
|
|
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.
|
|
835
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`. *
|
|
836
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"] }`).
|
|
837
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.
|
|
@@ -844,10 +874,10 @@
|
|
|
844
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).
|
|
845
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.
|
|
846
876
|
- `GameLoop` (interface): interface GameLoop<TContext = unknown> — Lifecycle hooks a game implements to drive init, per-tick simulation, and player join/leave.
|
|
847
|
-
- `Hotbar` (
|
|
848
|
-
- `HudCanvas` (
|
|
849
|
-
- `HudFade` (
|
|
850
|
-
- `HudPanel` (
|
|
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.
|
|
851
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.
|
|
852
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.
|
|
853
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.
|
|
@@ -48,6 +48,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
48
48
|
|
|
49
49
|
- `readQuantity` (function) · `import { readQuantity } from "@jgengine/core/commands/commandInput"`
|
|
50
50
|
|
|
51
|
+
## context-map — per-context state keyed by the context a wrapper stands for, so a wrapped context shares its model
|
|
52
|
+
|
|
53
|
+
- `ContextMap` (class) · `import { ContextMap } from "@jgengine/core/runtime/contextIdentity"`
|
|
54
|
+
|
|
51
55
|
## default-drive-codes — stock WASD/arrow throttle, brake, steer and handbrake key codes
|
|
52
56
|
|
|
53
57
|
- `DEFAULT_DRIVE_CODES` (const) · `import { DEFAULT_DRIVE_CODES } from "@jgengine/shell/gameKit"`
|
|
@@ -106,11 +110,11 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
106
110
|
|
|
107
111
|
## hud-canvas — the HUD root surface — design-resolution scaling plus live panel-placement editing (F2+C)
|
|
108
112
|
|
|
109
|
-
- `HudCanvas` (
|
|
113
|
+
- `HudCanvas` (const) · `import { HudCanvas } from "@jgengine/shell/gameKit"`
|
|
110
114
|
|
|
111
115
|
## hud-clock — in-game time-of-day readout bound to the sim clock
|
|
112
116
|
|
|
113
|
-
- `Clock` (
|
|
117
|
+
- `Clock` (const) · `import { Clock } from "@jgengine/shell/gameKit"`
|
|
114
118
|
|
|
115
119
|
## hud-coins — currency counter widget bound to a currency id
|
|
116
120
|
|
|
@@ -118,11 +122,11 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
118
122
|
|
|
119
123
|
## hud-fade — HUD group that fades when idle and returns when its values change
|
|
120
124
|
|
|
121
|
-
- `HudFade` (
|
|
125
|
+
- `HudFade` (const) · `import { HudFade } from "@jgengine/shell/gameKit"`
|
|
122
126
|
|
|
123
127
|
## hud-hotbar — optional inventory hotbar building block — games own slot chrome
|
|
124
128
|
|
|
125
|
-
- `Hotbar` (
|
|
129
|
+
- `Hotbar` (const) · `import { Hotbar } from "@jgengine/shell/gameKit"`
|
|
126
130
|
|
|
127
131
|
## hud-layout — persistent, live-editable HUD panel layout state for `HudCanvas`
|
|
128
132
|
|
|
@@ -130,7 +134,7 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
|
|
|
130
134
|
|
|
131
135
|
## hud-panel — anchored HUD slot — drop any widget in a named, player-movable panel
|
|
132
136
|
|
|
133
|
-
- `HudPanel` (
|
|
137
|
+
- `HudPanel` (const) · `import { HudPanel } from "@jgengine/shell/gameKit"`
|
|
134
138
|
|
|
135
139
|
## hud-slots — a play HUD of edge-pinned, player-movable panels in one component
|
|
136
140
|
|
|
@@ -46,7 +46,7 @@ Use [capabilities.md](capabilities.md) for intent-to-import discovery, [api.md](
|
|
|
46
46
|
- A viewer's interest is the game's `replication: { aoiRadius, aoiAlways }`. The host asks the scene spatial index for entities within `aoiRadius` of the viewer's own entity, adds `aoiAlways` ids (a list or a function), and sends each connection a diff of only what entered, changed or left its view. A viewer with no entity yet holds every entity.
|
|
47
47
|
- A hosted world auto-saves a changed world at most once per `saveIntervalMs` (the node server and the browser host default to `DEFAULT_HOST_SAVE_INTERVAL_MS`, 1000); `0` saves after every changed tick, which copies the whole world 20 times a second. `bun scripts/multiplayer-load.ts` reports host ms per tick, and bytes allocated per tick under `BUN_JSC_useGC=false`.
|
|
48
48
|
- A module that throttles itself with `priority` still gets a projected baseline each tick instead of diffs.
|
|
49
|
-
- Measure with `bun scripts/multiplayer-load.ts` (bytes and host ms per tick for N clients and E entities) and `bun scripts/multiplayer-two-clients.ts` (a real game, two clients).
|
|
49
|
+
- Measure with `bun run perf server` (N players posing in one room: host wall and CPU ms, MB and messages out per game second, from presence fan-out), `bun scripts/multiplayer-load.ts` (world replication: bytes and host ms per tick for N clients and E entities) and `bun scripts/multiplayer-two-clients.ts` (a real game, two clients).
|
|
50
50
|
|
|
51
51
|
## Traps
|
|
52
52
|
|
|
@@ -52,6 +52,8 @@ Once a session resolves, the shell (`GameServerHost`'s internal player) wires it
|
|
|
52
52
|
|
|
53
53
|
A synced pose (`@jgengine/ws/protocol`'s `WsPose`/`WsPresenceRow`) carries an optional `appearance: Record<string, string | number | boolean>` alongside position/rotation — primitive-valued client-set tags (skin id, mount id, active emote, team color) riding the same pose-sync frames as position, so a remote player's cosmetic state updates on the existing presence channel instead of a second round-trip.
|
|
54
54
|
|
|
55
|
+
The ws host router (`createHostRouter`) sends each room's full presence roster at most once per pose interval (`poseRules.minIntervalMs`, 100 ms by default), however many players move: the first move in a quiet room goes out at once, later moves and appearance changes ride the roster that closes the window, and a player's first pose or a leave goes out immediately. Every broadcast (presence, chat, voice) is encoded once per codec and the same frame goes to every subscriber, so a room's cost grows with players × subscribers, not with its cube.
|
|
56
|
+
|
|
55
57
|
**Server side** — `@jgengine/convex/server` ships an entire authoritative Convex backend as factories, not a template to copy: `jgengineTables()` (schema spread), `createGameServerFunctions({ runtimes?, auth? })`, `createLeaderboardFunctions({ auth? })`, `createPresenceFunctions({ auth?, freshWindowMs?, idleCutoffMs?, poseRules?, resolveSpawn? })`, `createChatFunctions({ auth?, historyLimit?, maxBodyLength?, minIntervalMs? })`, and `jgengineCronSpecs()` (tick/flush interval metadata for a `crons.ts`). A consumer's `convex/` directory is ~25 lines: `schema.ts` (`defineSchema({ ...jgengineTables() })`), four one-line re-export files (`runtime.ts`, `leaderboard.ts`, `presence.ts`, `chat.ts`, each just calling its factory), and `crons.ts` registering the tick (1s) + flush (60s) internal mutations — see `examples/convex-host` for the reference shape. No game-specific code lives there; any JGengine game can point at the same deployment. Games without a registered `GameRuntime` fall back to a no-save runtime that only understands `engine.ping`; pass `createGameServerFunctions({ runtimes: [createGameRuntime({ gameId, commands, loop, save })] })` to make `runCommand` / tick / save actually do something. When a host mutation must pair snapshot work with its own table writes in one transaction, use the factory's `helpers` (`loadSnapshot`, `applyCommand`, `persistSnapshot`, `runCommand` — same registry and auth mode) or the standalone `loadServerSnapshot` / `persistServerSnapshot` exports.
|
|
56
58
|
|
|
57
59
|
**How big a hosted world gets.** One server is one Convex document holding the world row, the roster, and every member's session state, so member count is a per-transaction cost. `slotsPerServer` is capped at `JG_MAX_MEMBERS_PER_SERVER` (256) and `createGameServerFunctions` throws at wiring time on anything larger — a bigger number was never a bigger world, just a document that overflows later. World geometry that outgrows one row goes in `jgWorldChunks`: hydrate by key (`loadServerSnapshot(ctx, server, runtime, { players, chunkKeys })`), read from clients with the generated `getChunks`, and key cells with `chunkKeyAt` / `chunkKeysInRadius` from `@jgengine/core/runtime/worldChunks`. Mutate chunks through `setChunk` / `updateChunk` / `deleteChunk` — a raw `snapshot.chunks[key] = …` is never marked dirty and never persists.
|