What if your scripting language understood the game loop?
Every game engine thinks in frames. Your scripts shouldn't have to pretend otherwise. Chasm is built around one idea: memory lifetime is part of the game loop, so it should be part of the language.
- Memory lifetime is part of the language, not an afterthought
- No garbage collector, no hidden frame spikes
- Designed around the game loop from the ground up
- Zero hidden allocations, zero surprises
@player_x :: script = 160.0
@player_y :: script = 120.0
@high_score :: persistent = 0
def on_tick(dt :: float) do
speed :: frame = 120.0
dx :: frame = 0.0
dy :: frame = 0.0
if key_down(:right) do dx = speed end
if key_down(:left) do dx = 0.0 - speed end
if key_down(:down) do dy = speed end
if key_down(:up) do dy = 0.0 - speed end
@player_x = copy_to_script(@player_x + dx * dt)
@player_y = copy_to_script(@player_y + dy * dt)
end
Every other scripting language was built for the wrong problem.
Lua was built for configuration. Python was built for general computing. GDScript was built to be approachable. They were all adapted for games but none of them were designed for the one constraint that makes games hard.
These languages hide memory from you. They decide when to allocate, when to collect, when to pause. You find out at runtime, usually during a boss fight.
Chasm doesn't hide anything.
Your code runs thousands of times per second, and every hidden allocation is a frame you're dropping.
The game loop has always had a memory model. Chasm just makes it official.
Every game dev already thinks this way, they just don't have a language that agrees with them.
- “This value only matters for this frame.
- “This value needs to survive until the script reloads.
- “This value lives for the entire session.
That's not a new idea. That's how game programmers already reason about state. Chasm takes that mental model and puts it directly into the syntax.
The compiler knows the difference. It enforces the boundaries. It tells you when you're accidentally paying a cost you didn't mean to pay.
@score :: script = 0 # reset on hot-reload
@best :: persistent = 0 # survives the session
@temp :: frame = 0.0 # gone after this tickThe game loop has a heartbeat. Chasm calls it the tick.
Every frame, your engine calls on_tick. That's one tick, the atomic unit of time in Chasm. At 60fps it happens 60 times a second. Everything in the language is designed around this boundary.
The dt parameter is how many seconds passed since the last tick, usually 0.016 at 60fps. You multiply movement and physics by it so the game runs at the same speed on every machine.
In Lua, dx and dyare objects waiting to be collected. In Chasm they live in the frame arena, a bump-allocated region that resets the moment the tick ends. The compiler knows they're :: frame lifetime so it never allocates them anywhere else.
def on_tick(dt :: float) do
# dx and dy are frame-lifetime, born here, gone at tick end
dx :: frame = @target_x - @player_x
dy :: frame = @target_y - @player_y
dist :: frame = sqrt(dx * dx + dy * dy)
if dist > 1.0 do
spd :: frame = 200.0 * dt / dist
@player_x = copy_to_script(@player_x + dx * spd)
@player_y = copy_to_script(@player_y + dy * spd)
end
end
# tick ends, dx, dy, dist, spd are gone.
# No GC. They never left the frame arena.Allocation is always visible.
The compiler won't let you accidentally promote a frame value into a longer lifetime. It's not a lint warning. It's not a convention. It's a compile error.
One function call. Explicit. Visible. You chose to pay that cost, and you can see exactly where in the code. There is no “I'll optimize it later”, because the language makes the cost obvious now.
player.velocity = get_frame_delta()
-- silently allocates a new table every frame@velocity = get_frame_delta()
# compile error: frame value cannot enter script lifetime@velocity = copy_to_script(get_frame_delta())
# explicit. one call. you chose this cost.Runs on your engine
Chasm embeds into any C-compatible engine. First-class support for Raylib and Godot out of the box.
Raylib
Godot 4Built around the frame boundary.
Not adapted to it.
Chasm is open source. It embeds like Lua, runs like C, and enforces memory lifetimes the compiler can prove at compile time, no GC, no pauses, no surprises.