C++20 · Vulkan · EnTT · Luau
A game engine built to be
read, not guessed at.
Kronos is a Vulkan-renderer engine with a real Forward+ pipeline, a bare EnTT-registry ECS, deterministic Jolt physics, and a sandboxed Luau layer that scripts against the same live state the editor does — no partial Instance-API illusion standing in for either.
Architecture
Four subsystems, one frame graph.
Nothing here is a stub behind a marketing name — each panel below is a real, load-bearing part of the runtime, not an aspirational roadmap item.
Renderer
Forward+, one real frame graph
Opaque pass → cascaded shadow maps → SSAO/DOF/motion-blur → bloom → ACES or AgX tonemap → an optional sampled 3D LUT. Studio's own viewport and an offline capture rig render through the identical path, so what a creator previews is what gets exported.
ECS
A thin wrapper, not a framework
Entities are opaque numbers over a real entt::registry. No inheritance hierarchy, no hidden allocation graph — component storage is exactly what EnTT gives you, queried with view<T...>().
Physics
Jolt, stepped on purpose
A fixed-tick simulation that steps automatically during play, or can be paused and single-stepped by exact, chosen deltas — the same mechanism a tooling script uses to reproduce a physics bug frame by frame.
Scripting
Luau, with a stated boundary
Flat global tables (world, mesh, render, physics) expose numbers, booleans, and strings only. No script ever holds a Vulkan handle, a pipeline, or a raw pointer — that boundary is enforced by what the bindings return, not by a runtime permission check.
Scripting surface
The console runs against the live scene.
Every snippet below is real, callable Luau — the same tables Studio's own Debug Console registers against the running ECS, mesh editor, sequencer, renderer, and physics preview.
1-- entities are plain numbers -- no Instance metatable to guess at 2local id = world.createEntity("Turret") 3world.setPosition(id, 12.5, 0.0, -4.0) 4world.setMaterial(id, "Metal") 5world.applyImpulse(id, 0, 8, 0) 6 7local hit = world.raycast(0, 5, 0, 0, -1, 0, 50) 8if hit then print(hit.entityId, hit.distance) end
1-- real vertex/edge/face topology, live in the viewport 2mesh.beginEditingBox(id, 0.5, 0.5, 0.5) 3mesh.extrudeFace(id, 3, 0.75) 4mesh.insetFace(id, 3, 0.2) 5mesh.setVertexPosition(id, 0, 9, 8, 7) 6-- editVersion bumps -- Modeling Mode re-uploads to the GPU next frame
1-- the same Sequence a creator's own timeline UI edits 2cinematic.addTrack("Camera Rail", "Camera") 3cinematic.addKeyframe("Camera Rail", "railT", 0.0, 0.0, "Bezier") 4cinematic.addKeyframe("Camera Rail", "railT", 4.0, 1.0, "Bezier") 5cinematic.setPlayhead(2.0)
1-- numeric/enum tuning only -- never a raw Vulkan handle 2render.setCinematicMode(true) 3render.setTonemapOperator("agx") 4render.setBloomSettings(1.1, 0.4, 0.7) 5render.loadColorGradingLut("looks/teal_orange.cube")
1-- deterministic, script-triggered steps -- for reproducing a bug exactly 2physics.pause() 3local ok = physics.step(1 / 60) 4assert(ok, "step only runs while playing + paused") 5physics.resume()
Runtime
What the profiler sees.
A representative capture of the runtime's own frame-time graph and scene counters — illustrative, not a live socket into a running instance.
Numbers animate client-side for illustration; they are not streamed from a live Kronos process.