Welcome to Plume3D
Plume3D is a scriptable 3D game engine for building games and tools with a small, immediate-style API. You write game logic in Wren; the engine handles windowing, rendering, audio, and (optionally) physics via modular integrations. Design resolution and scale modes (stretch, letterbox, integer) give you control over aspect ratio and pixel-perfect scaling.
Integrations
Section titled “Integrations”The engine is built from a small core plus integrations that each handle a major subsystem. Scripts see a unified API; the host wires these together.
| Integration | Purpose |
|---|---|
| Wren | Scripting. Runs your game: loads the main script, exposes the engine API as foreign classes (Engine, Logger, Mesh, Input, Window, Graphics, Audio, Scene, Node, Camera, Light, Raycast, Physics, Config, Resource, AnimationState, InstancedMesh, ParticleEmitter, CharacterController, Texture, Sprite, Text, Net, PlayFlow, Http, Random, etc.), and drives the lifecycle — init(), update(dt), draw(). |
| Vulkan | Rendering. Draws meshes and UI: loads SPIR-V shaders, manages vertex/index buffers, PBR materials with shadow mapping, GPU instancing via SSBO, skeletal skinning via bone matrix SSBO, records draw calls from Wren, and provides a Nuklear Vulkan backend so the full GUI is rendered with Vulkan. |
| OpenAL | Audio. 3D positional audio: creates sources from mounted files (WAV, MP3, OGG, FLAC), play/stop/pause, per-source volume/pitch/position/cone/attenuation, and global listener position/orientation/Doppler for spatial sound. |
| Jolt | Physics. Full physics integration: rigid bodies (static/kinematic/dynamic) with multiple shapes, constraints (fixed/hinge/slider/cone/6DOF), character controller, contact event callbacks, raycasts, and complete Wren API for forces, impulses, velocities, and simulation control. |
Platform and I/O are abstracted: SDL3 is used on desktop for window and input; a console port would swap in another implementation behind the same abstractions.
Roadmap
Section titled “Roadmap”Current capabilities and planned milestones: what’s in place, what’s still coming, and longer-term direction.
What’s in place
Section titled “What’s in place”- RenderingVulkan, PBR (Cook–Torrance), full material-map set — base color, metallic, roughness, normal, AO, emissive — assignable from Wren (per-map setters on Mesh), derivative-TBN normal mapping, triplanar fallback, shadow mapping (up to 4 maps, configurable resolution/bias), debug view modes, per-frame frustum culling, per-material alpha blending + back-face culling / double-sided (from .blend materials or set in Wren via mesh.blendMode / cullMode)
- SceneScene graph, nodes, transforms, cameras, lights (point/sun/spot/hemi/area); findNodeById, tag/collision-layer system
- ContentBlender 5 .blend loader: meshes, materials, UVs, animations, armatures, physics bodies, particles, blend instantiation
- ScriptingWren API: Scene, Mesh, Camera, Light, Input, Raycast, Physics, Resource, AnimationState, AnimController, InstancedMesh, ParticleEmitter, CharacterController, Gui, Ui, Texture, Sprite, Text, Net, PlayFlow, Http, SoundFontInstrument, Midi, Mixer, MixerEffect, Nav, SurroundRing, Ai, BehaviourTree, Bt, Blackboard, LostTargetTracker, AggroTable, Tactic, StatGate, TensionModel, NoiseBus, Shape, Hitbox, Hurtbox, Hit, Combat, Illumination, FogOfWar, SoundEmitter, PhaseEvaluator, Cam, CameraRig, SplitScreen, Viewport, Random, etc.
- Coroutines / AsyncCooperative coroutines the engine ticks every frame: Async.run { … } starts a coroutine, Async.wait(seconds) pauses it in scene time without blocking the frame, Async.nextFrame() resumes next frame, with Async.pending / Async.clear. Write “do X, wait, then do Y” — sequenced animation, timed events, AI — as straight-line code instead of hand-rolled timers. Built on Wren fibers: cooperative and single-threaded (frame-scheduling, not parallelism/threads); the engine drives it, so the game wires nothing into its own update.
- InputSDL3: keyboard, mouse, 4 gamepads; “just pressed” state. Touch is tracked in the C++ InputState (up to 8 fingers) but has no Wren binding yet — not scriptable.
- AudioOpenAL: 3D positional audio, HRTF, Doppler, seek/tell, source clone, WAV/MP3/OGG/FLAC; streaming float PCM (Audio.newStreamingSource + source.queueSamples) for procedural/synthesized audio fed over time; SoundFont (.sf2) instrument playback (SoundFontInstrument) — load a bank and play polyphonic notes from any source; Unity-style mixer groups (Mixer/MixerGroup) — route SFX/Music/Ambience/instruments to buses with per-group volume, mute, solo, ducking (music-under-dialogue sidechain), and per-group effects via OpenAL EFX — the whole aux-slot suite (reverb, echo, chorus, flanger, distortion, EQ, pitch, ring-mod, autowah, frequency-shifter, vocal-morpher, compressor), chainable, tuned live with addEffect(type)/setParam, plus per-group direct-path low/high/band-pass filters (setLowPass/setHighPass)
- MIDI inputLive MIDI device input on desktop (libremidi): Midi.inputDevices / openInput / poll normalized note, controller and pitch-bend events. Parsed off the device thread, delivered on the main thread. Midi.route(instrument) plays a SoundFont straight from a USB/virtual MIDI keyboard (native low-latency route).
- PhysicsJolt: static/dynamic/kinematic bodies, box/sphere/capsule/cylinder/convexHull shapes, triangle-mesh + heightfield terrain colliders, tunable material (restitution/friction/damping), constraints (hinge, slider, cone, 6DOF), contact events (begin/end/stay), full Wren API (forces, impulses, velocities, raycast, pause); spatial queries for gameplay/AI — overlapSphere (bodies near a point) and a per-body gameplay collision-layer bitmask (setCollisionLayer/getCollisionLayer) that filters overlaps + masked raycasts (Raycast.fromPointMasked) without changing what physically collides; manual fixed-step simulation (Physics.step/stepN) for deterministic, headless runs
- Character ControllerCapsule-based CharacterVirtual (Jolt): ground detection, step, jump, state machine (idle/walk/run/jump/fall/swim/crouch), configurable speeds
- CameraCinemachine-like camera rigs with no draw() plumbing: one-call Cam.thirdPerson / firstPerson / topDown / orbit / freeFly over a moving target, camera blending (Cam.blendTo with easing — position lerp + rotation slerp + lens crossfade), procedural handheld noise, impulse shake (Impulse.emit — decaying + distance-attenuated), and an occlusion collider (CameraRig.collider — pulls the camera in front of walls that would hide the target). Local co-op split-screen (SplitScreen / SplitLayout): each player gets their own third-person view in its own screen region, with dynamic merge/split — the views fuse into one group-framed camera when players cluster and split back apart when they spread, as a smooth animated blend (transitionDuration / mergeFraction). Layouts include a Kronnect-style angled split (SplitLayout.angled) — a 2-player diagonal divider that rotates with the players — and an animated rect-peel merge (SplitScreen.peel), all composited from per-viewport offscreen targets. The same RTT path powers a raw Viewport primitive (Viewport.new) for picture-in-picture / minimap / security-cam — a screen region with its own camera, composited over the main view by z/opacity. A per-scene director auto-ticks after update() and pushes the view/projection itself; coexists with the manual Graphics.setViewMatrix path (last-write-wins). Device-free, unit-tested solver core; plus the low-level Camera class (view/projection, world/screen conversion) it drives.
- AI & NavigationAuthor enemy AI in Wren: a behaviour tree (BehaviourTree + Bt.sequence/selector/leaf with closure leaves over a typed Blackboard) that the engine ticks for you — no per-frame plumbing; a lost-target → search → give-up FSM (LostTargetTracker), aggro/tactic/stat-gate selection (AggroTable, Tactic, StatGate) and an L4D-style pacing director (TensionModel); a detect-by-sound noise bus (NoiseBus / AlertMemory / PainMemory); and one-call perception (Ai.nearestVisibleTarget — range + field-of-view + line-of-sight, in one native call). Underneath: a Recast/Detour navmesh (Nav.bake / Nav.findPath) that routes around obstacles — genre-general (top-down / third-person / FPS), not a top-down- or swim-specific abstraction — and a surround-ring solver (SurroundRing) so a pack encircles a target instead of stacking on it, composing with the perception primitives on Physics (overlapSphere — who is near me; Raycast.fromPointMasked — layer-filtered line of sight). Device-free, unit-tested cores; see the ai_hunters example, which drives the whole loop from one Wren tree.
- Behaviour-Tree AuthoringAuthor enemy / mini-boss / boss behaviour trees as DATA and load them at runtime — for a GUI editor. The runtime uses Wren closures for leaves (great for hand-written AI), but a GUI can’t draw a closure, so the data path adds NAMED leaves: Bt.registerLeaf(name, paramSpec) registers an action leaf the editor lists (Bt.leafCatalog) and a .bt.toml references by name with typed params, and Bt.load(path) builds an engine-ticked tree from a hardened, bounded, cycle-checked .bt.toml (unknown-leaf / bad-param / cycle → Bt.loadError, existing tree left intact — the loader passed a blocking /security-review). Additive to the closure surface (Bt.leaf is unchanged). Boss phases: PhaseEvaluator.evaluate(currentPhase, health01, elapsed, thresholds) returns a forward-only phase the game feeds health01 into (the engine never reads HP) and writes to a blackboard int a phase_switch node reads — a regular enemy is one root, a mini-boss ~2 phase entries, a boss 4+, the same data machinery. The engine runs the tree; your game owns the policy (HP, damage, phase thresholds). See the bt_editor example — a palette from the live catalog, a 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 — and the bt_boss_demo example, where a phaseless enemy, a 2-phase mini-boss, and a 4-phase boss are one schema differing only in data.
- Light, Sound & VisionLight and vision as mechanics — the engine samples, the game owns the policy (no stealth/HP rules ship in the engine). Illumination.illuminanceAt(scene, x, y, z) → a continuous 0..1 “how lit is this point” (per-light falloff matching the render, with masked line-of-sight occlusion) — bidirectional (your lamp lights you), a raw value the game thresholds for a hard “spottable” stat or reads as a soft exposure meter. FogOfWar — a world-XZ visibility field (Unknown/Explored/Visible): configure bakes an occluder grid from static geometry, reveal marks line-of-sight cells with the view radius shrinking in the dark, Explored persists as memory, both team-shared and per-player, plus a wall-penetrating sonar reveal channel. NoiseBus.emitOccluded — the same wall that muffles a sound shrinks what the AI hears; SoundEmitter unifies the audible and audible-to-AI sides. Device-free, unit-tested cores; see the lsv_demo example, whose HUD meters exposure + noise. (Still landing under the surface: the per-source audio low-pass, a long-range “hear-before-see” emitter, and the on-screen fog render.)
- Action CombatHit/hurt boxes are Jolt sensor bodies riding a character’s bones — authored in Blender (meshes in Plume_Hitboxes / Plume_Hurtboxes collections, name = tag) or built by hand — and Jolt’s own collision detection drives Combat.onHit / onStay / onExit (owner/layer filtered, once-per-swing by default, with independent / reHitSeconds for flurries and damage-over-time). Instantiated volumes surface on node.hitboxes / node.hurtboxes tag maps; node.attachToBone is an engine-driven weapon/VFX socket. The engine DETECTS hits; your game OWNS health — there is no HP/damage/death type in the engine, only an opaque payload carried into each Hit (your damage or entity id). Hitbox.bindWindow gates a strike to an animation hit-window authored as Blender pose-markers; a disabled hurtbox is i-frames. Plus swept/overlap/pierce shape queries on Physics (castShape / overlapShape / raycastAll) over rigid bodies. Device-free, unit-tested core; see the combat-blender-demo example.
- AnimationKeyframe/FCurve from Blender: AnimationState, blend layers, crossfade, events (Blender markers), drivers, skeletal (bone hierarchy, skinning matrices)
- Animation ControllerPer-character AnimController (node.attachController): a TOML-authored graph of states, blend nodes (1D/2D), guarded transitions, a Mecanim-style Any State (global transitions that fire from any state), masked overlay layers (override/additive, with runtime per-layer weight via setLayerWeight) and root motion, with a lean State / Action / Context model — Actions overlay locomotion without exiting it, Contexts gate what’s allowed. Plus typed params, clip phase (normalizedTime, tagged sections, latch-and-refire END), behaviour hooks, and transition events (a named event per transition + an onTransition firehose). Auto-ticked and hot-reloaded each frame. Authored via a TOML asset, with a visual node editor (apps/node_editor) that round-trips it — states, blend trees and clips as nodes, with typed any-direction wires (transitions, plays, blend entries), a Layers panel, and a 3D preview that plays the edited controller on a skinned character with live layer-weight sliders.
- ParticlesTier 2 Jolt physics particles: body pool, emission rate, burst, play/stop/pause; rendered via instanced mesh
- Instanced MeshGPU instancing via SSBO: up to 65K instances per draw, per-instance position/transform/scale, dynamic updates, visibility toggle
- Sprites & TexturesTexture.load (PNG/JPG/WebP from the mounted project) → GPU texture; Sprite: camera-facing, depth-sorted, alpha-blended quads with world-space size, RGBA tint, and spherical/cylindrical/flat billboard modes
- SDF TextSigned-distance-field text, crisp at any scale: world-space draw(model) or screen-space drawScreen(x, y); face/outline color, outline width, softness, align, wrap, line spacing, rich-text markup, measured width/height/lineCount; built-in Inter font
- GUIFull Nuklear API: themes, fonts, layouts (dynamic/static/template/space), sliders, knobs, combos, trees, popups, menus, charts, color picker, custom drawing
- Retained UIData-driven retained-mode UI (Ui.load / Ui.render): author a panel/text/button tree in a
.uiTOML file, render it letterboxed into a screen rect (uGUI-style anchors, resolution-independent); buttons dispatch on_click → Game.<name>(); panels/images draw as quads, labels reuse the SDF text pass. Panels can draw a textured 9-slice (fixed corners, stretched edges/center) with the image referenced by path (image = “ui/panel.png”) and slicing authored once in a.ui.componentsidecar beside the PNG. Widgets: panel, text, button, toggle (checkbox), slider, progress bar, radio (grouped, mutually exclusive), text input (editable single-line fields, focus by click, UTF-8-aware caret), and a canvas element for custom drawing (rects/lines/bezier/text via Ui.canvas*). Container elements can auto-position their direct children with layout groups (horizontal / vertical / grid, with spacing/padding) that compose with the anchor system. Includes a runtime authoring API and a WYSIWYG visual editor (apps/ui_editor, —tool): element tree, inspector, live canvas, selection gizmo - Configgame.toml, class-based scripts (e.g. Radio, GameCamera), hot reload
- Save & LoadGame-authored, versioned save state: SaveState is a typed key/value store (bool / int / number / string); Save.write / Save.read / Save.exists serialize it to human-readable, versioned TOML under the app’s write directory. Not an automatic scene dump — the game chooses what to persist. A save written by a newer engine build reads back as null (rejected, never corrupted); enumerate slots with Resource.list.
- LightingMultiple lights (point/sun/spot/area) with shadow casting; Graphics.setLights + shadow mapping controls
- NetworkingGameNetworkingSockets transport: Net.startServer/connect/send (reliable or unreliable)/poll; server-authoritative entity replication with snapshot broadcast and client-side interpolation; dedicated-server mode (plume3d —server) runs game logic headless; PlayFlow Cloud client to request and poll a hosted server
- HTTPHttp.post(url, body): blocking JSON POST over HTTP/HTTPS → Map of ok/status/body, for out-of-band traffic like opt-in telemetry. Blocking — call it at event boundaries, never per frame.
- RandomnessSeeded deterministic RNG (PCG32): Random.new(seed) → int, intRange, float, shuffle; identical seed ⇒ identical sequence cross-platform, for reproducible runs and daily seeds. Engine.entropy() gives a non-deterministic OS-entropy seed when you need uniqueness (install ids) instead of reproducibility — not cryptographic.
- Tooling & TestingHeadless test mode (plume3d —test) runs Wren game logic with no window/Vulkan and exits 0/non-zero for CI; drive physics deterministically with Physics.step to assert simulated outcomes headlessly; Engine.exit(code); —tool editor mode for in-engine tools; automatic local crash reports on macOS/Linux — a fatal signal writes crashes/crash_<pid>.txt (signal, pid, backtrace) and is never uploaded, on Windows this is currently a no-op
Short-Term: Indie-level (small–medium 3D)
Section titled “Short-Term: Indie-level (small–medium 3D)”- Material texturesNormal map, roughness/metallic texture maps — PBR structure in place; texture samplers not yet wired to Wren.
- AudioOptional reverb zones (OpenAL EFX extension); environmental audio effects.
- Basic LODOptional: swap mesh by distance (single LOD fine for many indies).
- Depth pre-passOptional opaque depth pre-pass to cut overdraw (perf) — back-face culling and alpha/double-sided handling already ship (see Rendering, above).
Long-Term: AA-level (larger scope, higher fidelity)
Section titled “Long-Term: AA-level (larger scope, higher fidelity)”- Lighting pipelineMany lights (deferred or clustered forward), CSM, point cubemap shadows, contact shadows.
- Global illuminationLight probes, SSAO, simple reflection (probes or SSR).
- Post-processingBloom, tone mapping, DoF, motion blur, color grading.
- Asset pipelineMore formats (FBX/glTF), texture compression (ASTC/BC), clear “cook” step for release.
- Level streamingLoad/unload scene chunks by region; async asset loading.
- Occlusion cullingPVS or GPU occlusion to skip hidden objects.
- LOD systemMultiple LODs per model, selection by distance/screen size, optional impostors.
- Animation+IK, runtime retargeting. (Blend trees already ship — see Animation Controller, above.)
- Physics+Ragdolls, vehicles, trigger volumes.
- TerrainHeightmap or splat terrain, texture splatting, optional foliage.
- UI scaleScale Nuklear to complex HUD/menus; layout, themes, localization.
- Profiling / toolsFrame profiler, scene inspector, Blender-centric workflow tools.
Libraries
Section titled “Libraries”The engine uses these vendor libraries for shaders, file access, GUI, and config:
| Library | Purpose |
|---|---|
| Slang | Shaders. Compiles .slang shader source to SPIR-V for Vulkan. Apps ship compiled .spv files; the engine can watch source and recompile on change when hot-reload is enabled. |
| PhysicsFS | Virtual filesystem. Mounts the game directory or a .zip/.p3d archive as the project root so all asset and script loading (shaders, Wren, audio, TOML, blends) goes through one path. |
| Nuklear | Immediate-mode GUI. Single-header UI toolkit for windows, layout, labels, buttons, and edit fields. The Vulkan integration provides a Nuklear backend so Wren’s Gui API draws with Vulkan. |
| TomlPlusPlus | TOML parsing. Reads game.toml and engine.toml for window, design size, scale mode, physics layers, collision matrix, and other config; used by the host and config_toml module. |
Modules
Section titled “Modules”Supporting modules (used by the host and integrations) provide:
| Module | Purpose |
|---|---|
| app_package | Resolves the game path (directory, game.toml file, or .zip/.p3d archive) into a mount root for PhysicsFS so the engine can load assets and scripts from the project. |
| blend_load | Loads Blender .blend files (via Kaitai Struct): parses scene data, objects, cameras, lights, materials, textures, and mesh data so the engine can instantiate hierarchies and render them. |
| config_toml | Parses game.toml and engine.toml: window size, design resolution, scale mode, physics tag/collision layer names, and collision matrix. |
| audio_load | Decodes audio from memory (WAV, MP3, OGG/Vorbis, FLAC) into PCM for OpenAL. Used with PhysicsFS so all file access stays on the mounted project. |
| hotreload | Polling-based file change detection: watches config and script files by mtime so the engine can reload games and shaders during development without restarting. |
| Tool | Purpose |
|---|---|
| Blender addon | Lets you attach Wren class and config path references to objects and collections in Blender. The addon stores config path, script path, class name, Node Id, and tag/collision layers in the .blend; the engine loads config and scripts from the project and instantiates your classes. Save the .blend after configuring so the engine sees the metadata. |
| Vaultis | A companion desktop asset manager for game developers (macOS/Windows/Linux). Discovers Unity Asset Store packages (.unitypackage, .zip, .tar), imports them into a local vault, provides 3D model and audio previews, and exports assets to engine projects (Unity, Unreal, Godot, or custom — suitable for Plume3D). See vaultis.wyldmagic.gg for more. |
Getting started
Section titled “Getting started”New to Plume3D? The Getting Started guide walks you through:
- Get the binaries — Email hello@wyldmagic.gg or see Get Plume3D; extract and run from your app folder.
- Get the Blender addon — Email hello@wyldmagic.gg to request the addon zip; install in Blender to attach Wren scripts and config to objects and collections.
- Write an initial app — Create a folder with
game.tomlandmain.wren, implement theGamelifecycle (init,update,draw), and run it withplume3d ..
Next steps
Section titled “Next steps”- API Reference — Full Wren API: Engine, Logger, Mesh, Input, Window, Graphics, Gui, Audio, Config, Resource, Scene, Node, Camera, Light, Raycast, Physics, CharacterController, InstancedMesh, ParticleEmitter, Animation, Math, Texture, Sprite, Text, Net, PlayFlow, Http, Random.
- Examples — Run and learn from the example apps (aspect ratio, audio, GUI, mesh, blend loading, 3D radio, etc.).
- Licensing — License terms for the Plume3D engine, this documentation site, and third-party components.