Epok Engine v0.3.0
START WITH THE BIG PICTURE
What does this part of Epok do?
Version 0.3.0 made one gameplay surface behave identically in C++, Blueprints and Lua, added a timeline sequencer with a live scene preview, and replaced software music with a native driver for the PlayStation's own sound chip.
- 01Review compatibility
- 02Adopt the unified gameplay operations
- 03Re-cook music for the native driver
What is happening under the hood?
- Projects kept map format 6 and Blueprint format 5; no recreation or scene migration was required.
- The Lua gameplay profile advanced from 1 to 2, adding composite values, service operations and typed foreign receivers.
- Timeline assets advanced from schema 2 to 3, adding optional per-track sections with time remapping.
- Epok Pulse compiles a MIDI entirely on the host into an absolute-timestamped SPU command stream; the software synthesizer stayed selectable as a reference.
- Skeletal triangles gained per-corner texture coordinates and the per-model clip limit rose to 32.
Field note: If you author music, compare the Target Preview against the software reference before committing to the native driver.
Epok v0.3.0 completes a single gameplay API surface shared identically by C++, Blueprint and Lua, adds a native timeline sequencer with a live scene-camera preview, introduces the Epok Pulse native SPU music driver, extends skeletal characters with per-corner texturing and larger clip sets, and lands a round of PlayStation runtime, editor-preview and authoring-workflow improvements.
Compatibility
Projects created with v0.2.0 keep map format 6 and Blueprint format 5; this release requires no project recreation or scene migration. Continue to distribute the matching editor and epok-header-tool together.
Two versioned contracts advance and are handled transparently:
- Reflection schema 9 → 10. Schema 10 adds value declarations, function libraries and operation descriptors. Older manifests remain readable; newly extracted manifests write schema 10.
- Epok Lua Gameplay profile 1 → 2. Profile 2 adds composite values, service operations, typed foreign receivers and full-width handles. Existing projects with no explicit profile retain Legacy profile 1 behavior; new projects write profile 2. The VM call ABI follows the profile: ABI 2 moves values of up to 32 words through a bounded value arena, while scalar ABI 1 programs keep their direct one-word path.
Existing Blueprint builtin UUIDs remain compatibility aliases. New work resolves to catalog operation UUIDs, and loading an older graph never rewrites the asset.
The timeline asset schema advances 2 → 3. Version 3 adds optional per-track sections; v1 and v2 assets still load and migrate, and legacy flat-key tracks stay flat until explicitly converted. The runtime and cooker key limit rises from 4 to 256 keys per curve, while the cooked property-section total is capped at 16 tracks per compiled timeline for the device runtime profile.
The lighting bake fingerprint is now keyed by stable Actor UUID rather than hierarchy index, so adding or removing empty Actors no longer invalidates a bake; caches produced before this change are rebuilt on first open. Native music sequences cook to the EPSQ v3 command stream alongside the existing EPSB v2 sample bank; a project whose every sequence is native-only compiles with the software synthesizer paths omitted.
Gameplay API parity across C++, Blueprint and Lua
The engine now exposes one normative gameplay facade whose C++ annotations are the single signature authority; Blueprint nodes, Lua definitions, native adapters and VM bindings are all derived from those declarations rather than maintained by hand per language.
- Publish a frozen catalog of 3,048 classified capability rows. Of these, 431 are public gameplay operations, and every public operation is implemented with executable support, tests, examples and cost evidence on all five surfaces — C++, Blueprint, Lua (native C++), Lua VM (bytecode) and Lua VM (source) — with zero missing rows on any surface.
- Cover the full authoring surface across catalog categories, including objects and actors, the runtime facade, particles and effects, 2D gameplay, audio and music, collision, cameras and scenes, input and time, materials and visuals, sprites and palettes, static and editable meshes, timelines and sequences, skeletal animation, Memory Card persistence, UI text, math values, and utilities and events.
- Pass values as copied snapshots with a fixed wire layout.
Bool,Int32,UInt32and Q12Fixedoccupy one word; object identities occupy one word and are validated against their generation before dispatch; asset/class references and timeline/effect handles occupy two words;Vector2,Vector3and registered records use the sum of their members and are capped at 32 words. Retaining a collision, playback, resource or skeletal result never retains renderer or service scratch memory. - Evaluate arguments once, left to right. A foreign instance call first checks the receiver's generation and reflected class; a null, stale or wrong-class receiver returns the operation's zero/default result and performs no mutation.
- Run lifecycle events in component-before-owner order.
on_frameruns once per rendered frame, including while the simulation is paused, whereastickremains simulation-step based. Trigger callbacks carry a generationalObjectId, and a destroyed callback owner is quarantined until nested dispatch returns. - Persist through asynchronous Memory Card methods that report whether a request was accepted and expose completion and error through
MemoryCardSnapshot. Payloads are bounded to 4096 bytes, and the staged-word API addresses all 1024 words without heap allocation. - Transition scenes with bounded fade durations and an RGB loading color, and stream static geometry through an eight-page request queue whose requests never wait for media I/O and expose unavailable, pending, ready and failed states explicitly.
- Sample skeletal characters by portable imported vertex order in model or world space, with world conversion using the same authoritative Actor transform as rendering. Rigid single-vertex sampling evaluates only the selected bone's ancestor chain; baked compressed frames decode at most one 16-vertex block via an optional seek table; baked bone queries use an optional hierarchy/track sidecar instead of reconstructing bones from vertices. The cooker emits remaps, seek tables and baked-bone sidecars only when the operation manifest actually requires them.
- Track resource reachability at cook time. Blueprint and Lua compilers add each resolved operation's capability requirements to
Scripts.epokmanifest; native translation units may declare opaque requirements withEPOK_NATIVE_REQUIREMENTS(...), hashed as a build input. A missing requirement is a cook error rather than a silent target-time fallback.
The facade deliberately does not invent inverse kinematics, ragdolls, multi-weight skinning, networking, triangle-mesh physics or general dynamic Lua; collision reflects the engine's existing bounded box and segment facilities.
Native sequencer
A new immediate-mode timeline sequencer edits TimelineAsset documents directly in the editor, with a dedicated Scene/Sequencer layout available from the Layout menu (and the --sequencer-layout command-line flag) that docks the Scene view above the sequencer and restores the default layout with Escape.
- Drive the playhead in ticks on a 4096 Hz clock, displayed as frames at a selectable rate (24, 25, 30, 50 or 60 fps), with a frame ruler, a searchable track tree, snapping to display frames, box selection, multi-key dragging, and copy/paste that remaps key identities.
- Author two track kinds: property tracks bound to a reflected, timeline-animatable class property, and event tracks bound to a timeline-flagged function. Vector properties expand into per-lane (X/Y/Z/W) sub-tracks with independent values.
- Compose each property track from non-overlapping sections — half-open
[start, end)ranges with a source offset and a positive rational playback rate (numerator and denominator from 1 to 1024) for time remapping and retiming. Each section splits its property into per-lane channels, each carrying its own interpolation (Linear, Step, Smoothstep, Ease-In or Ease-Out) and keys. Converting a legacy flat-key track to a single section is an explicit, identity-preserving transaction, so existing timelines are never rewritten implicitly. - Blend tracks as Absolute or Additive, loop them Once or Repeat, and edit them through a dedicated Curve Editor window (per-track interpolation, lane polylines and a key inspector) and a Sections and Channels window (section ranges, source offset, speed and per-channel keys with inline validation).
- Transport with start/previous/play-pause (Space)/next/end controls plus reverse playback and looping, undo/redo of value edits, an in-editor Validate action that cooks a preview, and a Fit action.
Scene-camera preview
- Enable the preview eye to evaluate the compiled timeline onto a disposable clone of the scene while scrubbing or playing, driving Actor position, rotation and scale through
TimelineTransformand camera field of view (clamped 25–120, camera Actors only) throughTimelineCamera, using the same Q12 curves, section timing and additive blending as cooked playback. - Resolve bindings safely: explicit scene
TimelineComponentbindings win, and otherwise the preview auto-binds only a single unambiguous compatible Actor or component, never guessing an ambiguous or missing target. A status row reports how many tracks were applied, which bindings are missing, and which fields still require the game runtime (events and non-spatial adapters). - Substitute the preview scene for rendering and picking only while the sequence is open and not playing; saving and building always keep the authored values.
Epok Pulse native music
Epok Pulse is a new native SPU music driver that compiles a resident MusicSequence entirely on the host into a bounded, absolute-timestamped command stream, so the console interrupt only dispatches register commands to the 24 hardware voices while the SPU performs ADPCM playback and hardware ADSR. The previous runtime SoundFont synthesizer remains selectable as a software reference for fidelity comparison.
- Choose the driver per recipe in the music conversion UI: Epok Pulse (recommended) or the SoundFont software reference. Epok Pulse becomes the default, and existing driverless recipes adopt it when re-cooked.
- Compile MIDI ordering, sustain pedals, note lifetimes, instrument regions, layer admission, tuning, tempo and pitch/gain modulation offline into the EPSQ v3 stream: absolute-microsecond command timestamps, deduplicated tone templates, and no per-interrupt envelope, LFO or note-off matching on the target.
- Sample pitch and gain automation at 250 Hz plus MIDI event boundaries, emitting only changed registers. Hardware ADSR is fit conservatively and the cook and Target Preview report which note layers were adapted and how many offline voice steals occurred.
- Stay within explicit bounds: bank budgets up to 4 MiB, up to 65,536 input events, and an EPSQ payload from 40 bytes to 256 KiB with at most 32,768 commands over a ten-minute authored pass. Failures report actionable, specific errors — for example a controller that retimes an already-playing volume envelope forces the software reference rather than mis-cooking.
- Audition the cooked result in the model window with a Target Preview that decodes cooked ADPCM through a hardware-ADSR model; the existing Source Preview is unchanged.
- Fit sample budgets automatically on import: importing or reimporting a MIDI with a SoundFont now persists the highest sample rate that fits the PlayStation sample budget, replacing the previous manual optimizer step.
- Preserve advisory sequencer metadata: the MIDI importer keeps opaque
0x7Fmeta events as authoring metadata instead of blocking preview or conversion, and it reports the Standard MIDI File format number in the import form with per-song playback blockers. - Export a sequence's source interpretation to a standard signed-16-bit PCM WAV (
--render-music-sequence) as the bridge to a separately streamed disc-audio clip, and duplicate a sequence into a compact resident variant with--duplicate-music-sequence(bank budget and minimum sample rate options). - Guard target memory: the build pipeline now always analyzes the linked image and rejects a build that leaves less than 64 KiB of main RAM for the runtime heap, stacks and interrupt allocations, pointing at the sample-bank budget and streaming options when it fails.
Textured skeletal characters and expanded clip sets
- Store three optional per-corner texture coordinates on each skeletal triangle, so an atlas seam keeps distinct coordinates on either side without duplicating the cooked position it shares. Coordinates are optional: assets imported before this change stay readable and render unchanged, while a textured material slot with no coordinates now fails with an actionable message naming the triangle and slot instead of receiving an invented mapping.
- Read coordinates from the same polygon corners and front-face permutation as the triangulator, and invert the vertical axis exactly once at import so
v = 0is the top row of the source image, matching the texture importer and the target page mapping. Out-of-range coordinates are rejected rather than silently clamped. - Cook skeletal faces through the same page lookup the static mesh path uses, so both geometry kinds pack identical page coordinates through one helper, and both storage modes describe the same surfaces with only their position ordering differing.
- Raise the per-model clip limit to 32 from a single shared constant, and widen the importer manifest bound so a reimport that renames subassets cannot exhaust it.
- Report cooked memory from explicit target-layout sizes rather than host struct sizes, assert those sizes in the target build, and emit the full budget breakdown with every cooked model; neither the host nor the 512 KiB target animation budget is relaxed.
- Preserve full material state — texture, blend mode, depth bias, scroll and unlit choice — when editing a color or assigning a texture, and show the assigned texture in the model window preview.
- Correct the baked decoded-vertex counter, which previously counted a visible model that decoded nothing, and relabel the default storage mode as the smallest rather than the fastest: controlled measurements show baked storage is the faster representation in every workload, while frame CPU time, not memory, is the limiting resource.
Editor preview, framing and input
- Share one symmetric, non-skewed projection between the software preview, the GPU scene render and picking: the vertical projection is recentered to the true panel center and the depth-dependent clip-space skew term is removed.
- Frame the selection against the actual Scene panel aspect through a new panel-aware bounding fit, aggregating every active mesh descendant of the selection and sampling posed skeletal points and lighting geometry, so framing a controller or empty root includes its animated skeletal child. Framing no longer mutates the Actor, its pose, zoom, yaw or pitch, and restoring saved Scene view preferences no longer force-disables the grid.
- Render the scene preview with hardware back-face culling for editable and skeletal meshes (sprites stay unculled), exclude inactive Actors from both drawing and outlining, and cache resolved skeletal poses so animation refreshes the view without registering a scene edit. Selection outlines are now emitted only for wireframe mode, the selected Actor or the current timeline target.
- Ray-test picking against current posed world-space surfaces for skeletal and editable meshes, honoring inherited reflection and shear and filtering out inactive Actors, so a missing model produces no hit instead of an invisible unit cube.
- Improve Hierarchy input: double-clicking a row frames the selection, renaming moves to F2 (shown in the context menu and gated to a focused Hierarchy with no active text input), and an in-progress rename commits or cancels reliably on Enter, Escape, deactivation, window blur or an outside click, including for collapsed or filtered branches.
- Add opt-in analog controller support for play mode: a per-project Analog controller (port 1) option puts pad 1 into analog mode in the emulator bridge, and the runtime input API gains a signed Q12 axis reader (left and right sticks, ±4096 with exact center and no dead zone). Physical pads and existing projects are unchanged.
PlayStation runtime performance
- Skip actor and component dispatch work that provably does nothing: a compile-time trait detects whether a component actually overrides
tickor the frame hook versus inheriting the base no-op, records the result in the cooked class table, and lets the per-frame loops bypass resolution and the virtual call for no-op components. Base-family object lookups now compare a single family field instead of walking the ancestry chain, and the per-frame quarantine sweep early-returns when no releases are pending. - Project geometry three vertices per GTE kernel call, replacing per-vertex transforms for retained, non-dynamic mesh chunks that are fully inside the frustum, with a new conservative whole-chunk frustum test and near/far overflow guards, plus a fast-accept path in the polygon emitter that skips scissor math when all three outcodes are clear.
- Compile mesh chunks more tightly: parametric edges are now subdivided independently so a long, shallow surface no longer over-tessellates, the culling cell size is enlarged, and chunk encoding recursively splits an oversized face list instead of failing, snapping chunk origins so repartitioning is bit-identical to the whole-mesh transform and introduces no GTE seams.
Blueprint and workflow fixes
- Fix the Blueprint Delay node when driven from a per-frame pump such as Tick: the compiled latent invocation no longer cancels and restarts the countdown every frame, so a Delay reached repeatedly now runs to completion with standard semantics. Timeline-only and wait-for-playback graphs are unaffected.
- Classify a music import's source immediately when the import form opens or refreshes, so the dialog and its refresh button use identical rules, surface the detected Standard MIDI File format and per-song playback blockers, and expose only the supported resident load mode for event data.
- Close the Import Content modal automatically after a successful browse, import or drag-and-drop, and focus the assets tab when reviewing pending imports.
- Add an opt-in lighting Background Pass that draws a base surface such as a ground plane before ordinary 3D geometry, and key the lighting bake to stable Actor UUIDs so adding or removing empty Actors no longer invalidates a bake.
- Clear the selection and cancel a matching in-progress rename when an Actor is deleted, and add an opt-in GPU ordered-dithering setting that applies to 3D geometry only while leaving the HUD undithered.
Validation
- The required
release-policycheck runs Rust formatting and the default serialized Rust test suite for this pull request after buildingepok-header-tool. - The gameplay API parity strict checker enforces that every public gameplay row has executable support, tests, examples and cost evidence on all five surfaces before it passes; this release reports zero missing rows on any surface.
- Skeletal and native-music coverage import their fixtures, build the affected representations and execute them in PCSX-Redux, and reproducible benchmarks capture emulator frame timing and memory for the skeletal storage modes and the music driver.
- Emulator measurements are evidence for their fixed setups and are not physical-console timing. The new gameplay API surface, the skeletal texturing paths and the Epok Pulse driver have not been validated on physical PlayStation hardware, and cross-platform distribution testing remains separate from the required Windows release check.
Full comparison: v0.2.0...v0.3.0