These libraries are experimental. APIs may change without notice. Generated from source with koruc 0.1.7 on 11/4/2026.
Composition
@korulang/composition@0.0.1Time-first composition model — objects placed in global wall-clock time, tempo as beats→seconds convenience, magnetic references as phantom obligations — plus a renderer-agnostic seconds→Rect projection surface
composition/index.kz · 19 tors
@korulang/composition — time-first object model + renderer-agnostic projection. · 24 more lines
@korulang/composition — time-first object model + renderer-agnostic projection.
Ported from Intranquil's composition/layout laws: the timeline is NOT the
base reality; global wall-clock time is. Timelines are objects placed in
time; clips live inside timelines; tempo is a beats→seconds convenience;
a magnetic reference makes a timeline's start DERIVED — the <bound>
obligation means moving it by hand is unexpressible, and only a
comp.follow leader can relocate it.
Two surfaces in one module:
MODEL comp/tempo/timeline/clip/follow/shift/resolve — pure seconds.
PROJECTION view/proj — seconds → pixels. Emits Rect; knows nothing about
Metal, vaxis, or any renderer. The draw surface is the
consumer's seam — a metal ! frame arm, a terminal cell fill,
an SVG emitter all consume the same Rect.
Known toolchain gaps this package currently exposes (do NOT route around):
- 330_136: per-arg phantom checking is skipped for part-declared tors.
Package tors are module-declared, so <bound> enforcement at the call
site is measured working for imports — keep it that way.
- User type named `Item` collides with injected ast.Item when a consumer
merges parts into one module — this package's type is Clip (which is
also the better canvas word: .graph-clip).
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.
Composition 2 states open!resolved!Timeline 2 states free!bound!// ---- model vocabulary ----
//
// The composition: everything currently on the canvas. resolve consumes
// either state and re-mints <resolved!> — idempotent, like the TS
// $effect re-fire that re-runs propagateMagneticReferences.
~pub tor comp.new {} -> *Composition<open!>~pub tor comp.resolve { comp: *Composition<!open|!resolved> } -> *Composition<resolved!>tempo.new
index.kz:142~pub tor tempo.new { bpm: f64, start: f64 } -> *Tempo// A timeline placed on the canvas. Both placement forms exist in one tor —
// beats resolve through `tempo`, plain start/duration are wall-clock.
// (TS splits this across four optional fields with "ignored when" comments;
// the dual-signature input is the same semantic, named.)
~pub tor timeline.new {
comp: *Composition<open|resolved>,
tempo: ?*Tempo,
start: f64,
start-beats: ?f64,
duration: f64,
beats: ?f64
} -> *Timeline<free!>// A clip inside a timeline. Clips are placed relative to the parent's
// effective start; they don't carry magnetic refs (TS: a magnetic source
// must be a Timeline, never an Item).
~pub tor clip.new {
tl: *Timeline<free|bound>,
start: f64,
start-beats: ?f64,
duration: f64,
beats: ?f64
} -> *Clip// Bind follower's start edge to leader's end edge + offset. Consumes the
// follower's freedom — the returned token is <bound!>, and nothing in the
// vocabulary accepts a bound timeline for direct placement.
~pub tor comp.follow {
comp: *Composition<open|resolved>,
follower: *Timeline<!free>,
leader: *Timeline<free|bound>,
offset: f64
} -> *Timeline<bound!>// Move a timeline by delta seconds, in whichever unit drives its position
// (start_beats when tempo-bound, else start — verbatim from
// propagateMagneticReferences' delta application). Bound timelines refuse:
// their position is owned by the reference, not the hand.
~pub tor timeline.shift { tl: *Timeline<free>, delta: f64 }// Detach + free a timeline. The only tor that accepts <bound> for
// consumption: releasing a bound timeline also kills its follow edge —
// otherwise the comp would resolve against a freed pointer.
~pub tor timeline.release {
comp: *Composition<open|resolved>,
tl: *Timeline<!free|!bound>
}// Free the composition container — consumes whichever lifecycle state it
// is in. Children are caller-released (their tokens carry no obligation).
~pub tor comp.free { comp: *Composition<!open|!resolved> }// ---- resolution (object forms of the layout laws) ----
~pub tor resolve.timeline-start { tl: *Timeline<free|bound> } -> f64~pub tor resolve.timeline-end { tl: *Timeline<free|bound> } -> f64~pub tor resolve.clip-start { tl: *Timeline<free|bound>, clip: *Clip } -> f64~pub tor resolve.clip-abs-start { tl: *Timeline<free|bound>, clip: *Clip } -> f64view.new
index.kz:280// ---- projection vocabulary — the reusable surface ----
//
// A view: viewport width + pixels-per-second + world origin. Pan shifts the
// origin; zoom scales pps. A view is viewstate, not canvas state — a plain
// token with no lifecycle law, so it carries no phantom.
~pub tor view.new { width: f64, pps: f64 } -> *Viewview.pan
index.kz:287~pub tor view.pan { view: *View, dx-seconds: f64 }view.zoom
index.kz:292~pub tor view.zoom { view: *View, factor: f64 }proj.x
index.kz:298// world→screen, one axis.
~pub tor proj.x { view: *View, seconds: f64 } -> f64// Timeline → Rect at a given row. The consumer picks y/h; the model picked
// start/duration.
~pub tor proj.timeline-rect {
view: *View,
tl: *Timeline<free|bound>,
y: f64,
h: f64
} -> Rect// Clip → Rect, absolute time (parent start folded in).
~pub tor proj.clip-rect {
view: *View,
tl: *Timeline<free|bound>,
clip: *Clip,
y: f64,
h: f64
} -> Rect