This page is a living record of documentation completeness against the Plume3D engine source. Update this file whenever the engine gains new features or doc pages are added/updated.
Engine version at last review: 2026.1.1 — components; Engine.Version() returns the string 202601.001 (YYYYMM.PPP, version.h)
Engine source: Plume3d/Source/Plume3d
Last reviewed: 2026-08-01 (delta — Blender-authored combat on Jolt sensor bodies (engine PLM-198, ADR 0069) — COMPLETE. The combat substrate’s detection is re-architected onto Jolt sensor bodies (supersedes ADR 0066’s shape-cast detection; the “engine detects, game owns health” boundary + opaque payload are unchanged). A hit/hurt box is now a kinematic Jolt sensor riding its bone; Jolt reports the overlap and the engine partitions the contact drain into combat events. Hit/hurt geometry is authored in Blender — a mesh given a Passive+“Animated” rigid body, bone-parented, placed in a dedicated Plume_Hitboxes / Plume_Hurtboxes collection (object name = tag); blend_load surfaces bounded BlendCombatVolumes that instantiate turns into bone-attached sensor boxes. No NEW Wren class — additive methods on existing classes: Combat.onStay/onExit (lifecycle beside onHit), hitbox.independent/reHitSeconds (opt-out of per-owner swing-dedup / re-strike a persisting overlap — flurry & DoT), hitbox.bindController/hurtbox.bindController (link a Blender-authored box to the character’s AnimController), node.hitboxes/node.hurtboxes (tag→box maps), node.attachToBone/detachFromBone (engine-driven weapon/VFX socket). A Plume3D Blender add-on Combat Volumes panel one-clicks the authoring convention. /api/combat rewritten for the sensor model + Blender authoring; new /examples/combat-blender-demo (apps/combat_blender_demo — two Fighters with Blender-authored volumes, a lunge lands via Combat.onHit; render-verified 2 strikes); /examples/combat-demo reconciled (hand-built boxes, sensors). Landing Action Combat card updated. The .blend combat parse is a NEW untrusted-input surface → blocking /security-review PASSED (bounded volume/hull caps + field-offset checks; zero confirmed findings). Caveats: Jolt sensors have no CCD, so a one-frame teleport past a thin target can tunnel (realistic bone-swings caught frame-by-frame; Physics.castShape stays the swept one-shot) — tracked PLM-221; a volume rides its bone origin, not the authored mesh offset — PLM-222. Regression fixed: combat sensor bodies are now excluded from Physics.* world queries (a bullet raycast must not hit an invisible hitbox) — test_combat_queries. Headless tests: test_combat_hitbox (sensor mechanism), test_combat_wren (lifecycle/attachToBone/instantiate/independent/reHitSeconds), test_blend_load (combat-volume parse), test_combat_queries (query-exclusion). No new Wren class → 88 classes; +1 app (combat_blender_demo → 89). Suite 572/572, 0 warnings under -Werror. Block CMB — Blender-authored hitboxes COMPLETE on feat/blender-hitboxes. Engine-side Hyperscale/projects/plume3d.md, Block CMB + ADR 0069)
Prior: 2026-08-01 (delta — Data-driven behaviour-tree authoring for the GUI editor (engine PLM-211…219, ADR 0068) — COMPLETE. A GUI can’t draw a Wren closure, so a data-authored tree references named leaves + typed params; the engine runs the tree, the game owns HP/phase policy (like combat + LSV). Additive to ADR 0065 (the Bt.leaf closure surface is unchanged). Bt.registerLeaf(name, paramSpec) { |bb,dt,params| } — a named leaf the editor lists + a .bt.toml references (arity-3; params is a bound Map). Bt.leafCatalog — the registered names + schemas (the editor palette). Bt.load(path) — build an engine-ticked BehaviourTree from a hardened, bounded, cycle-checked .bt.toml (unknown-leaf / bad-param / cycle → Bt.loadError, tree left intact). New 1 Wren class PhaseEvaluator.evaluate(currentPhase, health01, elapsed, thresholds) — a forward-only boss-phase index the game feeds health01 into (the engine never reads HP) + writes to a blackboard int a phase_switch node reads; a mini-boss is 2 phase entries, a boss 4+, same data machinery. New /api/behaviour-tree-authoring page + /examples/bt-editor (apps/bt_editor — palette from the live catalog, canvas with node drag, a schema-driven param inspector, a PhaseSwitch node, subtree delete, save→.bt.toml with a Bt.load round-trip validation, and a live Run 1 tick preview — the richer UX is PLM-218) + /examples/bt-boss-demo (apps/bt_boss_demo — a phaseless enemy, a 2-phase mini-boss, a 4-phase boss, all one schema differing only in data; PLM-219). Landing Behaviour-Tree Authoring card added; Scripting class list +1. The .bt.toml loader is a NEW untrusted parser → blocking /security-review PASSED (injection-proof named-leaf model, atomic apply, TOML_EXCEPTIONS 0). Headless tests: test_ai_behaviour_tree (Parallel/Succeeder), test_ai_phase, test_bt_asset, test_bt_toml (malicious corpus), test_bt_load_wren (registerLeaf + load + params flow + GC-safety + phase_switch). +1 Wren class (PhaseEvaluator → 88), +2 apps (bt_editor + bt_boss_demo → 88). Suite 566/566, 0 warnings under -Werror. Block BTA COMPLETE (PLM-211…220) on feat/bt-authoring. Engine-side Hyperscale/projects/plume3d.md, Block BTA + ADR 0068)
Prior: 2026-08-01 (delta — Light, Sound & Vision as mechanics (engine PLM-199/203/206, ADR 0067) — IN PROGRESS. The Hadal P1 identity pillar: the engine SAMPLES, the game OWNS the policy (stealth, HP, detection — no rules ship in the engine). 3 new Wren classes + 1 method, over device-free unit-tested cores. Illumination.illuminanceAt(scene, x,y,z, mask) → a continuous 0..1 “how lit is this point” (per-light falloff matching the render — reads-lit == looks-lit — with a masked-LOS occlusion; no binary helper, the game thresholds it; bidirectional — your lamp lights you). FogOfWar — a world-XZ visibility field: configure bakes an occluder grid from static bodies, reveal(x,z,radius) (radius scaled by illuminance = darkness shrinks your view), state→0/1/2, persistent Explored, both team + per-player, + a wall-penetrating sonar channel (mark/tick/isRevealed). NoiseBus.emitOccluded(scene, …, mask) — a wall between a sound and a listener blocks it (the same wall muffles audio + shrinks what the AI hears). SoundEmitter — unifies the audible (a Source) + audible-to-AI (a NoiseBus) sides. New /api/light-sound-vision page + /examples/lsv-demo (apps/lsv_demo — a player crosses light/shadow while a HUD meters EXPOSURE + NOISE). Landing Light, Sound & Vision card added; Scripting class list +3. Headless Wren tests: test_illumination/_wren, test_visibility_field/test_fog_wren, test_audio_occlusion, test_noise_occlusion_wren. +3 Wren classes (Illumination/FogOfWar/SoundEmitter → 87), +1 app (lsv_demo → 86). Suite 546/546, 0 warnings under -Werror. Also Source.setOcclusion(gain, gainHF) — the audible muffle (the same wall that shrinks AI hearing muffles the audio; broadband gain + a per-source EFX low-pass). DONE since: PLM-202 (audio muffle) + PLM-207 (long-range — satisfied by architecture: audio sources are render/stream-independent). STILL LANDING (tracked PLM-210, renderer/refactor, no Wren change): the fog-of-war on-screen render composite (fog is queryable data + HUD today, not yet a world-space screen overlay) + the fog WrenVm-ownership refactor. Engine-side Hyperscale/projects/plume3d.md, Block LSV + ADR 0067)
Prior: 2026-08-01 (delta — Action combat: hit detection, the game owns health (engine PLM-196, ADR 0066). The action-combat substrate for Block CMB (Hadal). 5 new Wren classes in the engine module — the engine DETECTS hits, the game OWNS all health. Shape (capsule/box/sphere — immutable, reused by the box factories + queries). Hitbox / Hurtbox — bone-attached boxes (capsule/box/sphere(node, controller, bone, …)) with .layer/.tag/.owner/.payload/.enabled; a hitbox never strikes its own owner, strikes each victim once per swing, and Hitbox.bindWindow(controller, name) gates it to an animation hit-window (authored as Blender pose-markers). A disabled hurtbox is i-frames. Hit (read-only: attacker/victim/hitboxTag/point/normal/payload). Combat — Combat.onHit(scene, fn): the engine resolves overlaps each frame (swept, so a fast attack can’t tunnel a thin target) and calls fn(hit) at the tail of update() — no game-called tick (it would re-enter the Wren VM). There is deliberately no HP/damage/death type in the engine — the opaque payload (a game number set on the hitbox, carried into the Hit untouched) is the only wire across the health boundary; what a hit costs is 100% the game’s Wren. Plus 3 swept/overlap/pierce queries on Physics: castShape (swept, all bodies crossed), overlapShape (at-pose narrow-phase), raycastAll (piercing) → a List of RaycastHit. New /api/combat page + /examples/combat-demo (apps/combat_demo — a sword hitbox lunges at a body hurtbox; Combat.onHit reads the payload as damage off a plain Wren HP field; death disables the hurtbox). Landing Action Combat “What’s in place” card added; the Scripting class list gains the 5 classes. Six headless Wren tests (test_combat_wren) self-validate strike/dedup/i-frames/owner-layer/swept/queries/GC-safety. +5 Wren classes (Shape/Hitbox/Hurtbox/Hit/Combat → 84), +1 app (combat_demo → 85). Suite 524/524, 0 warnings under -Werror. Engine-side Hyperscale/projects/plume3d.md, Block CMB + ADR 0066)
Prior: 2026-07-31 (delta — Scriptable AI: behaviour trees + decision/FSM + noise (engine PLM-189, ADR 0065). A game can now author enemy AI in Wren (not just script it imperatively) — the follow-up ADR 0057 explicitly deferred. 19 new Wren classes in the engine module, thin bindings over the tested modules/ai cores. Behaviour tree — BehaviourTree (new(), setRoot(node), enabled=, blackboard(); the engine ticks it — no tree.tick, because a game-called tick would re-enter the Wren VM, which is forbidden), Bt factories (sequence/selector/inverter/repeat/cooldown/leaf, leaves are closures { |bb, dt| … } → BtStatus), BtNode (opaque, consumed-on-use), Blackboard (typed bool/int/number/string store). Decision / memory — LostTargetTracker lost→search→give-up FSM (+TargetState/LoseTargetMode/GiveUpMode), AggroTable (+static shouldSwitchFocus commitment gate), Tactic distance-band selection, StatGate (+GateWhen/GateEffect), TensionModel L4D pacing director (+TensionPhase, +static threatScoring). Noise — NoiseBus detect-by-sound (+static isAudible min-of-radii), AlertMemory, PainMemory. Plus two methods on the existing Ai class: Ai.nearestVisibleTarget(...) (range + FoV + native masked-raycast LOS + nearest, in one call — no Wren callback, so it’s leaf-safe) and Ai.tickInterval (the engine’s tree cadence; 0 = per-frame, default 10 Hz). /api/ai extended with all of it; /examples/ai-hunters reworked to drive the full loop from one Wren tree (perceive by sight+sound → navmesh-chase to a ring slot + tactic → lose → search last-known → give up; render-verified, hunters coloured by FSM state). Landing AI & Navigation “What’s in place” card updated; the Scripting class list gains the new classes. Three headless Wren tests (test_ai_bt_wren, test_ai_decision_wren, test_ai_noise_wren) self-validate the surface. 79 Wren classes (was 60 — +19), 84 apps (unchanged — ai_hunters reworked, not added). Suite 501/501, 0 warnings. Engine-side Hyperscale/projects/plume3d.md, Block AI + ADR 0065)
Prior: 2026-07-31 (delta — Split-screen: per-viewport HUD overlays (PLM-190, CAM-18, ADR 0064). Additive to the existing Graphics (no new class; +1 app, split_hud_demo → 84 apps). Graphics.perViewportOverlays(on) — when set and the frame has split-screen viewports, the retained-UI HUD is replayed into each cell (scissored) instead of drawing once full-screen, so a per-player HUD (health bar, score) stays inside its own region and never spills across a divider. The renderer builds the UI batches once, then replays them into each viewport’s sub-rect (a sub-rect VkViewport scales the full-window HUD in + a matching scissor clips it). Render the HUD once, full-window; the engine handles the per-cell replay. New /examples/split-hud (apps/split_hud_demo — a 4-way grid where a .ui HUD appears in every cell, clipped; render-verified). /api/graphics gains the perViewportOverlays method. Scope: screen-space retained UI only (world-space sprites / world text are scene content; screen-space SDF text per viewport is a follow-up). No new Wren class → 60 classes; 84 apps (was 83 — +split_hud_demo). Suite 495/495, 0 warnings. Completes the PLM-190 remaining-items sweep (Node.setQuat, per-player input, per-viewport HUD). Engine-side Hyperscale/projects/plume3d.md, PLM-190 + ADR 0064)
Prior: 2026-07-31 (delta — Split-screen: per-player input routing (PLM-190). Additive to the existing SplitScreen (no new class/app). SplitScreen.perPlayerInput(on) (default on) — player i’s camera reads gamepad i (right stick = look, left stick + triggers = free-fly move) instead of the shared mouse/keyboard, so each split-screen player drives their own view; falls back to mouse/keyboard for any player whose gamepad is not connected, and off shares the input. Backed by CameraDirector.setPlayerIndex. /api/camera SplitScreen gains the perPlayerInput row. test_split_screen verifies the per-player routing + toggle. No new Wren class → 60 classes; no new app → 83 apps. Suite 495/495, 0 warnings. Engine-side Hyperscale/projects/plume3d.md, PLM-190)
Prior: 2026-07-31 (delta — Node: setQuat — exact, gimbal-free rotation (PLM-190, ADR 0063). Additive to the existing Node (no new class, no new app). Node.setQuat(x, y, z, w) sets a node’s local rotation as a quaternion — exact at every orientation, including ±90° pitch where Euler setRotation hits gimbal lock. The camera system writes it (CameraDirector write-back), so Camera.worldToScreen is precise for first-person / top-down rigs; any Euler setter reverts the node to Euler mode. The quat→matrix convention matches modules/camera’s mat4FromQuat, so the node’s world matrix agrees with the rendered view. /api/node gains the setQuat row. test_camera_math verifies the −90°-pitch case (matrix basis == mat4FromQuat, forward == (0,−1,0), Euler-setter revert). No new Wren class → 60 classes; no new app → 83 apps (both unchanged). Suite 494/494, 0 warnings. Engine-side Hyperscale/projects/plume3d.md, PLM-190 + ADR 0063)
Prior: 2026-07-31 (delta — Camera: raw Viewport primitive — picture-in-picture / minimap (PLM-190, CAM-19, ADR 0062). One new public Wren class in the engine module — Viewport — a first-class composite viewport (a screen region + its own camera) for picture-in-picture / minimap / security-cam, independent of SplitScreen, over the RTT composite path (ADR 0061). Viewport.new(scene) + fluent setters: rect(x,y,w,h) (screen fractions), z(order) (higher = on top; the main Cam view sits behind), opacity(a), border(width), borderColor(r,g,b), and a camera — follow(target) / topDown(target, height) (minimap) / orbit(target, radius) / firstPerson(head) / freeFly(). Each Viewport owns its own camera director; WrenVm::tickCameras folds the main Cam view in as a z-behind fullscreen base and composites the Viewports on top (compositeViewports=true). A Viewport that goes out of scope finalizes + stops rendering. New /examples/pip (apps/pip_demo — third-person main + a top-down minimap; render-verified: the minimap shows the four corner landmarks + the centred player). /api/camera gains a Viewport section; landing Camera card + the Scripting class list gain Viewport. test_camera_wren gains a Viewport case (every setter + each camera preset + the GC/finalizer path, 40 frames). 60 Wren classes (was 59 — +Viewport), 83 apps (was 82 — +pip_demo). Suite 493/493, 0 warnings. Completes PLM-190 (animated merge → RTT composite + angled → rect-peel → PiP). Engine-side Hyperscale/projects/plume3d.md, PLM-190 + ADR 0062)
Prior: 2026-07-31 (delta — Split-screen: animated rect-PEEL merge (PLM-190, ADR 0061). Additive to the frozen SplitScreen — no new class; +1 app (peel_demo → 82 apps). Over the RTT composite path (ADR 0061): SplitScreen.peel(on) makes the dynamic merge slide each viewport’s rect from its split cell toward fullscreen (z-stacked, the “views expand together” Kronnect look) instead of the default fixed-rect camera-converge blend — the rects overlap mid-slide, which the axis-aligned path can’t do, so peel forces composite while a merge is in flight. Each camera still converges to the group pose, so the overlapping slides read as one coherent merge. New /examples/peel (apps/peel_demo — 2 players; panels slide together to a merged view; render-verified). /api/camera SplitScreen gains peel(on); landing Camera card notes it. test_split_screen gains a peel case (mid-merge the cells are wider than the base half + z-stacked + composite forced). 59 Wren classes, 82 apps (was 81 — +peel_demo). Suite 492/492, 0 warnings. Remaining PLM-190: PiP / raw Viewport primitive. Engine-side Hyperscale/projects/plume3d.md, PLM-190 + ADR 0061)
Prior: 2026-07-31 (delta — Split-screen: RTT composite path + angled (diagonal) 2-player split (PLM-190, ADR 0061). The v1 axis-aligned sub-rect path structurally can’t do a diagonal divider (a half-plane isn’t a rectangle), overlap, or picture-in-picture. Add an opt-in RTT composite path: each viewport renders to its own offscreen colour target (an offscreen render pass render-pass-compatible with the mesh pipelines, so they’re reused unchanged), then the renderer composites the targets as arbitrary polygons. Additive to the frozen SplitScreen: compositeMode(on) forces it (the composited disjoint split is pixel-equivalent to the fast path — always safe), and a new SplitLayout.angled — a Kronnect-style 2-player diagonal split whose divider is perpendicular to the on-screen direction between the players (it rotates as they move around each other); each player renders full-screen and the two complementary half-planes are composited (Sutherland–Hodgman clip), auto-enabling compositeMode. New /examples/angled-split (apps/angled_split_demo — 2 players orbiting, the diagonal divider rotating; render-verified sky/horizon/players undistorted). /api/camera SplitScreen + SplitLayout updated; landing Camera card notes angled split. test_split_screen gains an angled-layout case (complementary half-plane polygons, full-screen UV, one divider). 59 Wren classes (no new class — additive methods on SplitScreen + a SplitLayout constant), 81 apps (was 80 — +angled_split_demo). Suite 491/491, 0 warnings. The RTT path also unlocks the still-ahead animated rect-peel + PiP / raw Viewport primitive. Engine-side Hyperscale/projects/plume3d.md, PLM-190 + ADR 0061)
Prior: 2026-07-31 (delta — Split-screen: animated merge/split — camera-converge, no RTT (PLM-190, ADR 0060). Additive to the frozen SplitScreen (ADR 0059) — no new class, no new app. The dynamic merge/split is no longer a hysteresis hard-switch: it is an animated blend over a transitionDuration. Two new members on SplitScreen: transitionDuration(s) — seconds the merge↔split blend takes (0 = the old instant switch; default 0.4) — and mergeFraction (Num getter, 0..1: 0 split → 1 merged, the animation progress; isMerged still reports the settled target). During the blend the split cells are held in place while each cell’s camera eases from its player pose toward the shared group pose (smoothstep) and the dividers fade, then the cells collapse to one fullscreen group viewport — and because every cell converges to the same group pose, they show identical pixels at collapse, so there is no overlap artefact and no render-to-texture. /api/camera SplitScreen methods + scope note updated; /examples/split-screen reworked (single sweep grid→centre; new split_transition mid-blend screenshot). The Kronnect rect peel + PiP/overlap + raw Viewport primitive still need RTT (the next PLM-190 unit). Headless test_split_screen gains the animated-blend + transitionDuration(0) back-compat cases. 59 Wren classes, 80 apps (both unchanged — additive methods on an existing class + demo). Suite 490/490, 0 warnings. Engine-side Hyperscale/projects/plume3d.md, PLM-190 + ADR 0060)
Prior: 2026-07-31 (delta — Camera: occlusion collider + split-screen with dynamic merge/split (Camera epic, ADR 0059). Two capabilities on top of the rig system. Occlusion collider — additive setter CameraRig.collider(padding): the camera pulls in front of any wall that would occlude the target (masked physics raycast, ADR 0056), keeping padding clearance; new /examples/camera-collider (render-verified: wall occludes vs. camera pulled in). Split-screen — two new Wren classes: SplitScreen (SplitScreen.new(scene, players) → one third-person camera per player, layout/distance/height/dynamic/mergeDistance/splitDistance/isMerged/playerCount) and SplitLayout (auto/horizontal/vertical/grid). The renderer draws the scene once per viewport into its screen sub-rect (each with its own mesh-UBO instance; an empty viewport list is the byte-for-byte legacy single-camera path) with clear-attachment dividers; dynamic merge/split fuses the views into one group-framed camera (bounding-sphere → fov pull-back) when players cluster and splits them apart past a hysteresis band. New /examples/split-screen (apps/split_screen_demo — render-verified 4-way grid ⇄ merged group view). Landing Camera card updated; /api/camera gains SplitScreen/SplitLayout sections + the collider setter. v1 scope: ≤4 viewports, overlays full-screen, hysteresis merge/split (animated peel = additive v2, PLM-190). Headless test_camera_collider + test_split_screen; render-verified demos. 59 Wren classes (was 57 — +SplitScreen +SplitLayout), 80 apps (was 78 — +camera_collider +split_screen_demo). Suite 488/488, 0 warnings. Engine-side Hyperscale/projects/plume3d.md, Camera epic + ADR 0059)
Prior: 2026-07-31 (delta — Camera system: Cinemachine-like rigs + blending + noise + shake (Camera epic, ADR 0058). A streamlined, code-driven camera that replaces the ~185-line FPSCamera copy-pasted across ~9 demos, with no draw() plumbing — the engine auto-ticks a per-scene director that pushes the view/projection itself. Four new Wren classes in the engine module: Cam — a static factory for one-call rigs (thirdPerson(scene, target), firstPerson(scene, head), topDown(scene, target, height), orbit(scene, target, radius), freeFly(scene)) plus setActive(rig) / blendTo(rig, seconds, ease) / active(scene); CameraRig — the returned rig, with fluent setters (distance/damping/offset/fov/near/far/sensitivity/noise/height/radius/moveSpeed/shoulder/setActive/blendTo); Impulse — emit(scene, strength, x, y, z) decaying, distance-attenuated shake; and Ease — blend curves (linear/easeIn/easeOut/easeInOut/cut). Additive — the existing Graphics.setViewMatrix single-camera path and the low-level Camera class are untouched, and coexist last-write-wins. Device-free modules/camera solver core (unit-tested: math/damping/blend/body/aim/noise/impulse) + a native CameraDirector; test_camera_wren drives the whole surface headless; apps/camera_showcase render-verified (third-person follow → blend to orbit → shake). Existing /api/camera page extended with the rig system (low-level Camera kept below); new /examples/camera-showcase. Landing Camera “What’s in place” card added; the Scripting class list gains Cam/CameraRig. Freeze-staging: the Cam factory + named rigs + Ease/Impulse + blend semantics are frozen; the CameraRig setter param names/order are experimental for one release. 57 Wren classes (was 53 — +Cam +CameraRig +Impulse +Ease), 78 apps (was 77 — +camera_showcase). Suite 482/482, 0 warnings. Engine-side Hyperscale/projects/plume3d.md, Camera epic + ADR 0058)
Prior: 2026-07-31 (delta — Block-AI Wren surface: navmesh + surround-ring + AI math (engine PLM-187, ADR 0057). The scripted AI capstone for Block AI (enemy AI for Hadal, a top-down ARPG). Three new Wren classes in the engine module: Nav — a Recast/Detour navmesh (Nav.new(), bake(verts, tris) from a flat triangle soup → Bool, findPath(fx,fy,fz, tx,ty,tz) → a List of [x,y,z] waypoints that routes around blocked footprints); SurroundRing — an encounter-spacing solver (assign(id, weight, preferredRange, isMelee, desiredAngleDeg) → [angleDeg, radius, standby] sticky slots so a pack encircles instead of stacking on one point; release(id)); and Ai — decision-core math (Ai.stopDistance(attackRange, bodyRadius), Ai.inFieldOfView(ex,ey,ez, fx,fy,fz, tx,ty,tz, fovDeg)). Genre-general — the navmesh models walkable surfaces (top-down / third-person / FPS), not a top-down- or swim-specific abstraction. Thin bindings over device-free, unit-tested C++ cores; a Wren integration test (test_ai_wren) drives the whole surface headless (bake + cross-mesh path, distinct/stable/non-standby ring slots, the two Ai helpers). New /api/ai page + /examples/ai-hunters (apps/ai_hunters — four hunters detect a target, path around a central obstacle via the navmesh, and take distinct slots around it via the ring solver; render-verified encirclement). Landing AI & Navigation “What’s in place” card added; the Scripting class list gains Nav/SurroundRing/Ai, and the Physics card now names the spatial-query surface (overlapSphere + collision-layer masks from PLM-180). Composes with ADR 0056’s Physics.overlapSphere / Raycast.fromPointMasked. 53 Wren classes (was 50 — +Nav +SurroundRing +Ai; Nav had not been synced yet), 77 apps (was 76 — +ai_hunters). Gate 457/457, 0 warnings. Engine-side Hyperscale/projects/plume3d.md, Block AI + ADR 0057)
Prior: 2026-07-31 (delta — Physics: gameplay collision layers + spatial queries (engine PLM-180, ADR 0056). The sensory prerequisite for the new Block AI (enemy AI for Hadal, a top-down ARPG). A gameplay collision-layer bitmask on bodies — distinct from Jolt’s simulation ObjectLayer, it only filters queries, not what physically collides. On the existing Physics class (no new Wren class): setCollisionLayer(node, mask) / getCollisionLayer(node), overlapSphere(scene, x, y, z, radius, mask) → a List of nodes whose body center is within radius and whose layer intersects mask (the “who is near me” AI primitive), and Raycast.fromPointMasked(scene, o…, d…, maxDist, mask) — the layer-filtered line-of-sight cast (makes the previously-stub layerMask real). /api/physics gains a Spatial queries & collision layers section. Verified headless (Jolt unit test) + Wren integration test; gate 448/448, 0 warnings. No new Wren class → 50 classes; no new app → 76 apps (both unchanged). Engine-side Hyperscale/projects/plume3d.md, Block AI + ADR 0056)
Prior: 2026-07-30 (delta — Material textures: full PBR map set on script meshes + the MoltenVK render fix (engine PLM-021 / PLM-177, ADR 0055). The Mesh material-texture API (merged earlier as groundwork) is now ratified + documented + demoed end-to-end. On the existing Mesh class (no new Wren class): setBaseColorTexture/setMetallicTexture/setRoughnessTexture/setNormalTexture/setAoTexture/setEmissiveTexture(texture) + the scalar factors setColor/setMetallic/setRoughness + clearMaterial. The engine binds the six maps as a per-material descriptor set at set 1, bindings 1..6; an unassigned map falls back to a no-op default (white / flat-normal / black). Mesh shaders are per-app, so /api/mesh documents the binding contract. New /examples/material-textures (apps/material_textures — a script UV cube assigns all six maps from Wren, drawn with a reference shader that samples set 1 1..6 with derivative-TBN normal mapping). Landing Rendering “What’s in place” card updated (full material-map set + normal mapping). MoltenVK root cause (PLM-177): a per-app PBR shader that references the set-0 shadow array unconditionally fails to compile under Metal argument buffers unless the app enables shadow mapping (the engine only declares those bindings then); the material reference shader routes around it by keeping to set-1 samplers + lighting. Follow-ups filed: PLM-178 (make the set-0 layout always match the shader ABI) + PLM-179 (linear texture format for non-color maps). No new Wren class → 50 classes; +1 app (material_textures) → 76 apps. Engine-side Hyperscale/projects/plume3d.md, Block R + ADR 0055)
Prior: 2026-07-30 (delta — Effect wet/dry mix — audibility fix (engine PLM-160, ADR 0054). Bug the owner hit testing audio_effects_demo: added effects were inaudible. Diagnosed (loopback render + real-output capture) as correct-but-subtle — an OpenAL aux-slot effect is a parallel wet on top of the full-volume dry, and OpenAL caps the wet at unity, so it’s imperceptible under loud audio. Fix: a per-group wet/dry mix that ducks the dry so the wet stands out. Additive to the existing MixerGroup (no new Wren class): setEffectMix(mix) — 0 (default) = subtle parallel wet (unchanged), →1 = dry fully ducked (insert-style). Folded into the PLM-157 direct filter (one AL_DIRECT_FILTER per source). /examples/audio-effects-demo now sets a prominent mix per effect so each toggle is clearly audible. Also closes a test gap: the first regression test that asserts effects reach the audio output (renders through ALC_SOFT_loopback — CI-safe — and checks a reverb adds a tail the dry lacks + the mix ducks the dry). No new Wren class → 50 classes; no new app → 75 apps (both unchanged). Engine-side Hyperscale/projects/plume3d.md, Block AX2 + ADR 0054)
Prior: 2026-07-30 (delta — Mixer effects & filters: contract freeze + capstone (engine PLM-159, ADR 0053). Closes the audio-effects epic. No new API — ADR 0053 freezes the per-group effects + filters Wren surface (the MixerGroup effect/filter methods + MixerEffect) as a public contract (consumer games are Wren-only, so this is what they depend on); a future signature change now needs an ADR, while additive values (a new addEffect type or setParam name) stay free. /api/mixer gains a Stable API note. The GUI /examples/audio-mixer-studio is now the complete Unity-parity console — its Music bus gains a reverb preset cycle + a low-pass toggle beside volume/mute/solo/Master/ducking/Save. No new Wren class → 50 classes; no new app (extended audio_mixer_studio) → 75 apps (both unchanged). Out of scope (a later epic): software-submix v2 — parameterized compressor, arbitrary param-EQ, swept resonant filters, normalize, long delays, send-matrix. Engine-side Hyperscale/projects/plume3d.md, Block AX2 + ADR 0053)
Prior: 2026-07-30 (delta — Per-group direct-path filters (engine PLM-157, ADR 0052). The last native OpenAL EFX surface: a mixer group can carry one low/high/band-pass filter on its sources’ dry signal (AL_DIRECT_FILTER) — Unity’s mixer Lowpass/Highpass — separate from and composing with the effect chain. Additive to the existing MixerGroup (no new Wren class): setLowPass(gain, gainHF) (muffle), setHighPass(gain, gainLF) (thin), setBandPass(gain, gainLF, gainHF), clearFilter(). Gains are linear 0..1 shelf gains (OpenAL filters are not a swept resonant cutoff — that stays a software-mix feature). The engine keeps one AL_FILTER per group (rebuilt on change) and re-points each routed source’s direct filter each frame; direct filters are copy-on-attach, so no in-use-slot dance. Needs ALC_EXT_EFX (no-op otherwise). New /api/mixer Filters section; /examples/audio-effects-demo gains muffle/thin/clear buttons. No new Wren class → 50 classes; no new app (extended audio_effects_demo) → 75 apps (unchanged). Device-backed test reconciles low/high/band + swap/clear + a filter coexisting with an effect. Completes the native EFX exposure (aux-slot effects PLM-153/154 + filters PLM-157); only the contract-freeze ADR remains (PLM-159). Engine-side Hyperscale/projects/plume3d.md, Block AX2 + ADR 0052)
Prior: 2026-07-30 (delta — Full OpenAL EFX effect suite (engine PLM-154, ADR 0051). Building on the reverb pipeline, every OpenAL EFX aux-slot effect is now exposed through the existing MixerGroup.addEffect(type) + MixerEffect.setParam (no new Wren class). Accepted types (case-insensitive): reverb, chorus, echo, flanger, distortion, equalizer, frequencyshifter, vocalmorpher, pitchshifter, ringmodulator, autowah, compressor (unknown → null). Each starts from OpenAL’s defaults, so addEffect("echo") is already a working echo; tune it with setParam(name, value) (per-effect param tables now on /api/mixer). Calling addEffect more than once on a group builds an ordered serial chain. The backend applicator is table-driven (one {name → AL enum, int/float} registry per type); mixerEffectTypeFromString is the single source of truth for type names. Compressor is on/off only and Equalizer is a fixed 4-band (a parameterized compressor / arbitrary EQ stay a later software-mix feature). Needs ALC_EXT_EFX (no-op otherwise). /api/mixer Effects/MixerEffect sections rewritten for the full suite; new /examples/audio-effects-demo (apps/audio_effects_demo — every effect + a serial chain on real music). Landing Audio card updated. No new Wren class → 50 classes; +1 app (audio_effects_demo) → 75 apps (was 74). Device-backed test reconciles all 12 types + a chain. Engine-side Hyperscale/projects/plume3d.md, Block AX2 + ADR 0051)
Prior: 2026-07-30 (delta — Mixer effects: per-group reverb via OpenAL EFX (engine PLM-153, ADR 0050). A mixer group can host audio effects (OpenAL EFX); the first is reverb (EAX). One new Wren class MixerEffect (a non-owning effect handle) + additive MixerGroup methods: group.addReverbPreset(name) (Cave/Hangar/Underwater/ConcertHall/… → a MixerEffect), group.addEffect("reverb") (→ handle, null for unknown types), group.clearEffects(), group.effectCount; effect.setPreset(name) + effect.setParam(name, value) (named reverb params: decaytime, gain, diffusion, …). The engine builds the OpenAL EFX aux-slot chain behind the mixer (reconciled per frame; in-place param updates avoid rebuilds/leaks). Needs a device with ALC_EXT_EFX — safe gain-only no-op otherwise. Adversarially reviewed (an in-use-slot leak + a latched-error heisenbug found + fixed). /api/mixer gains Effects + MixerEffect sections; new /examples/audio-reverb-demo (apps/audio_reverb_demo — looping music with live reverb-preset switching). Landing Audio card + Scripting list updated. The rest of the EFX suite (echo/chorus/flanger/distortion/pitch/EQ/filters/…) follows in PLM-154+. 50 Wren classes (was 49 — +MixerEffect), 74 apps (was 73 — +audio_reverb_demo). Engine-side Hyperscale/projects/plume3d.md, Block AX2 + ADR 0050)
Prior: 2026-07-30 (delta — GUI audio-mixer example app (engine PLM-150). A wired GUI mixer console — new example app, no engine API change. apps/audio_mixer_studio routes a looping music bed + a one-shot SFX (the audio_3d clips) to Mixer.group("Music"/"SFX"), with a channel strip per group (volume slider + muted/soloed), a Master fader, a ducking toggle (duckUnder/clearDuck), and a Save mix button (Mixer.saveSettings() → settings.toml). Group defaults come from the app’s game.toml [Mixer]. Showcases the whole mixer stack (groups + gain/mute/solo + ducking + config + persistence) on one screen. New /examples/audio-mixer-studio. No new Wren class → 49 classes; +1 app (audio_mixer_studio) → 73 apps (was 72). Engine-side Hyperscale/projects/plume3d.md, Block AX2)
Prior: 2026-07-30 (delta — Mixer audio config: game.toml [Mixer] defaults + player settings.toml (engine PLM-149, ADR 0049). Default audio-mixer group volumes in game.toml — a [Mixer] section (master + [[Mixer.group]] name/volume/mute) applied to the Mixer at startup (groups pre-created), documented on /configuration. Plus a player-override settings.toml in the app dir (same shape), auto-loaded at startup after the game defaults (player wins); two new additive Mixer methods (no new class): Mixer.saveSettings() (write current volumes/mute) and Mixer.loadSettings() (re-apply now). /api/mixer gains a Defaults and player settings section. Internal --test harness (apps/_mixer_config_test, _-prefixed = not an example) verifies defaults-applied + settings round-trip. No new Wren class → 49 classes; no new example app → 72 apps (counts unchanged). Engine-side Hyperscale/projects/plume3d.md, Block AX2 + ADR 0049)
Prior: 2026-07-30 (delta — Native MIDI → SoundFont route (engine PLM-136, ADR 0048). Play a SoundFont from a USB-MIDI keyboard with one call, additive to the existing Midi class (no new Wren class). Midi.route(instrument) — after Midi.openInput, the engine feeds every incoming note / controller / pitch-bend / program-change event straight to a SoundFontInstrument in C++ (the native low-latency route, mapped on each event’s MIDI channel), no per-note Wren; events still reach Midi.poll for gameplay. Midi.clearRoute() stops it (also auto-cleared if the routed instrument is collected). /api/midi gains a Playing a SoundFont from a keyboard section; new /examples/midi-soundfont-demo (apps/midi_soundfont_demo — routes a keyboard to the piano, with a computer-key fallback). Completes Block M. Live device play is verified by hand with a MIDI keyboard (HUMAN-017); the route/clear wiring is CI-tested. No new Wren class → 49 classes; +1 app (midi_soundfont_demo) → 72 apps (was 71). Engine-side Hyperscale/projects/plume3d.md, Block M + ADR 0048)
Prior: 2026-07-30 (delta — Audio ducking / sidechain (engine PLM-148, ADR 0047). The V1 mixer effect: a group ducks (drops in level) while another group is making sound — the classic music-under-dialogue. Additive to the existing MixerGroup (no new Wren class). group.duckUnder(triggerGroup, amountDb, attack, release) — while any source in triggerGroup’s subtree is playing, group ramps down to amountDb (e.g. −12; 0/positive = no duck) over attack s and back to unity over release s. group.clearDuck() removes it; group.duckGain reads the live envelope (1 = not ducked) for a meter. Activity-driven (fires on a playing source, not keyed to loudness — a level-metered sidechain compressor is the later software-submix v2); the duck composes with group volume, and mute/solo still win. Applied every frame by the engine. /api/mixer gains a Ducking section; new /examples/audio-ducking-demo (apps/audio_ducking_demo — hold Space so a voice cue ducks the music). Completes Block AX. No new Wren class → 49 classes; +1 app (audio_ducking_demo) → 71 apps (was 70). Engine-side Hyperscale/projects/plume3d.md, Block AX + ADR 0047)
Prior: 2026-07-30 (delta — Audio mixer groups: Mixer + MixerGroup (engine PLM-147, ADR 0046). Unity-style audio buses: two new public Wren classes route any sound — SFX, Music, Ambience, and SoundFont instruments — through named groups with per-group volume, mute, and solo. Mixer (static facade): Mixer.master (the root group), Mixer.group(name) (get-or-create under Master), Mixer.groupUnder(name, parent) (nested). MixerGroup: name, volume / volume= (linear gain, clamped ≥ 0), muted / muted=, soloed / soloed=, addSource(source) (route a Source), addInstrument(instrument) (route a SoundFontInstrument). Each sound plays at its own volume × its group’s effective gain (group gain × ancestors’, gated by mute/solo — Unity-like solo plays only the soloed subtree), applied every frame; an unrouted sound plays under Master (unity), so adding the mixer changes nothing until you route + adjust. A source’s own setVolume is preserved (the group multiplier never leaks into getVolume). Hard cutover (PLM-147): SoundFontInstrument.update() was REMOVED — the engine now renders + queues instrument audio automatically each frame; games no longer pump it. New /api/mixer page + /examples/audio-mixer-demo (apps/audio_mixer_demo — Music/SFX/Ambience with live faders, mute, solo); landing Audio card updated; /api/soundfont + its example updated to drop update(). Ducking/sidechain is the next step (PLM-148). 49 Wren classes (was 47 — +Mixer +MixerGroup), 70 apps (was 69 — +audio_mixer_demo). Engine-side Hyperscale/projects/plume3d.md, Block AX + ADR 0046)
Prior: 2026-07-30 (delta — MIDI device input: Midi (engine PLM-135, ADR 0044). One new public Wren class in the engine module — Midi — reads live MIDI input (a USB / virtual MIDI keyboard) as normalized events. Static facade (like Net): Midi.inputDevices → a List of device-name Strings; Midi.openInput(device) (a Num index or a String name substring) → Bool; Midi.closeInput(); Midi.isOpen; Midi.openedDevice → String; Midi.poll() → a List of event Maps drained since the last call, each { "type": String, "channel": Num (0–15), "data1": Num, "data2": Num, "value14": Num } (type ∈ noteOn/noteOff/controlChange/programChange/pitchBend/channelPressure/polyPressure/clock/other; data1/data2 = note+velocity or controller+value; value14 = pitch bend 0–16383). Events are parsed + queued on the device’s callback thread and handed to Wren on the main thread each frame, so poll() is always safe. The note source is arbitrary — feed events to a SoundFontInstrument, gameplay, or anything. Desktop only (no-op on builds without a MIDI backend). Powered by libremidi (pinned v5.4.2). New /api/midi page + /examples/midi-input-demo (apps/midi_input_demo — a MIDI monitor). Landing MIDI input “What’s in place” card added. Live note-in from a physical device is verified with hardware (engine HUMAN-017); the parser + Wren binding + real enumeration are CI-tested with no device. 47 Wren classes (was 46 — +Midi), 69 apps (was 68 — +midi_input_demo). Engine-side Hyperscale/projects/plume3d.md, Block M + ADR 0044)
Prior: 2026-07-30 (delta — SoundFont instruments: SoundFontInstrument (.sf2 synthesis) (engine PLM-134, ADR 0043). One new public Wren class in the engine module — SoundFontInstrument — loads a SoundFont (.sf2) bank and plays instruments from it as real polyphonic audio, rendered through the streaming sink (PLM-133). SoundFontInstrument.new(path, sampleRate, channels) mount-loads a bank (PhysicsFS-sandboxed; channels = 1 mono / 2 stereo) and creates its own streaming source; check loaded (false if the bank was missing/unparseable — the object stays safe to call). Drive it from any note source (keyboard/gamepad/chart/MIDI) with noteOn(note, velocity) (MIDI note 0–127, velocity 0–1; polyphonic), noteOff(note), allNotesOff(), setPreset(index) (0…presetCount−1), controlChange(controller, value) (MIDI CC, e.g. 64 = sustain), pitchBend(value14) (14-bit, 8192 centered), setMaxVoices(n), setGain(db); read state via loaded / presetCount / presetName(i) / activeVoices. update() pumps audio and must be called once per frame (manual pump — automatic servicing is deferred to the audio-mixer epic). SoundFont banks are loaded resources, not committed — large licensed .sf2 files are git-ignored and fetched at runtime (owner decision); the demo/tests skip cleanly when absent. New /api/soundfont page + /examples/soundfont-demo (apps/soundfont_demo — a computer-keyboard piano); the landing Audio “What’s in place” card now lists SoundFont instrument playback. Live MIDI-device input is a separate upcoming capability (PLM-135) — do not claim it. 46 Wren classes (was 45 — +SoundFontInstrument), 68 apps (was 67 — +soundfont_demo). Engine-side Hyperscale/projects/plume3d.md, Block M + ADR 0043)
Prior: 2026-07-29 (delta — Audio: float-PCM streaming sink (engine PLM-133, ADR 0042). A way to stream synthesized / procedural float PCM to the audio device over time, additive to the existing Audio / Source classes (no new Wren class, no new Wren method beyond three additive members). Audio.newStreamingSource(sampleRate, channels) → a Source you feed over time (channels = 1 mono or 2 stereo; returns null on failure) — the counterpart to the one-shot Audio.newSource(path), which is unchanged. source.queueSamples(list) → queues a List of interleaved float samples in [-1, 1] (stereo interleaves L,R,L,R…; bounded to 1M samples/call) and (re)starts playback automatically as buffers are queued — the engine recycles finished buffers; returns Bool; a no-op on a non-streaming source. source.queuedBuffers → the number of buffers currently queued — poll it to pace refills (top up to ~4 each frame). Uses AL_EXT_FLOAT32 (float) with an int16 fallback; main-thread fed (top up each frame). It is the primitive a SoundFont/MIDI synth (Block M — PLM-134/136) renders into, and independently useful for procedural / streamed music. /api/audio gains a Streaming audio section (the three members, the interleaved-float format, the feed-each-frame pattern with a Wren example); new /examples/audio-streaming-demo (apps/audio_streaming_demo — a game streaming a procedural 220 Hz sine tone); the landing Audio “What’s in place” card now lists streaming float PCM. No new Wren class → 45 classes; +1 app (audio_streaming_demo) → 67 apps (was 66). Engine-side Hyperscale/projects/plume3d.md, Block M + ADR 0042)
Prior: 2026-07-29 (delta — Retained UI: text input — editable input field (engine PLM-142, ADR 0041). A new .ui element type, an editable single-line text field, additive to the existing Ui class + .ui format (no new Wren class, no new Wren method — created via Ui.createElement(doc, "input", parent) and read through the existing elem.text accessor). type = "input" — a focusable field: click it to focus (a caret appears), type to edit — insert / backspace / delete and Left/Right/Home/End caret movement are all UTF-8-aware (whole codepoints), and a press elsewhere blurs it. New fields: text (the initial/current value — read via elem.text), placeholder (a dimmed hint shown while empty), on_submit (a named handler fired on Enter → Game.<name>()), on_change (a named handler fired when the buffer changes), and caret_color (the caret bar colour). This is v1 — single-line, focus-by-click; selection, clipboard, multi-line, and IME composition are a documented follow-up (PLM-145) — do not claim them. /api/ui gains an Input element-type entry (the fields, focus-by-click, elem.text, the v1 scope); new /examples/ui-input-demo (apps/ui_input_demo — a Name field with initial text + a City field showing a placeholder); the landing Retained UI “What’s in place” card now lists text input. No new Wren class (Ui already exists) → 45 classes; +1 app (ui_input_demo) → 66 apps (was 65). Engine-side Hyperscale/projects/plume3d.md, Block U + ADR 0041)
Prior: 2026-07-29 (delta — Retained UI: layout groups — horizontal / vertical / grid (engine PLM-143, ADR 0040). A container .ui element can now auto-position its direct children by flow, additive to the existing Ui class + .ui format (no new Wren class, no new app, and no new Wren method — layout is authored in the .ui). New container fields: layout = "horizontal" | "vertical" | "grid" (Horizontal packs children left→right, Vertical top→bottom, Grid wraps every columns items — each row as tall as its tallest child; absent = anchor positioning, unchanged), spacing (px gap between items), padding = [L, R, T, B] (px insets on the content origin — L/T seed the flow cursor, R/B reserved for a future width-wrap), and columns (Grid only, ≥ 1). A layout group overrides only each child’s position; the child keeps its own size (from its offsets), and children of a flow-positioned element still anchor relative to its resolved rect, so layout groups compose with the anchor system (a grandchild centres inside a flow-placed cell). Existing .ui files load identically. /api/ui gains a Layout groups section (the four fields, the three modes, size-preservation + anchor composition); the landing Retained UI “What’s in place” card now lists layout groups. No new Wren class (Ui already exists) → 45 classes; no new app → 65 apps (counts unchanged). Engine-side Hyperscale/projects/plume3d.md, Block U + ADR 0040)
Prior: 2026-07-29 (delta — Retained UI: Progress + Radio widgets (engine PLM-141, ADR 0039). Two new .ui element types on the existing Ui class + .ui format (no new Wren class, no new app). type = "progress" — a display-only progress bar; it reuses the slider’s value/range model (value in [min, max], color track, fill_color fill) and has no handle and no pointer interaction. type = "radio" — a grouped toggle: it reuses the toggle’s checked/check_color/on_change plus a new group (string). Radios sharing the same non-empty group are mutually exclusive — clicking one selects it (checked = true) and deselects its group siblings; a radio never toggles back off. Both are polled through the existing UiElement.value/checked accessors and created via Ui.createElement(doc, "progress"|"radio", parent) — no new Wren methods (the group is authored in the .ui file). /api/ui documents both element types + the mutual-exclusion behaviour; the apps/ui_widgets demo + /examples/ui-widgets gain a progress bar + a Low/High radio group; the landing Retained UI “What’s in place” card now lists progress + radio. Combo/Dropdown, ScrollView, and Pills are explicitly deferred (PLM-144 — they need popup/scissor-clip/rounded-rect infrastructure the retained UI lacks); do not claim them. No new Wren class (Ui already exists) → 45 classes; no new app (ui_widgets already exists) → 65 apps. Engine-side Hyperscale/projects/plume3d.md, Block U + ADR 0039)
Prior: 2026-07-29 (delta — Retained UI: textured 9-slice + image-by-path + .ui.component sidecar (engine PLM-139/140, ADR 0038). Two related retained-UI features on the existing Ui class + .ui format (no new Wren class). (1) Textured 9-slice now renders (PLM-139). A .ui panel/button with nine_slice = true + border = [L,R,T,B] (texture px) and an image now draws as a real 9-slice — corners keep their pixel size, edges stretch on one axis, the center stretches both (the fields previously round-tripped but did nothing). (2) Image-by-path + .ui.component sidecar (PLM-140, ADR 0038 — additive extension to Frozen Contract #1). A .ui element can reference an image BY PATH — image = "ui/panel.png" (a mounted PNG, resolved once per element on first Ui.render, PhysFS-sandboxed) — alongside the legacy bare-integer texture handle. A .ui.component TOML sidecar next to the image (<image path>.ui.component, e.g. ui/panel.png.ui.component) carries slicing metadata (nine_slice + border) authored once with the asset; a .ui referencing that image by path picks it up automatically for values the .ui left at defaults (an explicit .ui border wins). PNG only (PSD/XCF deferred); untrusted-input hardened at the parser boundary. Existing .ui files load identically. /api/ui gains a Textured panels & 9-slice section (the image field, 9-slice behaviour, the sidecar); new /examples/ui-nineslice-demo (apps/ui_nineslice_demo — two different-sized frames share one textures/panel.png + a border=24 sidecar, corners unstretched); landing Retained UI “What’s in place” card updated (9-slice + image-by-path / .ui.component). No new Wren class (Ui already exists), so classes hold at 45; 65 apps (was 64 — +ui_nineslice_demo). Engine-side Hyperscale/projects/plume3d.md, Block U + ADR 0038)
Prior: 2026-07-29 (delta — Coroutines / Async: an engine-ticked cooperative scheduler (engine PLM-031, ADR 0037). One new public Wren class in the engine module — Async, a cooperative coroutine scheduler over Wren’s core Fiber. Async.run { ... } starts a coroutine (a zero-arg block; runs immediately to its first suspend or completion); inside one, Async.wait(seconds) suspends for seconds of scene time (frame delta, not wall-clock) and Async.nextFrame() suspends until the next frame; Async.pending (getter) counts suspended coroutines and Async.clear() cancels them all (e.g. on a scene change). The engine auto-ticks the scheduler every frame (after physics, before Game.update) — the game wires nothing into its own update, and a game that never imports Async pays nothing (guarded). Cooperative + single-threaded (Wren fibers are thread-confined — this is frame-scheduling, NOT parallelism/threads); a coroutine that never yields blocks the frame; timing is deterministic under fixed-step / headless runs. New /api/coroutines page + /examples/coroutine-demo (apps/coroutine_demo — a self-cycling traffic light + a per-frame counter, both driven by coroutines). Landing Coroutines / Async “What’s in place” card added; the Short-Term Scripting roadmap item (“Coroutines or async for ‘wait then…’”) removed (now ships); the Coroutines API-gap bullet resolved. 45 Wren classes (was 44 — +Async), 64 apps (was 63 — +coroutine_demo). Engine-side Hyperscale/projects/plume3d.md + ADR 0037)
Prior: 2026-07-29 (delta — Save/Load: game-authored, versioned SaveState + Save (engine PLM-022, ADR 0036). Two new public Wren classes in the engine module. SaveState — a typed key/value store (bool / int / number / string): setBool/setInt/setNumber/setString, getBool/getInt/getNumber/getString (1-arg and 2-arg-with-fallback), has, remove (→ Bool), clear, count (getter), keys (sorted List). Save — a static file facade over the app’s PhysicsFS write dir: write(name, state) → Bool (nested paths get their parent dirs created), read(name) → SaveState|null, exists(name) → Bool. Serializes to human-readable versioned TOML ([meta].format_version + [data]); a save written by a newer build reads back as null — rejected, never mis-parsed. Game-authored (the game chooses what to persist), NOT an automatic scene dump. Slots enumerate with the existing Resource.list("saves", ".sav") — there is no Save.list. New /api/save page + /examples/save-load-demo (apps/save_load_demo — persists a launch counter across runs). Landing Save & Load “What’s in place” card added; the Short-Term Save/load roadmap item removed (now ships). 44 Wren classes (was 42 — +SaveState +Save), 63 apps (was 62 — +save_load_demo). Engine-side Hyperscale/projects/plume3d.md + ADR 0036)
Prior: 2026-07-25 (delta — Resource.list directory enumeration + node-editor model picker (engine PLM-125, ADR 0035). New public Resource.list(dir, ext) — the sorted filenames of the files in a mounted directory, filtered by a case-insensitive suffix (e.g. ".blend"), sub-directories excluded, PhysicsFS-sandboxed (a ../absolute dir lists nothing, never escapes the project). Backed by new C++ FsMount::enumerateFiles. The /examples/node-editor 3D Preview gains a Model dropdown over Models/*.blend next to the collection dropdown, so a converted marketplace character previews alongside the bundled sprite; both picks are remembered across restarts. Additive Wren API on the existing Resource class (42 Wren classes, still 62 apps; the Scripting landing card already lists Resource). Security-reviewed (untrusted-path surface — sandbox + memory-safety clean). Engine gate 359/359, 0 warnings. Engine-side Hyperscale/projects/plume3d.md + ADR 0035)
Prior: 2026-07-24 (delta — Animation layers: runtime weight API + layers editing (engine PLM-123/124, ADR 0034). Layers were fully implemented in C++ but unreachable from Wren. New ctrl.setLayerWeight(layer, weight) / ctrl.layerWeight(layer) — layer is the authored [[layers]] name or its 1-based index; weight clamps to 0..1; an unknown layer is an inert no-op. Documented in a new Layers section on /api/animation-controller that also states the model plainly for Unity users: a Plume3D layer is a masked pose overlay over ONE graph, not a per-layer state machine (owner-decided direction; ADR 0021/0034). The /examples/node-editor gains a Layers panel (edit name/blend/mask, see the Actions bound to a layer, renaming re-points them) and live per-layer weight sliders in the 3D preview. Also fixed: a blend2d node was previously saved back in the blend1d schema, producing a controller the engine rejects on load — 1D and 2D are now each edited and saved in their own schema (param+at vs param_x/param_y+x/y), and clip sections are preserved. Additive Wren API on an existing class (42 Wren classes, still 62 apps); landing Animation Controller card updated. Engine gate 355/355, 0 warnings. Engine-side Hyperscale/projects/plume3d.md + ADR 0034 + the layers-strategy spec)
Prior: 2026-07-24 (delta — Node editor: preview collection picker + lossless round-trip (engine PLM-121/122). The /examples/node-editor 3D Preview gains a collection dropdown that auto-discovers the model’s collections (the shipped Character.blend exposes Fire + Bramble sprites), previews the picked one live (rebuild + re-attach), and remembers your pick across restarts in a gitignored tool-owned file — no config file to maintain. Separately, the editor’s serializer now round-trips every controller section losslessly, including the ones it doesn’t draw as nodes — [[layers]], [transitions], [actions], [contexts] — which were previously silently dropped on Save. Adversarial review fixed 5 confirmed edge cases (3 emitter shapes: array-of-scalars, empty array, non-bare section name; 2 picker: default-resolution + coverage honesty), verified via device runs + an independent tomllib round-trip. App-side Wren only (no engine/schema change). Still 62 apps. Engine gate 352/352, 0 warnings. Engine-side Hyperscale/projects/plume3d.md + layers-strategy spec)
Prior: 2026-07-24 (delta — In-editor 3D animation visualizer (engine PLM-118). The /examples/node-editor gains a 3D Preview mode: it renders the skinned character playing the controller being edited, driven live by the edited graph (serialize → scratch TOML → attachController, which hot-reloads while preserving state + params). Drive it with WASD/Space/K → params (walk transitions/blends/Any-State without launching the game); orbit + zoom camera with a track/no-track toggle for root-motion; a toggleable floor + gravity toggle + grounded readout for ground-checking. Full-frame mode (the design’s de-risk path; an embedded render-to-texture panel is a follow-up — the record_scene_meshes renderer refactor for it already landed). Bundled controllers/preview.toml pairs with Character.blend so it works out of the box. Docs: a new 3D preview section on the node-editor page. Still 62 apps (enhances the existing example). Screenshot-verified; engine gate 351/351, 0 warnings. Engine-side Hyperscale/projects/plume3d.md + design spec + ADR 0033)
Prior: 2026-07-24 (delta — StateBehaviour Wren class (engine PLM-116). A new StateBehaviour base class in the engine module — Unity’s StateMachineBehaviour as pure sugar over the controller’s per-state hooks: subclass, override onEnter/onUpdate/onExit/onEnd, then attach(controller, state). Documented in a new State behaviours subsection on /api/animation-controller. No engine/schema/AnimController-API change (additive Wren sugar). 42 Wren classes now (was 41). Still 62 apps. Engine gate 351/351, 0 warnings. Engine-side Hyperscale/projects/plume3d.md)
Prior: 2026-07-24 (delta — Node editor UX: pan/zoom, multi-select, dropdowns (engine PLM-114). The /examples/node-editor editor gains an effectively-infinite canvas (wheel-zoom toward the cursor, middle-drag pan, Fit view), multi-select (box-marquee, shift-click, multi-drag, multi-delete), and dropdowns wherever options are drivable — a structured guard editor (param dropdown + operator dropdown + a value typed to the param; or a trigger / END) and to / state-ref / blend-entry-clip / blend-param pick-lists. Pure Wren app (no engine/schema change, no new page). Still 62 apps. Screenshot-verified; engine gate 350/350, 0 warnings. Engine-side Hyperscale/projects/plume3d.md)
Prior: 2026-07-24 (delta — Transition events (engine PLM-115, amends the controller schema + Wren API). A graph transition can raise a named event: an optional event = "Name" on any per-state OR [any_state] transition, delivered to ctrl.onEvent("Name", fn) on commit; plus ctrl.onTransition(fn) — a firehose fn(from, to) on every committed transition. Order: onExit(from) → onEnter(to) → onTransition → onEvent. Additive/backward-compatible (a transition with no event, a controller with no subscribers, behave as before). Recorded in engine ADR 0032. Docs: new Transition events subsection on /api/animation-controller + the event in the TOML example + the Animation Controller landing card. The node editor round-trips the event field. Still 62 apps, no new page. Engine gate 350/350, 0 warnings. Engine-side Hyperscale/projects/plume3d.md)
Prior: 2026-07-24 (delta — Any State: Mecanim-style global transitions (engine PLM-113, amends the controller schema). The AnimController TOML gains an optional top-level [any_state] table — global transitions evaluated every tick from whatever state is current (Mecanim’s Any State), so a die → death edge is reachable from every state without wiring it per-state. Each entry is the usual { to, guard, fade } plus an optional self (Can-Transition-To-Self, default false — won’t restart the state you’re in). Any-state edges are checked first (global override); END is not allowed on a global edge. Additive/backward-compatible — a controller with no [any_state] is unchanged; no new Wren API (TOML-authored). Frozen-schema amendment owner-approved (engine HUMAN-015), recorded in engine ADR 0031. The /examples/node-editor gains an “Any State” source node that round-trips it. Docs: the controller page’s new Any State section + the TOML example + the Animation Controller landing card. Still 62 apps (no new app). Engine gate 345/345, 0 warnings. Engine-side Hyperscale/projects/plume3d.md)
Prior: 2026-07-24 (delta — Node editor: any-direction connectors + blend trees (engine PLM-112, refines U-M4). The /examples/node-editor editor now routes connectors from any node side (not just left→right — edgeAnchor picks the facing edge) and treats blend trees as first-class: a unified state/blend/clip graph with three typed edge kinds (transitions gold + guard label, plays blue, blend-entries teal + @threshold) and a blend inspector (param + entry thresholds). Loads/edits/saves the full controller — states + blend nodes (1D/2D via param/param2 + entries) + clips + transitions + params + root_motion — round-tripping the LOCKED PLM-077 schema (verified 3 states / 1 blend / 5 clips → reload → 3/1/5, thresholds and all). Adversarial review FIX-FIRST → fixed dangling-ref-on-delete (a delete now prunes every reference), malformed-load type-guards, quoted non-bare loaded keys, and a stale link-in-progress; plus a refKind node/clip toggle. Still 62 apps (enhances the existing example — no new app, no new Wren API). Gate 339/339, 0 warnings. Engine-side Hyperscale/projects/plume3d.md, Block U)
Prior: 2026-07-24 (delta — Node animation-state editor — Block U capstone (engine PLM-087, U-M4). New /examples/node-editor (apps/node_editor) — a visual editor for AnimController graphs: states are nodes, transitions are directed bezier wires, drawn on the retained-UI Canvas; chrome (toolbar/list/inspector) via immediate-mode Gui. Loads a controller .toml (Config.load, the LOCKED PLM-077 schema — never a new format), extracts the state graph (states + node/clip refs + guarded transitions + default), supports select/drag/add/link/delete/rename/edit-guards/set-default, and saves back with a schema-aware serializer that round-trips the whole controller ([controller]/[params]/[clips.*]/[nodes.*]/[states.*]+inline transitions/[root_motion]) — verified. 62 apps now. Screenshot-verified. No new Wren API (built on the U-M3 Ui.canvas* + Config/Gui). This completes Block U (retained-mode GUI): U-M1 runtime+editor → U-M2 widgets → U-M3 Canvas → U-M4 node editor. Deferred: live-preview on a skinned character, node-position persistence, pan/zoom. Engine-side Hyperscale/projects/plume3d.md, Block U)
Prior: 2026-07-24 (delta — Retained UI: Canvas / custom-draw capability (engine PLM-111, Block U/U-M3). A type = "canvas" element is a region the game draws custom primitives into with a new Ui.canvas* API — canvasRect/canvasLine/canvasBezier/canvasText, in canvas-local design px, colours 0..1 — composited in the retained-UI pass and letterboxed with the rest of the document. New /examples/ui-canvas (apps/ui_canvas — a node graph of boxes + bezier wires + labels; 61 apps now) + a top delta; /api/ui documents the canvas element type + the four draw methods; the /examples/ui-editor can add a canvas. No new Wren class (new element type + Ui statics). The node-graph enabler for the upcoming node editor. Screenshot-verified; gate 339/339, 0 warnings. Engine-side Hyperscale/projects/plume3d.md, Block U)
Prior: 2026-07-24 (delta — Retained UI: widget breadth — Toggle + Slider (engine PLM-110, Block U/U-M2). Two interactive widgets added to the retained UI: type = "toggle" (a checkbox — a click flips checked) and type = "slider" (drag to set value in [min, max]). Retained-state-polling model: the game reads element.checked / element.value each frame (an optional on_change also dispatches to Game.<name>()). New /examples/ui-widgets (apps/ui_widgets — a toggle + slider recolour a swatch by polling; 60 apps now) + a top delta; /api/ui documents the toggle/slider element fields + the checked/value accessors; the /examples/ui-editor can add them. No new Wren class (new element types + UiElement accessors). Screenshot-verified; gate 337/337, 0 warnings. Engine-side Hyperscale/projects/plume3d.md, Block U)
Prior: 2026-07-24 (delta — Retained UI: WYSIWYG editor + authoring API — U-M1 complete (engine PLM-109 + PLM-004/ADR 0020, Block U). The retained UI now has a visual editor: new /examples/ui-editor (apps/ui_editor) — an element tree, an inspector, and a live canvas rendered by Ui.renderEdit (the identical runtime ⇒ WYSIWYG), click-to-select via Ui.hitTest, and a selection gizmo drawn through a transparent Gui.beginOverlay. Built on a new editor-authoring tier on /api/ui: Ui.newDocument/createElement/deleteElement/reparent/save/hitTest/renderEdit, UiDocument.elementAt, and UiElement type/parentId/id=/rect/color/fontSize/onClick — every structural edit keeps the tree valid and invalidates handles (re-fetch by id). The editor runs standalone or plume3d --tool apps/ui_editor <target> to edit a target project’s .ui; the --tool CLI (the data/script-root split) is now documented on the UI Editor example page (engine ADR 0020). /api/gui gains beginOverlay/endOverlay. Landing “Retained UI” card + Scripting unchanged (no new Wren class — new methods on existing Ui/UiElement/Gui). Screenshot-verified; gate 328/328, 0 warnings. Engine-side tracked in Hyperscale/projects/plume3d.md, Block U)
Prior: 2026-07-24 (delta — Retained-mode UI shipped — Wren Ui API + .ui format (engine PLM-108, ADR 0019, Block U/U-M1). The first user-facing slice of the retained UI: new /api/ui documents the Ui / UiDocument / UiElement Wren classes and the .ui TOML document format (a [document] + [[element]] tree, parent-by-id, uGUI-style anchors/offsets, panel/text/button widgets). Ui.load(path) parses + validates a .ui (malformed input returns null, never a crash); Ui.render(doc, x, y, w, h) letterboxes the design into a screen rect (resolution-independent, aspect preserved), runs pointer interaction, and submits the draws (panels/images as quads, labels via the SDF Text pass); a button released inside fires Game.<on_click>() from native context. UiElement is a non-owning handle (valid/id/visible/text/onClick/resolved x/y/width/height). New app apps/ui_demo + /examples/ui-demo: a .ui card (title, subtitle, interactive button) rendered full-window, screenshot-verified. Landing “What’s in place” card added (Retained UI); the Scripting card now lists Ui. Built on the U-M1 stack (engine PLM-104 core → 105 codec → 106 Vulkan backend → 107 pointer phase → 108 this API). Engine-side tracked in Hyperscale/projects/plume3d.md, Block U)
Prior: 2026-07-23 (delta — Animation-controller demo — CT-M1 complete (engine PLM-086). New app apps/character_anim + /examples/character-anim: the epic’s end-to-end proof — a skinned Character.blend/Fire_Sprite driven through the AnimController via node.attachController("controllers/biped.toml") (a blend-node locomotion State, typed speed param from input, root-motion designation, a tagged cancel section driving input-buffering/cancel-windows, an onEnter hook from the auto-tick). Screenshot-verified (the character skins + animates via the controller, no validation errors). Named character_anim to sit alongside — not replace — the existing character_controller Jolt physics demo. Fixture-limited to one action so the blend is single-entry + no distinct masked-Action clip yet (a richer multi-action rig is HUMAN-014); the engine supports the full blend/Action (unit-tested). Engine-side tracked in Hyperscale/projects/plume3d.md, Block CT)
Prior: 2026-07-23 (delta — State / Action / Context runtime (engine PLM-085, ADR 0018/0030). The controller’s behaviour model is now live: Actions — transient overlays played via ctrl.action(name) on a masked layer over the current State (without exiting it), completing on the clip’s latch-and-refire END, interrupting any action on the same layer; Contexts — modal param gates (when/allows) that permit/block States and Actions (a gated State transition holds and preserves its trigger; a blocked action(...) is a no-op). [actions]/[contexts] in the TOML schema now have runtime (previously validate-only). [api/animation-controller](/api/animation-controller/) gains an “Actions & Contexts” section + the action(name) method + the two schema tables. Landing card updated. Engine-side tracked in Hyperscale/projects/plume3d.md, Block CT)
Prior: 2026-07-23 (delta — TOML controller-asset schema now documented + loaded (engine PLM-084, ADR 0017/0029). node.attachController(path) now parses the LOCKED controller-asset schema into a live graph: [controller] (name/default_state), typed [params], [clips] (+ tagged sections), [nodes] (blend1d/blend2d), [transitions] value-objects, [states] with guarded edges (param OP value / bare-trigger / END), masked [[layers]] (override/additive), and [root_motion]. controller.play(name) now accepts a named transition (value-object) or a bare clip. Controllers hot-reload when the TOML file changes (a bad reload keeps the previous graph). The parser validates + bounds all untrusted input at the boundary (dangling/duplicate/malformed refs rejected with diagnostics — a /security-review found no exploitable issues). [api/animation-controller](/api/animation-controller/) gains an “Authoring the graph” section with the full schema; the landing card is updated (TOML authoring now shipped). [actions]/[contexts] are validated but their runtime is PLM-085. Engine-side tracked in Hyperscale/projects/plume3d.md, Block CT)
Prior: 2026-07-23 (delta — Animation controller Wren surface (engine PLM-083, ADR 0017). New /api/animation-controller: a per-character AnimController attached via node.attachController(path), layered over AnimationState. Ships the runtime surface — typed params (setFloat/Int/Bool/setTrigger + getters), a direct-play escape hatch (play(name)), clip phase (currentState, normalizedTime, inSection over the highest-weighted “primary” clip), behaviour hooks (onEnter/onUpdate/onExit/onEnd, the last a latch-and-refire END — fires once when a non-looping clip finishes and again on each loop wrap), and read-only rootMotionDelta (character-local; the engine strips the motion from the pose and the game applies the delta to the body). The engine auto-ticks attached controllers each frame (after event dispatch, before Game.update); tick(dt) is also callable for manual stepping. Landing “What’s in place” card added. TOML graph authoring (states/blend nodes/transitions/layers) lands in the next sync (PLM-084). Engine-side tracked in Hyperscale/projects/plume3d.md, Block CT)
Prior: 2026-07-16 (delta — Contact callbacks now deliver a PhysicsContact object (ADR 0016, PLM-064). onContactBegin/onContactEnd/onContactStay previously took eight positional arguments (nameA, nameB, px, py, pz, nx, ny, nz); they now take one argument, a PhysicsContact, with accessors getNodeA()/getNodeB() (name), getNodeAId()/getNodeBId() (numeric node id, unique, 0 = no node), getPointX/Y/Z(), getNormalX/Y/Z(), and hasManifold() (false for onContactEnd, whose point/normal are zeroed — so a script can tell “no manifold” from a real contact at the origin). This closes the gap that PhysicsContact was a fully-bound Wren class the engine never handed to a script — it existed only as a constructible empty object. It is a breaking signature change: the arg count is validated at registration and a wrong-arity callback is rejected with a logged error rather than fed garbage (the VM’s own arity check compiles out of release builds). /api/physics contact section rewritten; the previous “String name, not a Node” correction is superseded. Engine-side tracked in Hyperscale/projects/plume3d.md, Block J)
Prior: 2026-07-16 (delta — Engine↔docs gap analysis — a six-lens audit of the engine’s public surface against this site. An entire ADR had never reached the docs: ADR 0009 (accepted 2026-07-10) shipped Http.post and Engine.entropy; neither appeared anywhere on the site, and this very log jumps 07-08 → 07-12 straight past it. Closed: new /api/http; Engine.entropy() on /api/engine; Config.save on /api/config (which was marked Current while documenting half its class); the API Overview index was missing six live pages (Http, Net, PlayFlow, Sprite, Texture, Text) and now lists them. Seven documented claims were false and are corrected — worst first: collision_matrix was documented backwards, and the page’s own recommended example dropped every dynamic body through the floor; contact callbacks pass a String name, not a Node, so every documented example was uncallable; Engine.Version() returns 202601.001, not 2026.1.1; getBodyCount counts all bodies, not active ones; raycast directions must be normalised; gamepadButton takes an index, not a name; addNode(name) is required, not optional. [Window] had no keys documented despite this file claiming it was covered. The landing page had silently skipped four capabilities’ cards (Sprites, SDF Text, Networking, HTTP) over four months and overstated touch. Four “API gaps” bullets were wrong or understated — notably “Coroutines — Not in Wren runtime”, when the vendored VM ships Fiber. Two shipped apps (sdf_text_demo, sprite_demo) were invisible here while API pages point readers at them by name → PWEB-013. Engine-side tracked in Hyperscale/projects/plume3d.md)
Prior: 2026-07-16 (delta — Ongoing contacts + bounded contact queues (PLM-061): new Physics.onContactStay(scene, callback), fired once per step for each pair still touching — the ongoing-contact counterpart to onContactBegin/onContactEnd, for grounded checks and sustained pushes. It is a firehose (every step, every touching pair), so the queue behind it only runs while a callback is registered. The same change fixes a pre-existing leak: the contact queues were drained only inside the “is a callback registered?” check, so an app with contacts but no contact callback grew them for the lifetime of the world. Queues are now capped (4096 events each) and drained every frame regardless of listeners — the engine holds the bound itself instead of trusting a consumer, so a C++-only app is safe too. Interacts with manual stepping: a long stepN run has no frame in between to drain, so it can now drop the excess rather than grow without bound. /api/physics updated. Engine-side tracked in Hyperscale/projects/plume3d.md, Block J)
Prior: 2026-07-16 (delta — Manual physics stepping (ADR 0015, PLM-058): new Physics.step(scene, dt) / Physics.stepN(scene, dt, n) / Physics.fixedDeltaTime, advancing a scene’s physics on demand, decoupled from the game loop. Makes headless physics possible: a plume3d --test run never runs the loop, so this is the only way to advance a sim there — a test can now step to rest and assert the outcome deterministically (same build + machine) instead of capturing a windowed screenshot. Requires the scene’s auto-step to be paused (setSimulationPaused), enforced, so the loop and a manual stepper can never both advance a scene. /api/physics updated. Engine-side tracked in Hyperscale/projects/plume3d.md, Block J)
Prior: 2026-07-15 (delta — OverPar physics Wren surface (ADRs 0011–0014, PLM-026/055/056/057): new Physics body builders — sphere/capsule/cylinder/convexHull (static+dynamic) — plus a static triangle-mesh collider (addStaticMesh) and a heightfield collider (addStaticHeightfield, backed by a new C++ ShapeType::HeightField), and runtime material setters (setRestitution/setFriction/setDamping). /api/physics updated. Engine-side tracked in Hyperscale/projects/plume3d.md, Block J)
Prior: 2026-07-12 (delta — Blender addon 0.2.0 overhaul (PLM-044…054): in-Blender Validate + Write Config to .blend buttons, class-name picker, multi-select batch layers, addon preferences (fallback layer names), Blender 4.2+ extension manifest, collision-layer default fixed to the engine’s Default layer, save-sync no longer stamps unconfigured objects. /blender-addon updated. Engine-side tracked in Hyperscale/projects/plume3d.md, Block T)
Prior: 2026-07-08 (delta — Billboarded sprites (ADR 0008, PLM-009): new Sprite + Texture Wren API, new /api/sprite + /api/texture pages. Same-day SDF text (ADR 0007, PLM-010): Text API + /api/text. Engine-side tracked in Hyperscale/projects/plume3d.md, Block B steps 11–12)
Prior: 2026-07-03 (full networking stack: Net transport (ADR 0003) + replication (ADR 0005) + PlayFlow client (ADR 0004). New /api/net, /api/playflow; /dedicated-server + /configuration updated)
Engine features that landed after the 2026-03-25 review, now documented (PLM-032):
| Feature | Engine source | Doc status |
|---|
Random — seeded, deterministic RNG (PCG32): Random.new(seed), int(n), intRange(min,max), float(), shuffle(list). Identical seed ⇒ identical sequence cross-platform (ADR 0002). | wren_engine_module.cpp + wren_api.cpp | ✅ Done — new /api/random page; API index + landing updated. |
Headless test mode — plume3d --test <appdir> (alias --headless) runs <appdir>/tests/run.wren → Tests.main() on the headless VM (no window/Vulkan), exits with Engine.exit code (ADR 0001). | engine/src/app_host.cpp | ✅ Done — new Testing (Headless) guide + sidebar entry. |
Engine.exit(code) — clamps 0–255; ends the run / sets the process exit status. | wren_api.cpp (Engine) | ✅ Done — added to /api/engine. |
apps/toml_editor example | apps/toml_editor | ✅ Done — new /examples/toml-editor page + examples index (Tools). |
--tool <toolDir> <dataDir> editor mode — run a tool’s main.wren against another project’s data dir. | engine/src/app_host.cpp | ⏳ Partial — usage shown on the TOML Editor page; full dedicated doc pending the engine ADR (engine ticket PLM-004). |
| Page | Status | Notes |
|---|
/api/ai | Updated 2026-07-31 | AI. Scriptable AI (engine PLM-189, ADR 0065): author a behaviour tree in Wren, engine-ticked. BehaviourTree/Bt/BtNode/Blackboard/BtStatus (closure leaves `{ |
/api/behaviour-tree-authoring | New 2026-08-01 | Data-driven BT authoring for the GUI editor (engine PLM-211…217, ADR 0068): named leaves so a GUI/asset can reference a leaf a closure can’t be. Bt.registerLeaf(name, paramSpec) {|bb,dt,params|} (arity-3, typed params) · Bt.leafCatalog (editor palette) · Bt.load(path) → an engine-ticked BehaviourTree from a hardened .bt.toml (bounded/cycle-checked; unknown-leaf/bad-param → Bt.loadError, tree intact) · the .bt.toml schema (sequence/selector/parallel/decorators/leaf/phase_switch). PhaseEvaluator.evaluate(currentPhase, health01, elapsed, thresholds) — forward-only boss phase the game feeds health01 (engine never reads HP) → a blackboard int a phase_switch reads; mini-boss = 2 entries, boss = 4+, same data. Additive to ADR 0065 (Bt.leaf closure unchanged). /security-review PASSED. |
/api/light-sound-vision | New 2026-08-01 | Light/Sound/Vision as mechanics (engine PLM-199/203/206, ADR 0067): the engine samples, the game owns the policy. Illumination.illuminanceAt(scene,x,y,z,mask) → continuous 0..1 (per-light falloff matching the render + masked LOS; no binary helper; bidirectional). FogOfWar — world-XZ visibility field: configure (bake occluder grid) / beginFrame / reveal(x,z,radius) (radius scaled by illuminance = darkness shrinks view) / state→0/1/2 (persistent Explored) / mark+tick+isRevealed (wall-penetrating sonar); team + per-player. NoiseBus.emitOccluded(scene,…,mask) — a wall blocks what the AI hears. SoundEmitter — unifies audible + audible-to-AI. Frozen (ADR 0067). Under the surface, still landing: audio low-pass (PLM-202), long-range emitter (PLM-207), fog render composite (PLM-205). |
/api/combat | Rewritten 2026-08-01 (ADR 0069) | Action combat on Jolt sensor bodies (engine PLM-196 + PLM-198) — the engine detects hits, the game owns health. Shape (capsule/box/sphere). Hitbox/Hurtbox — bone-attached sensor boxes + .layer/.tag/.owner/.payload/.enabled; hitbox never hits its own owner, once-per-swing, bindWindow gates to a Blender-marker hit-window, bindController links a Blender-authored box to a controller; .independent/.reHitSeconds = flurry/DoT; disabled hurtbox = i-frames. Hit (read-only attacker/victim/hitboxTag/point/normal/payload). Combat.onHit/onStay/onExit(scene, fn) — lifecycle events, engine-ticked at the tail of update() (no game-called tick — it would re-enter the VM). Blender authoring: meshes (Passive+Animated rigid body, bone-parented) in Plume_Hitboxes/Plume_Hurtboxes collections (name = tag) surface via node.hitboxes/node.hurtboxes maps; node.attachToBone/detachFromBone is the engine-driven socket. No HP/damage/death type in the engine — the opaque payload is the only health-boundary wire. Detection is sensor-overlap, not swept — no CCD (PLM-221); Physics.castShape stays the swept one-shot, and combat sensors never surface in Physics.* world queries. |
/api/engine | Updated 2026-07-16 | Engine.exit(code) (ADR 0001, clamps 0–255, record-and-honor; exact codes in --test mode). 2026-07-16: added Engine.entropy() (ADR 0009 — non-deterministic 53-bit seed, std::random_device + high-res clock; explicitly not /api/random’s frozen determinism, and not cryptographic), and corrected the version format — it is YYYYMM.PPP (e.g. 202601.001), not Year.Month.Patch / 2026.1.1. That false claim was echoed here too. |
/api/random | New 2026-06-29 | Seeded, deterministic RNG (PCG32, ADR 0002): new(seed), int(n) [0,n), intRange(min,max) inclusive, float() [0,1), in-place shuffle(list). Frozen-sequence contract noted. |
/api/coroutines | New 2026-07-29 | Coroutines / Async (engine PLM-031, ADR 0037): one new Wren class Async — a cooperative coroutine scheduler over core Fiber. Async.run { ... } (start a zero-arg-block coroutine, runs to first suspend/return), Async.wait(seconds) (suspend for seconds of scene time), Async.nextFrame() (suspend to next frame), Async.pending (getter — count of suspended coroutines), Async.clear() (cancel all). The engine auto-ticks the scheduler each frame (after physics, before Game.update) — no per-game wiring; guarded so an app that never imports Async pays nothing. Cooperative + single-threaded (frame-scheduling, NOT parallelism/threads); a coroutine that never yields blocks the frame; deterministic under fixed-step/headless. |
/api/http | New 2026-07-16 | Blocking HTTP POST (ADR 0009, shipped 2026-07-10, undocumented until now): Http.post(url, body) → Map {ok, status, body}. Fixed limits: 1 MiB request cap (response uncapped), 5s connect / 10s read, redirects followed, JSON-only, POST-only, HTTPS via OpenSSL. No auth-header parameter — tokens ride in the URL query. The C++ error field is dropped by the binding, so script cannot tell why a send failed. Graceful ok=false on non-networking (console) builds. |
/api/logger | Current | — |
/api/shader | Current | Minimal; shader has only a name property |
/api/mesh | Updated 2026-07-23 | Covers all Mesh methods. Added render state (PLM-103): blendMode (“opaque”/“alpha”), cullMode (“none”/“back”/“front”), doubleSided, per-mesh alpha blending + culling for procedural meshes. |
/api/input | Updated 2026-07-24 | Keyboard, mouse, gamepad (4 players). 2026-07-24 (engine PLM-120): gamepadButton/gamepadAxis now actually populate — the engine recorded a controller on connect but never called SDL_OpenGamepad, and SDL3 only delivers button/axis events for an opened pad, so every read silently returned false/0.0 engine-wide; the device is now opened on connect and closed on disconnect (page body already documented the intended behavior, so no API-surface change). 2026-07-16: corrected gamepadButton/gamepadAxis — the button/axis argument is a Num index only; the page promised “Num or String … name” but the binding reads wrenGetSlotDouble with no string path, so a name silently yields always-false / always-0.0. Added a Touch section stating plainly that touch is C++-only (InputState, 8 fingers) with zero Wren binding. |
/api/window | Current | Covers size, fullscreen, design size, scale modes |
/api/graphics | Updated 2026-07-31 | Updated 2026-03-25: added setLights, setShadowMappingEnabled, setShadowBias, setShadowMapSize, setDebugView. 2026-07-31: added perViewportOverlays(on) — replay the retained-UI HUD per split-screen cell (CAM-18, ADR 0064). |
/api/gui | Current | Updated 2026-03-25: expanded from 7 to full API (~100 methods). Bug fixed 2026-03-25: all color parameters corrected to 0–255 (not 0–1); separator last param documented as Bool (not rounding radius); colorPicker return corrected to 0–255; setWindowAlpha stays 0–1. |
/api/audio | Updated 2026-07-29 (PLM-133) | Covers global audio, Source 3D. 2026-07-29 (PLM-133, ADR 0042): new Streaming audio section — Audio.newStreamingSource(sampleRate, channels) (a Source fed over time; mono/stereo; null on failure), source.queueSamples(list) (queue interleaved floats in [-1,1]; (re)starts playback; buffers recycled; 1M/call cap; no-op on a file-backed source), source.queuedBuffers (poll to pace refills). Additive to Audio/Source (no new class); the one-shot newSource(path) is unchanged. Uses AL_EXT_FLOAT32 with an int16 fallback; main-thread fed. |
/api/mixer | New 2026-07-30 (PLM-147) | Mixer + MixerGroup — Unity-style audio mixer groups (buses) routing Sources + SoundFontInstruments (ADR 0046). Mixer.master, Mixer.group(name) (get-or-create), Mixer.groupUnder(name, parent); MixerGroup.name/volume/volume=/muted/muted=/soloed/soloed=/addSource(_)/addInstrument(_). Effective gain (group × ancestors, mute/solo) applied to every routed source each frame; user setVolume preserved. Hard cutover: SoundFontInstrument.update() removed (engine auto-services instruments). |
/api/midi | New 2026-07-30 (PLM-135) | Midi — live MIDI device input (USB/virtual keyboard) as normalized events (ADR 0044). Static facade: Midi.inputDevices (List of names), openInput(index|name) → Bool, closeInput(), isOpen, openedDevice, poll() → List of {type,channel,data1,data2,value14} maps. Parsed + queued on the device thread, delivered to Wren on the main thread each frame. Desktop only (no-op otherwise). Note source is arbitrary (feed a SoundFontInstrument). Live note-in needs hardware (HUMAN-017); parser + binding + enumeration are CI-tested. |
/api/soundfont | New 2026-07-30 (PLM-134) | SoundFontInstrument — loads a SoundFont (.sf2) bank and plays it as real polyphonic audio through the streaming sink (ADR 0043). SoundFontInstrument.new(path, sampleRate, channels) (loaded reports success), noteOn(note, velocity) / noteOff(note) / allNotesOff, setPreset(index), controlChange(controller, value), pitchBend(value14), setMaxVoices, setGain, presetCount / presetName(i) / activeVoices. The engine renders + queues its audio automatically each frame (no update() — removed in PLM-147). Banks are loaded resources (git-ignored, fetched at runtime). Note source is arbitrary; routes to a mixer group via MixerGroup.addInstrument. |
/api/scene | Updated 2026-07-16 | findNodeById, createCharacterController, createParticleEmitter, createInstancedMesh, findInstancedMesh. 2026-07-16: corrected addNode(name) — name is required, not optional. Wren makes arity part of the signature, so addNode() aborts the fiber; pass "" for an unnamed node. |
/api/node | Updated 2026-08-01 | Added setQuat(x,y,z,w) — exact, gimbal-free quaternion rotation (ADR 0063). Combat (PLM-198, ADR 0069): hitboxes/hurtboxes (Blender-authored tag→box maps on an instantiated character), attachToBone(character, controller, bone)/detachFromBone() (engine-driven weapon/VFX bone socket). Documented on /api/combat. |
/api/camera | Updated 2026-07-31 | The camera rig system (ADR 0058): Cam factory (thirdPerson/firstPerson/topDown/orbit/freeFly + setActive/blendTo/active), CameraRig fluent setters (incl. collider(padding) — wall pull-in, ADR 0059), Impulse.emit, Ease. Plus split-screen (ADR 0059): SplitScreen (new(scene, players) + layout/distance/height/dynamic/mergeDistance/splitDistance/transitionDuration/isMerged/mergeFraction/playerCount) and SplitLayout (auto/horizontal/vertical/grid/angled) with dynamic merge/split — an animated blend (ADR 0060) — plus the RTT composite path (compositeMode), the angled 2-player diagonal split, and the animated rect-peel (peel) (ADR 0061). Plus the raw Viewport primitive for picture-in-picture / minimap (ADR 0062). The low-level Camera (view/projection, world/screen) class is kept below the rig sections. |
/api/light | Current | Updated 2026-03-25: added castsShadows / castsShadows= |
/api/raycast | Current | Covers all Raycast methods and RaycastHit |
/api/config | Updated 2026-07-16 | 2026-07-16: added the previously undocumented Config.save(path, tomlText) (the page covered only half the class while marked Current) — TOML text persistence to the write dir, not scene serialisation. Also corrected Config.load’s documented return: the root is always a Map (the List/primitive mapping applies to nested values only). |
/api/save | New 2026-07-29 | Save/Load (engine PLM-022, ADR 0036): SaveState — typed key/value store (bool/int/number/string): setBool/setInt/setNumber/setString, getBool/getInt/getNumber/getString (1-arg + 2-arg-with-fallback), has, remove, clear, count, keys. Save static facade — write(name,state)→Bool, read(name)→SaveState|null, exists(name)→Bool, over the app’s PhysicsFS write dir (parent dirs auto-created). Versioned human-readable TOML ([meta].format_version + [data]); a newer-format save reads as null (rejected, not corrupted). Game-authored, not an automatic scene dump. Slots via Resource.list (no Save.list). |
/api/resource | Current | Covers Resource, BlendResult |
/api/animation | Current | Updated 2026-03-25: added blend layers, crossfade, stopLayer, full AnimationPlayOptions (loop, speed, timeOffset, blendWeight, layer, crossfadeDuration) |
/api/animation-controller | Updated 2026-07-24 (PLM-123) | AnimController (engine PLM-083/084/085/123, ADR 0017/0034): per-character controller via node.attachController(path). Typed params (setFloat/Int/Bool/setTrigger + getters), direct-play escape hatch (play(clip|namedTransition)), Actions (action(name) — masked overlays over the current State, completing on END) + Contexts (modal param gates), Layers (setLayerWeight/layerWeight by authored name or index — masked pose overlays over the single graph, NOT per-layer state machines; documented for Unity users), clip phase (currentState, normalizedTime, inSection), behaviour hooks (onEnter/onUpdate/onExit/onEnd — latch-and-refire END), read-only rootMotionDelta, tick. Engine auto-ticks + hot-reloads attached controllers. TOML controller-asset schema fully loaded — [controller]/[params]/[clips]/[nodes] (blend1d/blend2d)/[transitions]/[states]/[[layers]]/[actions]/[contexts]/[root_motion]; malformed assets rejected at the parser boundary. |
/api/math | Current | — |
/api/physics | Updated 2026-07-16 | Physics class: forces, impulses, velocities, gravity, pause, raycast, contact events (onContactBegin/onContactEnd/onContactStay, plus the 4096-event queue cap and its manual-stepping interaction); box/sphere/capsule/cylinder/convexHull bodies (static+dynamic), static triangle-mesh + heightfield colliders, material setters (restitution/friction/damping); manual stepping (step/stepN/fixedDeltaTime) for headless/deterministic sim. Combat shape queries (PLM-196, ADR 0066): castShape (swept, all bodies crossed), overlapShape (at-pose narrow-phase), raycastAll (piercing) → a List of RaycastHit, layer-masked — the swept/overlap/pierce primitives shared with the combat queries. PLM-198/ADR 0069: combat hit/hurt sensor volumes are excluded from these world queries (a bullet/LOS ray must not stop on an invisible hitbox). Contact callbacks (ADR 0016, PLM-064): a single PhysicsContact argument — getNodeA()/getNodeB() (name, "" if unbound), getNodeAId()/getNodeBId() (numeric id, 0 if unbound), getPoint*/getNormal*, hasManifold(). PhysicsContact documented inline as a sub-section. Wrong-arity callbacks are rejected at registration. 2026-07-16 corrections still current: getBodyCount returns all bodies (incl. static and sleeping), not active ones; raycast directions should be normalised (magnitude scales both ray length and the reported distance). (The earlier “callbacks pass a String name, not a Node” correction is superseded by the PhysicsContact object above.) |
/api/character-controller | New 2026-03-25 | CharacterController + CharacterState constants |
/api/instanced-mesh | New 2026-03-25 | InstancedMesh: addInstance, setInstancePosition, setInstanceTransform, GPU instancing (SSBO) |
/api/particles | New 2026-03-25 | ParticleEmitter: play/stop/burst, setPosition/setNormal, setMeshFromNode |
/api/net | New 2026-07-03 | Networking transport (GameNetworkingSockets, ADR 0003): Net.startServer, connect, send(conn,bytes,reliable), poll() → List of {type,conn,payload} Maps, close, isServer, connectionCount. + Replication (ADR 0005): setReplicatedEntity, removeReplicatedEntity, clearReplicatedEntities, replicatedEntities(), replicatedEntitiesLerp(alpha). Static facade; desktop-only; NUL-safe byte payloads. |
/api/playflow | New 2026-07-03 | PlayFlow cloud hosting client (ADR 0004): PlayFlow.isConfigured, requestServer(region,customData) → instanceId, serverInfo(id) → {ok,status,host,port,error}. Static facade; key from PLAYFLOW_CLIENT_KEY env. Matchmaking flow + Docker packaging documented. |
/api/sprite | New 2026-07-08 | Billboarded sprites (ADR 0008, PLM-009): Sprite.new(texture); size/color/billboard (0 spherical / 1 cylindrical / 2 flat); drawAt(x,y,z). Camera-facing, alpha-blended, depth-sorted quads. |
/api/texture | New 2026-07-08 | Texture.load(path) → GPU texture (PNG/JPG/WebP from the mounted project); width/height/valid. Used by Sprite (ADR 0008, PLM-009). |
/api/text | New 2026-07-08 | SDF text (ADR 0007, PLM-010): Text.new(content); text/size/color/outlineColor/outlineWidth/softness/align/wrapWidth/lineSpacing/richText setters; width/height/lineCount; draw(model) (world-space) + drawScreen(x,y) (HUD). Rich-text <color>/<size> markup. Single-channel SDF, runtime-baked from Inter; frozen-contract layout (golden tests). <b>/<i> deferred (PLM-043). |
/api/ui | Updated 2026-07-29 (PLM-139/140/141/142/143) | Retained-mode UI (ADR 0019, PLM-104–108, Block U): Ui.load(path) → UiDocument; Ui.render(doc, x, y, w, h) (letterboxed, resolution-independent; runs pointer interaction; a button released inside fires Game.<on_click>()); Ui.get(doc, id) → UiElement (valid/id/visible/text/onClick/resolved x/y/width/height). Documents the .ui TOML format: [document] + [[element]] tree, parent-by-id, uGUI anchors/offsets, panel/text/button widgets, 9-slice borders. Panels/images draw as quads; labels reuse the SDF Text pass. Codec is untrusted-input-hardened (a /security-review found no exploitable issues). U-M1 slice is Panel/Text/Button; more widgets + a visual editor are U-M2/M3/M4. 2026-07-29 (PLM-139/140, ADR 0038): new Textured panels & 9-slice section — textured 9-slice now actually renders (fixed corners, edges stretch one axis, center stretches both; the nine_slice/border fields previously round-tripped but were inert), the element can reference an image by path (image = "ui/panel.png", a mounted PNG resolved once on render, PhysFS-sandboxed) alongside the legacy integer texture handle, and slicing can be authored once in a .ui.component TOML sidecar (<image path>.ui.component, nine_slice+border) that a path-referenced image picks up automatically. Additive to the frozen .ui codec (existing files load identically); PNG only. 2026-07-29 (PLM-141, ADR 0039): two new element types — type = "progress" (a display-only progress bar reusing the slider’s value/range model: value/color/fill_color, no handle or interaction) and type = "radio" (a grouped toggle: checked/check_color/on_change + a group string; radios sharing a non-empty group are mutually exclusive — exactly one selected, never toggles back off). Both are polled via the existing value/checked accessors and created via `Ui.createElement(doc, “progress" |
- Touch input —
InputState tracks up to 8 touch fingers (SDL_FINGER_DOWN/UP/MOTION, input_state.cpp) but there is no Wren binding at all — grep -riE 'touch|finger' engine/integrations/wren/ returns zero matches. (Corrected 2026-07-16: this bullet used to say there was no section “beyond the touch mention”, implying partial exposure; there was never any. /api/input now states the gap explicitly, and the landing Input card no longer advertises touch as scriptable.)
- BlendResult.classes / BlendResult.configs — Not fully documented (just noted on
/api/resource). These return the list of class names and config paths found in the blend; may want example usage added.
- Material textures via Wren — no way to assign a texture to a mesh’s PBR material from Wren: the handles exist in the C++
PBRMaterial struct and no binding sets them. (Corrected 2026-07-16: this bullet used to say “No loadTexture API exists”, which is now false — Texture.load(path) ships (see /api/texture), but it feeds sprites, not PBR materials. Textures also reach materials via BlendResult.instantiate from .blend data. The gap is the Wren PBR-material setter specifically.)
- SkeletalAnimation via Wren — The C++ engine has full bone skinning (
ArmatureState, BoneState, GPU bone matrix SSBO), but the Wren API exposes animation only through AnimationState. Direct bone matrix access is not yet exposed. Document if/when bone override API is added.
- Physics constraints — no Wren builder: C++ supports Fixed/Point/Hinge/Slider/Cone/SixDOF (
ConstraintSettings) and there is no Physics.addHingeConstraint etc. (Clarified 2026-07-16: constraints are nonetheless reachable from Wren via BlendResult.instantiate, which builds them from .blend rigid-body-constraint data — see apps/physics_constraint_hinge. The gap is authoring one from script, not using one.)
Physics shapes beyond box — resolved 2026-07-15 (ADRs 0011/0013/0014): sphere/capsule/cylinder/convexHull (static+dynamic), addStaticMesh (triangle mesh), and addStaticHeightfield (new C++ ShapeType::HeightField) are Wren-exposed. Remaining by design: dynamic mesh/heightfield bodies are impossible (Jolt mesh/heightfield shapes are static-only).
- Particle Tier 1 (GPU compute) — The
ParticleTier enum has GPU and Hybrid tiers in C++ but only Tier 2 (Jolt physics) is currently wired to Wren. Document when implemented.
Audio reverb/EFX — resolved 2026-07-30 (engine PLM-153/154, ADRs 0050/0051): the OpenAL EFX extension is exposed per mixer group — MixerGroup.addEffect(type) / addReverbPreset(name) / clearEffects + MixerEffect.setParam cover the whole aux-slot suite (reverb, echo, chorus, flanger, distortion, EQ, pitch, ring-mod, autowah, frequency-shifter, vocal-morpher, compressor), chainable. See /api/mixer and /examples/audio-effects-demo. Direct-path low/high/band-pass filters are now exposed too (engine PLM-157, ADR 0052): MixerGroup.setLowPass/setHighPass/setBandPass/clearFilter on the group’s dry signal — see /api/mixer Filters. The native OpenAL EFX surface is now fully exposed.
Coroutines — resolved 2026-07-29 (engine PLM-031, ADR 0037): the engine-integration gap is closed. New public Wren class Async is a cooperative scheduler over the VM’s core Fiber (vendor/Wren/src/vm/wren_core.wren) that the engine auto-ticks every frame — Async.wait(seconds) (scene-time delay) and Async.nextFrame() (yield to next frame), plus Async.run/pending/clear. See /api/coroutines and /examples/coroutine-demo. (The 2026-07-16 correction stands: the VM always shipped Fiber; what was missing was the engine hook resuming a fiber across frames on the game clock — that is what this adds.) Deferred by design (ADR 0037): async/await keywords and promise-based async I/O over Http/Net can layer on the same scheduler later.
Save/load — resolved 2026-07-29 (engine PLM-022, ADR 0036): game-authored, versioned SaveState + Save now ship — see /api/save and /examples/save-load-demo. By design this is a game-authored key/value store, deliberately not automatic scene serialisation (rejected in the ADR as brittle across engine versions) — the game persists what it chooses. Remaining by design: only scalar values in v1 (bool/int/number/string; structure via key naming), and saves live under the app write dir, not a per-user OS data directory (a future, additive extension). Cloud sync is the sibling engine PLM-138 (blocked on HUMAN-019).
| Page | App | Status | Notes |
|---|
/examples/ai-hunters | apps/ai_hunters | Updated 2026-07-31 | AI Foundation full loop (engine PLM-189, ADR 0065): four hunters run a Wren-authored behaviour tree — perceive by sight (Ai.nearestVisibleTarget = range + FoV + masked-raycast LOS against a static obstacle body) and sound (NoiseBus) → navmesh-chase to a SurroundRing slot + pick a Tactic → when the target flees + goes silent, lose it (LostTargetTracker) → search the last-known point → give up and return to spawn. Seeded, navmesh-snapped scatter; hunters coloured by FSM state (engage/search/give-up); render-verified. Also documents the world-space render pattern (apply the scene camera each frame). |
/examples/bt-editor | apps/bt_editor | Updated 2026-08-01 | BT GUI editor (engine PLM-217/218, ADR 0068): author a tree as data — a palette from the live Bt.leafCatalog (+ composites/decorators + a PhaseSwitch node), a canvas (boxes + parent→child wires, click to select, drag to move), a schema-driven inspector (typed param sliders for the selected leaf), Delete subtree, Save + validate → trees/enemy.bt.toml round-tripped through Bt.load, and Run 1 tick (a live preview). --test-verified surface; windowed run clean. |
/examples/bt-boss-demo | apps/bt_boss_demo | New 2026-08-01 | The payoff (engine PLM-219, ADR 0068): a phaseless enemy, a 2-phase mini-boss, and a 4-phase boss authored from one .bt.toml schema/loader/editor, differing only in data. HP is game-owned → fed to PhaseEvaluator → phase_switch; the HUD meters HP + phase + action + a phase-enter banner. Render-verified (a boss crossing a threshold + switching subtree while the same-HP mini-boss has not). --test-verified surface + test_bt_load_wren mechanics. |
/examples/lsv-demo | apps/lsv_demo | New 2026-08-01 | Light/Sound/Vision (engine PLM-199/203, ADR 0067): a player crosses light and shadow while two live HUD bars meter EXPOSURE (Illumination.illuminanceAt, respecting the wall’s shadow) and NOISE (game-computed), with FogOfWar reveal whose radius shrinks in the dark. The engine samples; the game owns the policy (thresholds/HP). --test-verified surface + test_illumination_wren/test_fog_wren/test_noise_occlusion_wren mechanics. |
/examples/combat-demo | apps/combat_demo | Updated 2026-08-01 (ADR 0069) | Action combat with hand-built boxes (engine PLM-196): a sword Hitbox (a Jolt sensor) lunges into overlap with a body Hurtbox; Combat.onHit reads the hitbox’s opaque payload as damage off a plain Wren _hp field; on death the game disables the hurtbox (a corpse stops taking hits). No engine HP type — health is 100% game-side. --test-verified surface + test_combat_wren mechanics. (Blender-authored path → combat-blender-demo.) |
/examples/combat-blender-demo | apps/combat_blender_demo | New 2026-08-01 (PLM-198, ADR 0069) | Blender-authored combat: CombatVolumes.blend carries a Fighter with a sword hitbox + body hurtbox authored in Blender (bone-parented meshes in Plume_Hitboxes/Plume_Hurtboxes). Instantiated twice; owners set game-side off the node.hitboxes/hurtboxes tag maps; a lunge lands via Combat.onHit (engine turns each volume into a bone-attached Jolt sensor). No hitbox geometry in code. Render-verified 2 strikes; --test asserts the volumes surface. |
/examples/camera-showcase | apps/camera_showcase | New 2026-07-31 | Camera system end-to-end (Camera epic, ADR 0058): Cam.thirdPerson follows a moving player, Cam.blendTo eases to an orbit rig (with noise sway), Impulse.emit shakes — all with draw() = just scene.draw() (no camera plumbing). Render-verified. |
/examples/camera-collider | apps/camera_collider | New 2026-07-31 | Occlusion collider (Camera epic, ADR 0059): a wall between the camera and target — the plain rig’s view is filled by the wall, the .collider(0.3) rig pulls the camera in front of it so the player stays visible. Static physics body + masked raycast. Render-verified. |
/examples/split-screen | apps/split_screen_demo | Updated 2026-07-31 | Local co-op split-screen (Camera epic, ADR 0059; animated merge ADR 0060): SplitScreen.new(scene, players) — four players in a 2×2 grid that dynamically merges into one group-framed view when they cluster and splits apart when they spread, now an animated blend (transitionDuration/mergeFraction). The renderer draws the scene once per viewport with dividers. Render-verified (grid / mid-transition / merged). |
/examples/angled-split | apps/angled_split_demo | New 2026-07-31 | Kronnect-style angled 2-player split (ADR 0061): SplitLayout.angled — a diagonal divider perpendicular to the on-screen direction between the players, so it rotates as they move around each other. Each player renders full-screen to an offscreen target; the renderer composites the two complementary half-planes (the RTT composite path). Render-verified (divider rotating; sky/horizon/players undistorted). |
/examples/peel | apps/peel_demo | New 2026-07-31 | Animated rect-peel merge (ADR 0061): SplitScreen.peel(true) — the split viewports slide together to fullscreen (z-stacked, over the RTT composite path), the Kronnect “views expand together” look, instead of the default camera-converge blend. Render-verified (2 players; panels slide seamlessly into one). |
/examples/pip | apps/pip_demo | New 2026-07-31 | Picture-in-picture with the raw Viewport primitive (CAM-19, ADR 0062): a top-down minimap Viewport composited over a third-person Cam main view (rect/z/opacity/border + own camera). Render-verified (minimap shows the corner landmarks + centred player). |
/examples/split-hud | apps/split_hud_demo | New 2026-07-31 | Per-viewport HUD (CAM-18, ADR 0064): Graphics.perViewportOverlays(true) replays a retained-UI HUD into each split-screen cell (scissored), so a per-player HUD stays inside its region. Render-verified (a 4-way grid; the HUD appears in every cell, clipped — no spill across dividers). |
/examples/plume-triangle | apps/plume_triangle | Current | — |
/examples/aspect-ratio | apps/aspect_ratio_demo | Current | — |
/examples/window-demo | apps/window_demo | Current | — |
/examples/audio-basic | apps/audio_basic | Current | — |
/examples/audio-3d | apps/audio_3d | Current | — |
/examples/audio-player | apps/audio_player | Current | — |
/examples/audio-player-3d | apps/audio_player_3d | Current | — |
/examples/blend-load | apps/blend_load | Current | — |
/examples/physics-simple | apps/physics_simple | New 2026-03-25 | — |
/examples/physics-stack | apps/physics_stack | New 2026-03-25 | — |
/examples/physics-friction | apps/physics_friction | New 2026-03-25 | — |
/examples/physics-constraint-hinge | apps/physics_constraint_hinge | New 2026-03-25 | — |
/examples/physics-particles-debris | apps/physics_particles_debris | New 2026-03-25 | — |
/examples/physics-golf | apps/physics_golf | New 2026-07-16 | Restitution side by side: three spheres (0.0/0.5/0.9) dropped from the same height. Correction: the old row called this a “restitution/friction/damping” demo — friction (0.4) and damping (0.05, 0.3) are identical on all three, so restitution is the only variable. Procedural geometry, no .blend, no input; auto-screenshots at t=2.2s then exits, doubling as a headless smoke test (engine PLM-026/055). Despite the name, there is no golf. |
/examples/physics-mesh-terrain | apps/physics_mesh_terrain | New 2026-07-16 | Ball rolls into a parabolic trough (y = 0.14x²) built as a 17×9 grid in Wren and used as both the render mesh and the addStaticMesh collider, so visuals and collision agree by construction. Winding verified upward (Jolt mesh shapes are one-sided). Procedural, no .blend, no input; auto-screenshots at 2.4s then exits (engine PLM-056). |
/examples/physics-heightfield | apps/physics_heightfield | New 2026-07-16 | Ball settles into a bowl from a 16×16 row-major height grid (cellSize 0.8) feeding both addStaticHeightfield and the render mesh. Documents the main gotcha: the grid’s (0,0) sample sits at the node origin and extends +X/+Z, so the node is offset to centre the bowl. Procedural, no .blend, no input; auto-screenshots at 2.8s then exits (engine PLM-057). |
/examples/sprite-demo | apps/sprite_demo | New 2026-07-16 | Billboarded sprites (engine PLM-009 / ADR 0008): one Texture.load PNG shared by four tinted Sprites. Depth sorting is engine-side (per-frame view-space z), not draw order. Only exercises billboard = 0 (spherical) — cylindrical/flat ship but are unexercised by any app. Procedural scene, no .blend, no input. |
/examples/sdf-text-demo | apps/sdf_text_demo | New 2026-07-16 | SDF text (engine PLM-010 / ADR 0007): world-space draw(model), screen-space drawScreen(x,y), outline, wrap/align and rich-text markup — all confirmed present. Unusual: no camera and no scene; identity view/projection puts the model matrix straight in NDC. size is world units for draw, pixels for drawScreen. |
/examples/character-controller | apps/character_controller | New 2026-03-25 | — |
/examples/character-anim | apps/character_anim | New 2026-07-23 | The Animation Controller epic end-to-end demo (engine PLM-086, CT-M1): a skinned Character.blend/Fire_Sprite driven by a TOML controller via node.attachController — a blend-node locomotion State, typed speed param from input, root-motion designation, a tagged cancel section driving input-buffering/cancel-windows, and an onEnter hook from the auto-tick. Screenshot-verified (character skins + animates via the controller, no validation errors). Fixture-limited to one action (Idle_ArmSwing) → single-entry blend + no masked-Action clip yet (richer rig: HUMAN-014). |
/examples/ui-demo | apps/ui_demo | New 2026-07-24 | Retained-mode UI end-to-end (engine PLM-108, ADR 0019, Block U/U-M1): Ui.load("ui/main.ui") + Ui.render(doc, 0, 0, Window.getWidth(), Window.getHeight()). A .ui document (dark backdrop, centered card, title/subtitle, a Play button whose on_click = "onPlay" dispatches to Game.onPlay()) letterboxed full-window. Panels/button draw as quads, labels via the SDF text pass. Screenshot-verified (ui_demo.png at ~1.5s). |
/examples/ui-widgets | apps/ui_widgets | Updated 2026-07-29 (PLM-141) | Retained-UI widget breadth (engine PLM-110/141, Block U/U-M2): a type="toggle" checkbox + a type="slider" (drag to set value) whose state the game polls each frame (element.checked/element.value) to recolour a swatch panel. Widgets own their state; the game reads it (optional on_change handler). 2026-07-29 (PLM-141, ADR 0039): extended with a display-only type="progress" bar and a Low/High type="radio" group (mutually exclusive via a shared group, polled through the same checked accessor). Screenshot-verified. |
/examples/ui-canvas | apps/ui_canvas | New 2026-07-24 | Retained-UI Canvas custom-draw (engine PLM-111, Block U/U-M3): a type="canvas" region the game draws into with Ui.canvasRect/canvasLine/canvasBezier/canvasText (canvas-local design px, colours 0..1), composited + letterboxed. Demo draws a node graph (boxes + gold bezier wires + labels) — the node-editor building blocks. Screenshot-verified. |
/examples/node-editor | apps/node_editor | Updated 2026-07-24 (PLM-123/124) | The Block-U capstone (engine PLM-087, U-M4; enhanced by PLM-112/113/114/118/121/122/123/124): a visual node editor for AnimController graphs — a unified state/blend/clip graph + a Mecanim Any State on the retained-UI Canvas, chrome via immediate-mode Gui. Typed any-direction wires; pan/zoom (wheel + middle-drag + Fit); multi-select (box/shift/multi-drag); dropdowns (structured guard editor + to/ref/clip/param pick-lists); and a 3D Preview mode that renders the skinned character playing the edited controller live (WASD/Space/K → params; orbit+zoom+track camera; floor/gravity toggles + ground-check; a collection dropdown that auto-discovers the model’s collections and remembers your pick, no config file). Plus a Layers panel (edit a pose-overlay layer’s name/blend/mask, see the Actions bound to it — renaming re-points them) and live per-layer weight sliders in the preview. Loads/edits/saves a controller .toml (PLM-077 schema + [any_state] + transition event), round-tripping it losslessly — including [[layers]]/[transitions]/[actions]/[contexts] (preserved verbatim instead of dropped), with blend1d/blend2d each edited and saved in its own schema (a 2D node used to be written back malformed and then rejected by the engine) and clip sections preserved. --tool-capable. Screenshot-verified. |
/examples/ui-editor | apps/ui_editor | New 2026-07-24 | WYSIWYG .ui editor (engine PLM-109, ADR 0019/0020, Block U — closes U-M1): a Nuklear-chrome editor (left: add/delete/save + element tree; right: inspector) over a live canvas rendered by Ui.renderEdit (the identical runtime ⇒ WYSIWYG), click-to-select via Ui.hitTest, a selection gizmo via transparent Gui.beginOverlay. Built on the editor-authoring tier (Ui.newDocument/createElement/deleteElement/reparent/save, UiElement rect/color/fontSize/…). Runs standalone or plume3d --tool apps/ui_editor <target> (the data/script-root split, ADR 0020). Screenshot-verified. |
/examples/gui-overview | apps/gui_overview | Current | — |
/examples/gui-input | apps/gui_input | Current | — |
/examples/gui-calculator | apps/gui_calculator | Current | — |
/examples/gui-configurator | apps/gui_configurator | Current | — |
/examples/gui-terminal | apps/gui_terminal | Current | — |
/examples/mesh-lines | apps/mesh_lines | Current | — |
/examples/mesh-points | apps/mesh_points | Current | — |
/examples/mesh-indexed | apps/mesh_indexed | Current | — |
/examples/mesh-draw-range | apps/mesh_draw_range | Current | — |
/examples/mesh-dynamic | apps/mesh_dynamic | Current | — |
/examples/mesh-attributes | apps/mesh_attributes | Current | — |
/examples/mesh-transparency | apps/mesh_transparency | New 2026-07-23 | Per-mesh render state on procedural meshes (engine PLM-103, follow-up to PLM-025/ADR 0026): an opaque red quad behind two translucent panes set with mesh.blendMode = "alpha", plus doubleSided/cullMode. Engine partitions opaque-then-transparent and blends back-to-front (depth-write off); the shader emits vertex alpha. Procedural, no .blend, no input; auto-screenshots at t=2s. .blend-imported materials set the same state automatically. |
/examples/instanced-mesh-gpu | apps/instanced_mesh_gpu | New 2026-03-25 | — |
/examples/material-textures | apps/material_textures | New 2026-07-30 | Script-assigned PBR material textures (engine PLM-021/PLM-177, ADR 0055): a UV cube built with Graphics.newMesh gets its full six-map set (base/metallic/roughness/normal/AO/emissive) assigned from Wren via mesh.set*Texture, drawn with a reference shader that samples set 1 bindings 1..6 with derivative-TBN normal mapping. The reference shader deliberately omits the set-0 shadow array so it compiles on macOS/MoltenVK without shadow mapping (root-caused in PLM-177). Screenshot-verified. |
/examples/gui-buttons | apps/gui_buttons | New 2026-03-25 | — |
/examples/gui-chart | apps/gui_chart | New 2026-03-25 | — |
/examples/gui-combo | apps/gui_combo | New 2026-03-25 | — |
/examples/gui-drawing | apps/gui_drawing | New 2026-03-25 | — |
/examples/gui-labels | apps/gui_labels | New 2026-03-25 | — |
/examples/gui-layout | apps/gui_layout | New 2026-03-25 | — |
/examples/gui-menu | apps/gui_menu | New 2026-03-25 | — |
/examples/gui-popup | apps/gui_popup | New 2026-03-25 | — |
/examples/gui-property | apps/gui_property | New 2026-03-25 | — |
/examples/gui-showcase | apps/gui_showcase | New 2026-03-25 | — |
/examples/gui-sliders | apps/gui_sliders | New 2026-03-25 | — |
/examples/gui-theme | apps/gui_theme | New 2026-03-25 | — |
/examples/gui-toggles | apps/gui_toggles | New 2026-03-25 | — |
/examples/gui-tree | apps/gui_tree | New 2026-03-25 | — |
/examples/gui-window | apps/gui_window | New 2026-03-25 | — |
/examples/toml-editor | apps/toml_editor | New 2026-06-29 | GUI game.toml editor; documents standalone + --tool usage. Added to examples index under “Tools”. |
/examples/save-load-demo | apps/save_load_demo | New 2026-07-29 | Save/Load end-to-end (engine PLM-022, ADR 0036): reads a slot, increments a persisted launch counter, writes it back — re-run to watch it climb. Game-authored SaveState (int/string/bool/number) via Save.write/Save.read; null read = no save yet or a newer-build save. Press S to re-save; a Gui window shows the count. Added to examples index under “Data & persistence”. |
/examples/coroutine-demo | apps/coroutine_demo | New 2026-07-29 | Coroutines end-to-end (engine PLM-031, ADR 0037): two Async.run coroutines self-sequence with no per-frame timer code — one cycles a traffic light with Async.wait (GREEN 3s → YELLOW 1s → RED 3s, looping), the other counts frames with Async.nextFrame (one resume/frame). The engine auto-ticks both; a Gui window shows the light, frame count, and Async.pending (2). Added to examples index under “Scripting”. |
/examples/ui-input-demo | apps/ui_input_demo | New 2026-07-29 | Retained-UI editable text input end-to-end (engine PLM-142, ADR 0041): two type = "input" fields — a Name field that opens with initial text (Ada Lovelace) and a City field that shows a dimmed placeholder until you type. Click to focus (a caret appears; a press elsewhere blurs); typing edits the buffer (UTF-8-aware insert/backspace/delete + arrow/Home/End). The game reads the value via the existing elem.text accessor and reacts to on_change/on_submit (Enter) — no new Wren method. v1 = single-line (selection/clipboard/multi-line/IME deferred, PLM-145). Auto-screenshots ui_input_demo.png at ~1.5s. Added to examples index under “Mesh & graphics”. |
/examples/ui-nineslice-demo | apps/ui_nineslice_demo | New 2026-07-29 | Retained-UI textured 9-slice end-to-end (engine PLM-139/140, ADR 0038): two panels of very different sizes (160×120 and 500×400) share one image = "textures/panel.png" referenced by path, and a textures/panel.png.ui.component sidecar marks it nine_slice with a border = [24,24,24,24] — so both frames keep the same fixed-size corners while their edges/center stretch. No bare texture handle, no per-.ui border (the sidecar supplies it). Auto-screenshots ui_nineslice_demo.png at ~1.5s (host render-verified: corners unstretched). Added to examples index under “Mesh & graphics”. |
/examples/audio-streaming-demo | apps/audio_streaming_demo | New 2026-07-29 | Audio float-PCM streaming end-to-end (engine PLM-133, ADR 0042): creates a mono 48 kHz streaming source with Audio.newStreamingSource(48000, 1) and, each frame while queuedBuffers < 4, synthesizes ~1/60 s of a 220 Hz sine and queues it via queueSamples (interleaved floats in [-1,1]); the engine recycles buffers + auto-(re)starts playback so the tone is gapless. A Gui window shows the frequency + live queued-buffer count. Added to examples index under “Audio”. |
/examples/audio-ducking-demo | apps/audio_ducking_demo | New 2026-07-30 | Audio ducking / sidechain (engine PLM-148, ADR 0047): a Music drone and a Voice tone each routed to a Mixer.group; music.duckUnder(voice, -14, 0.05, 0.4) ducks Music 14 dB while Voice plays. Music is fed every frame; Voice is fed only while Space is held, so holding Space fires the duck and a Gui.progress bar shows the Music group’s live duckGain. Activity-driven (fires on a playing source in the trigger subtree). |
/examples/audio-reverb-demo | apps/audio_reverb_demo | New 2026-07-30 | Per-group reverb via OpenAL EFX (engine PLM-153, ADR 0050): a looping music track (TheFieldOfDreams.mp3) routed to Mixer.group("Music"), with a button cycling the group’s reverb preset (Off/Cave/Hangar/Underwater/Concert Hall) via music.clearEffects() + music.addReverbPreset(name). The engine builds the EFX aux slot behind the mixer. Needs a device with ALC_EXT_EFX; host-run verified. |
/examples/audio-effects-demo | apps/audio_effects_demo | New 2026-07-30 | The full OpenAL EFX effect suite (engine PLM-154/157, ADRs 0051/0052): the same looping music on Mixer.group("Music"), with a button per effect (reverb/echo/chorus/flanger/distortion/pitch/ring-mod) that swaps the chain via clearEffects() + addEffect(type) + setParam(...), a button building a serial chain (distortion → echo), and a row of direct-path filter buttons (muffle/thin/clear via setLowPass/setHighPass/clearFilter). Needs a device with ALC_EXT_EFX; host-run verified. |
/examples/audio-mixer-studio | apps/audio_mixer_studio | New 2026-07-30 (effects added PLM-159) | A wired GUI audio-mixer console (engine PLM-150/159): a looping music bed + one-shot SFX (the audio_player music clip + audio_3d SFX) routed to Mixer.group buses, with per-group volume slider + mute/solo channel strips, a Master fader, a ducking toggle, a Music-bus reverb preset cycle + low-pass toggle (the EFX stack), and a Save-mix button (Mixer.saveSettings() → settings.toml). Group defaults from game.toml [Mixer]. The complete Unity-parity console — groups + gain/mute/solo + ducking + effects + filter + config + persistence together. Host-run verified. |
/examples/audio-mixer-demo | apps/audio_mixer_demo | New 2026-07-30 | Audio mixer groups (engine PLM-147, ADR 0046): three procedural tones (Music/SFX/Ambience) each an ordinary streaming Source routed to a Mixer.group(...) via addSource; per-group sliderFloat volume + checkbox mute/solo, plus Mixer.master. The engine applies each group’s effective gain to its routed sources every frame, so faders/mute/solo take effect live. Shows regular audio (not just instruments) routing through the mixer. |
/examples/midi-soundfont-demo | apps/midi_soundfont_demo | New 2026-07-30 | Play a SoundFont from a USB-MIDI keyboard (engine PLM-136, ADR 0048): loads a .sf2, opens the first MIDI input, and calls Midi.route(inst) so the engine feeds device events straight to the instrument (native low-latency route). A computer-keyboard fallback (A–K) plays it without MIDI hardware; the window shows the routed device + active voices. Bank is a git-ignored loaded resource (see README). Live device play is a manual/HUMAN-017 check. |
/examples/midi-input-demo | apps/midi_input_demo | New 2026-07-30 | MIDI monitor (engine PLM-135, ADR 0044): lists MIDI input devices with Midi.inputDevices, opens device 0 with Midi.openInput(0), and each frame drains Midi.poll() into a scrolling Gui log of note/controller/pitch-bend events (main-thread-safe; drained off the device callback thread by the engine). Shows a hint when no device is connected. Desktop only; live note-in needs a USB-MIDI keyboard (HUMAN-017). |
/examples/soundfont-demo | apps/soundfont_demo | New 2026-07-30 | SoundFont instrument played from the computer keyboard (engine PLM-134, ADR 0043): loads a .sf2 bank with SoundFontInstrument.new("soundfonts/YDP-GrandPiano.sf2", 48000, 2), edge-detects the A–L / W–O rows into one piano octave (press → noteOn, release → noteOff), maps Space to the sustain pedal via controlChange(64, …), and Z/X shift octave (the engine renders the instrument’s audio automatically). The bank is a git-ignored loaded resource (fetch per apps/soundfont_demo/README.md); the demo shows a hint if it is absent. A Gui window shows the bank name, octave, and live active-voice count. |
| App | Category | Status |
|---|
apps/skeletal_animation | Skeletal animation (demo) | Loads a rigged .blend (armature + skinned mesh), attaches the mesh to its armature, and plays a skeletal clip; Scene.draw() GPU-skins any armature-bound node. Ships with the Bramble_Sprite character (Models/Character.blend, 13-bone rig, Idle_ArmSwing). The producer-side path is complete as of 2026-07-22 (engine Block AF — skin-weight parse PLM-070 + armature attach PLM-073, on top of the runtime skinning PLM-071/072/074/076; ADR 0022). No new public Wren/CLI/config surface — the demo drives the existing node.animations[name].play() API. Owner to author the full /examples/skeletal-animation page (and a landing “What’s in place” card if skinning is surfaced as a headline capability) once the character is finalized. |
apps/low_tide | Game (pre-production) | No game.toml or Wren scripts yet — only a .blend file and a PDF design doc. Document when the game is runnable. |
apps/net_echo_server + apps/net_echo_client | Networking | Client/server pair (server via --server, client windowed). Covered inline on /api/net + /dedicated-server. |
apps/net_loopback_demo | Networking | Self-contained headless Net smoke test (connects to itself; run via --server); covered inline on /api/net. |
apps/net_replication_demo | Networking | Headless replication smoke test; covered inline on /api/net. |
apps/net_arena_server + apps/net_arena_client + apps/net_arena_probe | Networking | Visual multiplayer demo: clients pick a shape (keys 1-4); the server orbits + replicates one shape per client. Covered inline on /api/net. A standalone /examples/net-arena page (with a screenshot) would be a good addition. |
| Page | Status | Notes |
|---|
/ (landing page) | Updated 2026-07-29 | 2026-07-29 (roadmap reconcile): removed roadmap items that already ship (they were listed as pending and present in “What’s in place”): dropped Frustum culling from Short-Term (the Rendering card already lists “per-frame frustum culling”); narrowed Short-Term Rendering robustness to just the Depth pre-pass (back-face culling + alpha/double-sided already ship); narrowed Long-Term Animation+ to IK / retargeting (blend trees ship via the Animation Controller). Save/load and Scripting (coroutines/async) stay in Short-Term — genuinely not yet implemented (engine PLM-022/PLM-031, both backlog). 2026-07-16: added the “What’s in place” cards that four months of engine syncs had silently skipped — Sprites & Textures, SDF Text, Networking, HTTP — plus Engine.entropy() on the Randomness card and crash reporting (macOS/Linux; a no-op on Windows) on Tooling & Testing. Removed the overstated “touch” claim from the Input card (C++-only, no Wren binding). Added the missing classes to the three Wren class enumerations. Prior: 2026-03-25 roadmap restructure; Vaultis added to Tools. |
/getting-started | Current | — |
/testing | New 2026-06-29 | Headless testing guide: plume3d --test, tests/run.wren → Tests.main() convention, Engine.exit, full exit-code table, CI example. Sidebar entry added after Configuration. |
/dedicated-server | Updated 2026-07-03 | Dedicated-server run mode (plume3d --server): headless Game.update tick loop, [Network] config, shutdown/exit codes. Networking transport (GNS), replication, and the PlayFlow client are live (links to /api/net, /api/playflow); only a live PlayFlow account is pending (HUMAN-007). Engine runbook: RUNBOOK.md. |
/downloads | Updated 2026-03-25 | Added Vaultis companion tool section |
/blender-addon | Updated 2026-07-12 | Addon 0.2.0 (PLM-044…054): Validate + Write Config buttons, class picker, multi-select batch layers, preferences fallback layer names, extension manifest (Blender 4.2+), collision default = engine “Default” layer, project-rooted forward-slash paths, no-overwrite file creation. |
/configuration | Updated 2026-07-16 | Covers [Game], [Window], [Physics], [Network]. 2026-07-16 — the page was billed as the full game.toml reference while [Window] had no keys at all and collision_matrix was documented backwards: matrix indices are Jolt motion layers (0 = static, 1 = moving), not indices into collision_layers; the page’s own former example [[0,0],[1,1]] disabled static-vs-dynamic contacts and dropped every dynamic body through the floor. Now documented truthfully with a danger callout. Added all ten [Window] keys with real defaults, [Game] (incl. undocumented terminal), the mandatory-[Game] parser rule, PLUME_SERVER_PORT, and fixed [Network].max_clients (a hard cap, not advisory; default 32 applies only when [Network] is present — otherwise 0 = unlimited). |
/licensing | Current | — |
/engine-license | Current | Third-party table accurate as of 2026-03-25 |
| Feature | Location in engine | Doc needed |
|---|
Wren #!command terminal attribute | wren_engine_module.cpp (EngineHelper) | Covered in GUI Terminal only; could use a dedicated guide |
| Hot-reload | modules/hotreload/ | Mentioned in landing page and getting-started; no dedicated page |
| PhysicsFS virtual filesystem | engine/src/fs_mount.cpp | Not documented; .p3d archive format not described |
| Blender 5.0 Kaitai spec | tools/validate_plume3d_blend_kaitai5/ | Tools page in engine repo covers this; no website page |
| CI/CD (Gitea Actions, Docker) | .gitea/workflows/, docker/ | Dev-only; no website page needed |
| Wren syntax highlighting bundle | tools/Wren.tmbundle/ | Could note in Getting Started as an optional setup step |
PLUME3D_BLEND_DUMP=file.txt env var | Engine runtime | Mentioned in Blend Load example; could be a debug guide |
| CMakePresets.json (24 presets) | Root CMakePresets.json | Engine BUILDING.md covers this; no website page needed |
Vaultis is now referenced in:
/ (landing page) → Tools table
/downloads → Companion Tools section
Not yet done:
- No dedicated
/vaultis page (may be appropriate as the integration matures)
- Plume3D-specific
EngineType.plume3d export preset does not yet exist in Vaultis source; it uses EngineType.other + custom import root. Update this note when a dedicated preset is added.