These libraries are experimental. APIs may change without notice. Generated from source with koruc 0.1.7 on 8/19/2026.
raylib
@korulang/raylib@0.0.1raylib for Koru — leak-proof windows, frame-loop-as-effect-branch, and drawing with phantom obligations
raylib/index.kz · 15 tors
@korulang/raylib — raylib for Koru (v0: window + frame loop + basic drawing) · 23 more lines
@korulang/raylib — raylib for Koru (v0: window + frame loop + basic drawing)
Lifts raylib's window/drawing core behind phantom obligations. The C footguns
this compiles away:
1. InitWindow() must be paired with CloseWindow() — forget it and the GL
context / window leaks. Here the window lifecycle is ASYMMETRIC
(opened -> active -> closed, see the lifecycle section below): an
active window's forgotten close is auto-healed (unique discharger) or
a KORU030 under --auto-discharge=disable — and a window that is opened
but NEVER RENDERED is a KORU030 outright, in both modes: allocate-
without-use has no discharge path at all.
2. Drawing outside a BeginDrawing()/EndDrawing() bracket is a silent no-op
or corruption. Here every draw call borrows a *Frame<frame> handle that
only exists inside the `! frame` effect-branch body — drawing outside a
frame is not expressible.
3. Forgetting EndDrawing() hangs the swap chain. Here the engine owns the
bracket: the frame body inlines between Begin and End, so the user
cannot omit it (same safe-by-construction shape as sqlite3's
`! row` iteration, which owns sqlite3_finalize).
House style follows evp/index.kz and sqlite3/index.kz: `<state!>` grants an
obligation, `<!state>` consumes it, bare `<state>` borrows.
Phantom lifecycles
Derived from the phantom labels in the declarations below — state! issues an
obligation the compiler will chase, !state discharges it, a bare state holds it without moving it. Nothing here is hand-drawn.
Window 2 states opened!active!Frame 1 state frame!Texture 1 state loaded!// Window lifecycle — the ASYMMETRIC model (canonical fixture:
// koru tests/regression/336_OBLIGATION_MATRIX/db.kz, connected -> active):
//
// opened — fresh from window.open, never rendered. NO disposer accepts
// this state, and `frames` takes a `count` param so the
// auto-discharge walk cannot route through it either: a window
// you open and never render is a KORU030 compile error, not a
// silent RAII autodrop.
// active — has rendered at least once (`frames` consumes
// <!opened|!active> and re-mints <active!>).
// closed — window.close accepts ONLY <!active>.
//
// raylib's InitWindow doesn't report failure; IsWindowReady() lifts the
// invalid-window state into an honest `| err` branch instead of letting a dead
// window limp on.
~pub tor window.open { width: i32, height: i32, title: string }
| win *Window<opened!>
| err string// Frame pacing. Without this the frame loop runs as fast as the machine will
// go — a 1200-frame animation finishes in half a second and looks like a
// single flash, which is indistinguishable from "nothing rendered" and is
// exactly how the first boids demo appeared to fail.
//
// A BORROW, not a transition: pacing does not change what the window IS, so it
// takes `<opened>` and mints nothing. That keeps it usable as an ordinary link
// in the chain between `open` and `frames`, which is the only place it makes
// sense to call.
~pub tor window.fps { win: *Window<opened>, fps: i32 }// Explicit opened -> active transition WITHOUT rendering — the error-path
// exit. After window.open, if a later setup step fails, the <opened!> window
// has no other legal route to close (close accepts only <!active>, and
// that's the point: silent drop of an unused window must not compile).
// `activate` is deliberately NON-VOID: the auto-discharge walk is depth-1
// void-terminals-only, so it can never be auto-inserted — writing it is
// always a visible, human decision. (Ruling 2026-07-17. NB for the
// transition-chaining aspiration, koru 330_071: chaining must not route
// through non-void transitions, or this guarantee dies — record kept with
// the queued koru batch.)
~pub tor window.activate { win: *Window<!opened> } -> *Window<active!>~pub tor window.close { win: *Window<!active> }// The frame loop — the heart of every raylib program, as an effect branch.
// This is the `opened -> active` transition: it consumes either state
// (<!opened|!active>, so repeated frame batches are fine, mirroring
// tx.begin/tx.exec in the 336 fixture) and re-mints <active!> through
// `| done`. Only a window that has been through here can be closed.
//
// `! frame` fires once per frame with the body inlined between BeginDrawing
// and EndDrawing — the engine owns the bracket, exactly as sqlite3's `! row`
// owns sqlite3_finalize. The loop ends after `count` frames or when the OS
// asks the window to close, whichever comes first (bounded so tests halt).
~pub tor frames { win: *Window<!opened|!active>, count: i32 }
! frame *Frame<frame!>
| done *Window<active!>// No-op consumer of the per-frame borrow. The compiler auto-inserts this to
// release `<frame!>` at each frame-body boundary; the user never calls it.
// (Mirror of sqlite3's release.row.)
~pub tor release.frame { f: *Frame<!frame> }color
index.kz:201~pub tor color { r: u8, g: u8, b: u8 } -> Color// Solid-color texture, generated on the GPU — no file dependency, the
// deterministic path tests use.
~pub tor texture.gen.color {
win: *Window<opened|active>,
width: i32,
height: i32,
col: Color
}
| tex *Texture<loaded!>
| err string// Texture from an image file (png, bmp, ...).
~pub tor texture.load { win: *Window<opened|active>, path: string }
| tex *Texture<loaded!>
| err string~pub tor texture.unload { tex: *Texture<!loaded> }// Drawing — every draw call borrows the frame handle, so it can only appear
// inside a `! frame` body.
~pub tor draw.rect { f: *Frame<frame>, x: i32, y: i32, w: i32, h: i32, col: Color }~pub tor draw.texture {
f: *Frame<frame>,
tex: *Texture<loaded>,
x: i32,
y: i32,
tint: Color
}~pub tor draw.clear { f: *Frame<frame>, col: Color }~pub tor draw.text {
f: *Frame<frame>,
text: string,
x: i32,
y: i32,
size: i32,
col: Color
}