Epok Engine v0.4.0
START WITH THE BIG PICTURE
What does this part of Epok do?
Version 0.4.0 adds terrain you can sculpt, navigation your characters can follow, and a Native PC runtime that runs the same gameplay code on your computer before it ever reaches a console.
- 01Review compatibility and the editor version
- 02Try the new template browser and gameplay flavors
- 03Sculpt terrain, bake navigation, iterate on Native PC
What is happening under the hood?
- Every authoring contract from v0.3.0 is unchanged: no project recreation, scene migration or asset rewrite.
- A project descriptor must declare editor_version 0.4.0; upgrade through the Hub, which backs up the descriptor first.
- New projects choose a template, a gameplay flavor (C++, Blueprint or Lua) and a target platform as independent choices.
- Appending a variant to a reflected enum or a member to a record no longer breaks saved Blueprint graphs.
- The gameplay API reaches 485 public operations, each implemented on C++, Blueprint and all three Lua execution modes.
- Terrain, navigation and the Native PC runtime have not been validated on physical PlayStation hardware.
Field note: Start with the compatibility section. The one thing you must do to an existing project is let the Hub move its editor version forward.
Epok v0.4.0 adds a heightmap terrain editor, baked NavLite navigation with agents, links and moving obstacles, and a Native PC play runtime that runs the same generated C++ gameplay on the host. Project creation becomes a template browser with a gameplay flavor, the Third Person template ships an animated player with an orbit camera and a remappable control surface, the Inspector is rebuilt as a dense property panel, and the PlayStation runtime gains a round of hot-path work. The gameplay API surface grows to 485 public operations, all of them reachable from C++, Blueprint and every Lua execution mode.
Compatibility
Every authoring contract that v0.3.0 defined is unchanged: project descriptor format 1, map format 6, Blueprint format 5, Epok Lua Gameplay profile 2 and timeline schema 3. This release requires no project recreation, scene migration or asset rewrite.
The editor version advances, so a project descriptor must read editor_version: 0.4.0. A project authored with v0.3.0 moves forward through the Hub's confirmed upgrade, which backs the descriptor up into .epok/migrations before publishing a new one; headless open paths reject a mismatched revision rather than migrating silently. The shipped examples/sample-game and both examples/gameplay-api-parity projects are updated.
Two example projects are removed rather than carried forward. examples/rpg-2-5d-demo and examples/timeline-spell still declared editor_version: 0.1.0 over startup maps in the retired Entity/Behaviour format, so no editor since v0.1.0 could open them — and the portable archive shipped both of them anyway, because it copies examples/ whole. Their acceptance harnesses copied the projects verbatim, so those were equally unreachable and are removed with them, along with the tutorial page that walked through the spell project. The coverage they described — camera VRAM footprints, effect load capacity, the authored combat bridge, staged spell capture and relocation rebuilds — needs new fixtures on current-format projects; knowledge/maintainers/testing.md records what each one asserted.
Appending to a reflected enum or record no longer breaks a saved Blueprint graph. Assignability now accepts a same-named enum whose stored numbering survives and records that agree on their shared prefix, which is where both paths already met; renumbering, removal and reordering still fail. Split pins address members by name, so no asset migration is needed and saved graphs keep their bytes.
Terrain is new content rather than a new contract. A project that creates no terrain is unaffected, and the first terrain in a project also brings in the engine's ground atlas, plus a sun when the scene has no light.
Two generator requirements changed for contributors. The API reference is now extracted with the pinned target toolchain's own freestanding headers and fails loudly when that toolchain is absent, instead of publishing a headerless parse. Regenerating the parity coverage needs the libclang Python bindings, which host setup does not install; pip install libclang==18.1.1 into a virtual environment supplies both the bindings and a matching library.
Heightmap terrain
Terrain is a sculpt-and-paint authoring layer over the existing geometry pipeline, not a second renderer. The authored payload is a compact binary grid; the draw geometry bakes into the editable-mesh chunk format, so terrain inherits chunk culling, retained packets, precomputed visibility, geometry streaming and the lighting bake without a new runtime path.
- Create a terrain from GameObject > Terrain or Project > Add > Terrain. Heights live on cell corners and materials on cells, from 1 to 256 cells per axis (default 32 x 32) at 0.25 to 15.0 world units per cell (default 4.0), with an atlas divided up to 8 x 8 and 64 tiles.
- Store heights in Q8 — one 256th of a unit — which spans exactly the ±128 local units a chunk vertex can encode. A 32 x 32 grid is about six kilobytes, so a brush stroke rewrites the grid rather than a mesh document and undo can afford a whole-grid snapshot per stroke.
- Draw one quad per cell, with warped corners allowed, because the renderer splits every quad into two triangles anyway: two triangle faces would cost twice the cooked bytes for the same image. Flat cells merge into larger blocks up to a configurable power-of-two limit.
- Resolve borders from neighbours with autotiling. The painted byte is a material row and the baker picks the column and rotation from the cell's four edge neighbours, so a path painted one cell at a time resolves its own borders instead of leaving a staircase. Transition tiles are painted art, not a runtime blend: the console has no multitexturing, so a material can only change at a cell boundary and the gradient has to live inside the tile. Merging compares resolved tiles rather than painted materials, so two cells of one material that resolved to different borders never merge.
- Collide through a heightfield collider that generalises the existing single-axis ramp to a two-axis grid. It samples the grid bilinearly in Q12 and stores two bytes per grid corner. Like the ramp it is a surface to stand on and never an obstacle, so a slope is walked up rather than pushed against.
- Paint with the blockout sculpt brush, which gains a vertex displacement pass that triangulates the quads a stroke bends.
- Keep cooking limits apart from payload validation: a grid that is merely too heavy for the console still loads, so the build names the actor that uses it instead of reporting a missing asset file.
Measured on the sample project, a 32 x 32 terrain at 4 units raises static RAM from 899 KiB to 1.24 MiB of the console's 2 MiB, roughly 370 bytes per quad once cooked quads, baked corner colours and retained packets are counted. The default atlas is sixteen 64-pixel tiles in one page — grass, dirt, stone and water, four variants each — derived from a CC0 pack by Jonas Voland; see THIRD_PARTY_NOTICES.md. See Terrain.
NavLite navigation
NavLite bakes a bounded walking graph from static geometry and drives agents over it inside a shared PlayStation search budget.
- Contribute walkable surfaces from
Collider3DComponentbox tops andslope_rise/slope_axisramps, or from an EditableMesh actor carryingNavigationSurfaceComponent, whose transformed triangles supply floors, slope tests and bake obstruction tests. A navigation surface supplies footing to agents; it does not add general triangle physics to controllers, raycasts or projectiles. - Delimit the bake with Build > Add Navigation Bake Volume and one profile shared by every volume in the map: sample
spacing0.5, footprintradius0.18,heightclearance 0.5,step_height0.4,max_slope45 degrees andcollision_mask1. Surfaces may be slopes, separate stair treads or neighbouring modular pieces; unsupported gaps, steep slopes, tall risers, walls and low ceilings reject a connection. - Preview a selected volume as a translucent walkable area with point markers and connections. The editor generates and caches the preview automatically, even without a saved bake, clips it to the selected volume and reports its point count — or the reason generation failed — in the Scene toolbar. The preview never edits the scene or its materials.
- Move an unparented Actor3D with
NavigationAgentComponent:move_to,stop,arrived,failedandstatusare reflected,move_on_startdrives a target from authoring, and optionalmoving_clip/idle_clipvalues select animation clips. Agents sample baked surfaces while moving and perform collision-checked lift/forward/lower motion over steps. - Author jumps and climbs with
NavigationLinkComponent— a parabolic arc with anarc_height, or a vertical-then-horizontal climb — with aduration, optionalbidirectionalgeneration and a bake-time clearance check over the entire trajectory. These are authored trajectories; the engine never infers jump capability from a character mesh. - Exclude a moving door or crate from the static bake with
NavigationObstacleComponent. - Rebake automatically when inputs change. Mesh geometry, collider ramp parameters, transforms, profile settings and authored links all participate in the fingerprint, and Build/Play refresh stale data.
Version 1 supports flat surfaces and obstacles. See NavLite.
Native PC play
Play now chooses a runtime before it chooses a runner. Native PC and PlayStation execute the same generated C++ gameplay on the same fixed 60 Hz clock, so a controller can be iterated on the host and then run unchanged on the console. The PlayStation runner selection is retained while Native PC is active.
- Run the generated gameplay out of process. Only bounded actor and HUD snapshots cross back into the editor renderer, so a crash in game code cannot take the editor with it.
- Address four logical controllers. Pad 1 to Pad 4 map to a multitap's Pad1a to Pad1d sockets on native hardware and to the matching virtual pads in Native PC Play, and analog stick (0x53) and analog pad (0x73) packets are supported on every active pad rather than only two ports.
- Rebuild faster. PlayStation rebuilds reuse a finer-grained view of their inputs, and play data is validated before it is handed to a runner.
Project creation and gameplay flavors
New Project was three text cards and a template value. It is now a template browser with a thumbnail, title and summary per card, a large preview and a description beside it, and a Project Defaults block holding the two decisions a new project actually makes.
- Treat template, gameplay flavor and target platform as three independent axes of one set of creation options rather than a template enum multiplied by a language. The flavor chooses which starter implementation is written and nothing else, so a project created in Lua can gain C++ classes and Blueprints afterwards; nothing in the descriptor records the choice, because nothing acts on it after creation.
- Write the starter three ways. Third Person ships as project C++, as an authored graph of ten function bodies and as a Lua class over one set of assets, one hierarchy and one set of transforms and colliders. None of them calls another's implementation, and the generated README names only the source that was actually written. Sample gets the same treatment through its Spinner.
- Create transactionally: a template that fails part way removes the folder it claimed instead of leaving a half-written project to be opened.
- Configure tools without losing sight of the project. Dependencies is now the third pane of the Hub content area beside Projects and New project, drawn from the same page the editor draws inside Preferences, and carries a platform tab bar whose one enabled tab says these tools build for the PlayStation. The startup notice stays a window, because it interrupts, and its button switches to the pane.
Third Person template and controls
The Third Person template is now a lightweight playable example rather than a level placeholder. It contains no combat, lock-on, NPC systems or audio.
- Play an animated character with camera-relative movement on the left stick or WASD, camera orbit and pitch on the right stick or the mouse, and a jump on Cross or K. Analog movement transitions between walk and run, and the character carries idle, jump, fall and landing states; keyboard movement is digital and therefore reaches run speed.
- Explore an optimized low-poly arena with an obstacle-aware orbit camera, static props and a directional sun, in a hierarchy grouped for immediate editing (Environment with Geometry, Collision, Props and Lighting; Gameplay with Camera and Player).
- Remap input through a dedicated controls surface built on original Epok controller artwork — a hand-authored 1000 x 650 vector illustration distributed under the repository's MIT license, whose button regions scale with the image.
See Third Person template and Input, time and collision.
Inspector
The property editor gets its own layout language while keeping the editor's palette: full-width section bands, two-column rows whose label is clipped instead of wrapped, a compact grid and a proportional face at panel size. Only density and typography are pushed, so the panel reads as one more Epok window and every tone still comes from the editor theme.
- Open each section's commands from a menu on its band, through the affordance at the band's right end or by right-clicking it, and reset Transform from its own command instead of a menu that answered anywhere in the window.
- Fold the mesh selector into the renderer sections. One component owns both the geometry and the way it is drawn, so a separate selector section read as a component that could not be removed. Each renderer section re-checks what the actor holds after the selector runs.
Gameplay API surface
The frozen candidate denominator is now 3,261 classified rows, of which 485 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.
- Expose distance fog through the gameplay facade. It was reachable only from hand-written C++ through a raw global, so the classifier saw renderer machinery and placed the whole surface outside the gameplay denominator, while the environment-effects document told scripts to set it.
FogSettingsplus the scene accessor pair close that, and the setter rejects an out-of-range range whole so a scripted scene cannot diverge from an authored one. Members arestart_distance/end_distancebecauseendis a script keyword and would be unreachable from the VM modes. - Expose the post-HUD screen fade the same way — the last capability in that family without a facade. The getter reports the authored amount rather than the drawn one, since the renderer takes the larger of this and the transition opacity and the transition snapshot already reports its side. The setter clamps instead of rejecting, because every input has an unambiguous nearest valid amount, and the store keeps surviving scene activation, which is what lets a game hide the frame where a bank comes up.
- Deepen the tween surface while keeping its pure-value shape.
Easegrows from four curves to seventeen — polynomial and circular, In/Out/InOut — plus scheduling delay, restart and ping-pong loops with a cycle count, and a vector form whose timing member is a real scalar tween, so component-wise agreement with three scalar tweens is structural. Easing kernels are exact at both endpoints, monotonic and within four Q12 units of the real curve across every alpha; the three original curves keep their integer expressions. - Publish the arithmetic a controller needs. Trigonometry, roots, angle approach and the analog dead zone lived in the template's own C++ header, where no other language could reach them. They are now reflected Math operations in the shared catalog, with the two animation calls a manually driven locomotion cycle needs, so the three template flavors agree by construction rather than by transcription. Lua also gains the six spatial builtins Blueprint already had.
- Carry vector properties through every Lua execution mode. Reading a component of a vector property emitted a member access on what is actually a
Fixed[3]in the native backend, and the VM boundary asked for a whole array it cannot move while the generated setter deduced an initializer list from a braced wire value. Both were backend faults rather than profile limits, and all three modes now produce a console build of the Third Person Lua controller.
See Gameplay API.
Generated reference and recorded evidence
- Parse the runtime with the target toolchain's own freestanding headers. The reference had been extracted from a translation unit with no standard headers at all; one fatal diagnostic suppressed everything after it, so a single recorded line stood for seven hundred and ten errors, three hundred and twenty-nine of them an unresolved
Fixed. The parser then gave every unresolved type an implicitint, so the published reference described much of the runtime as taking and returning plain integers. Two hundred and forty-nine of the public rows carried the wrong declared types, eighty-nine of them an integer where the operation takes fixed point. The umbrella header also pulled in a configuration-switched alternative body, which turned the sequence service's own declarations into redefinitions and spelled plainly named declarations as unnamed types at a file offset; that service goes from one callable to thirty-one, and no module reports a diagnostic. - Resolve reflected value members by brace depth. A backward walk treated any line containing
};as the end of the previous declaration, so a member whose own initialiser closes a brace cut the search short and later members of the same annotated record were classified as internal implementation while their siblings stayed public. Across the runtime headers this corrects eight member lines in four records. - Keep generated text independent of the checkout. The extractor spelled unnamed declarations and diagnostics with absolute paths, which put a developer's home directory in the repository and made
--checkpass only in that one directory. Reference output and the three validation harnesses' evidence now rewrite paths to repository-relative form at the single point where each file is written. - Fail the suite when a tracked file names another product. The rule that Epok describes its own behaviour on its own terms is now an invariant checked over every tracked file: terms come from a list beside the tools, paths are enumerated through git so ignored output and submodule contents stay out, matching is whole-word so ordinary prose is untouched, and a negative control proves the sweep would notice. Only the two files that cannot avoid spelling a term are exempt, by name.
PlayStation runtime hot paths
The runtime keeps authoritative gameplay at its original fixed-step cadence. These paths remove repeated work; they do not skip collision checks, reduce the number of actors, simplify meshes or slow down animation.
- Narrow collision synchronization to enabled colliders and their complete parent chains rather than all visual, camera, audio and reserved slots, with a root fast path for unparented colliders. Translation-only edits reuse the existing TRS basis, local and inverse transforms resolve sine and cosine once per nonzero axis, and inverse unit scale bypasses software division; host tests compare forty thousand rotated matrices against the previous column-by-column arithmetic. Trigger synchronization is skipped only when there are neither active triggers nor previous overlap pairs awaiting exit events.
- Batch independent segment queries with
raycast_batchagainst one world snapshot, with the same nearest-hit, generation, layer, trigger and started-inside rules as individual queries, caller-owned storage and no allocation. - Project interior retained geometry without fog or animated UVs through a compact eight-byte vertex, which fits 128 vertices in the 1 KiB scratchpad against 51 full clipping vertices. Packet order, per-triangle depth, topology and UVs are unchanged, and projection overflow, frustum boundaries, fog and scrolling keep their checked full-coordinate paths.
- Divide with native 32-bit arithmetic where the numerator fits, in both the clipper and sprite projection, preserving each path's original rounding; sprites wholly inside the frustum bypass six no-op clipping passes.
- Compile the runtime and gameplay scripts at
-O2by default while the SDK stays size-optimized, withEPOK_RUNTIME_OPTandEPOK_SCRIPT_OPTfor build comparisons. The executable RAM-budget gate still applies.
Validation
- The required
release-policycheck validates the release version against the release branch and its tags, then buildsepok-header-tooland runs the serialized Rust test suite on Windows. - 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.
- The forbidden-terms sweep runs as an ordinary test over every tracked file, so the product-naming rule no longer depends on review. A second repository check joins it: every shipped example must parse, resolve its startup scene and declare this editor's version, which is the gate that the two removed examples would have failed since v0.1.0.
- The Rust suite now passes on macOS as well as Windows. Two checks were host-specific rather than portable: one wrote a stand-in encoder under a Windows-only path and name, and the byte-level audio oracle regenerated its source tone from
f32::sin, which is not bit-identical across libm implementations — the captured tone differed by one sample in 2,205. That tone is now a committed fixture, so every expected hash in the oracle is unchanged and it can no longer disagree with itself from one host to another. - Emulator measurements are evidence for their fixed setups and are not physical-console timing. Terrain, NavLite, the Native PC runtime and the runtime hot-path work have not been validated on physical PlayStation hardware, and cross-platform distribution testing remains separate from the required Windows release check.
Full comparison: v0.3.0...v0.4.0