chore(release): v0.2.0
All checks were successful
bootstrap / cfree-fixpoint (push) Successful in 16s
ci / build-and-test (push) Successful in 1m28s
commit-lint / conventional-commits (push) Successful in 2s

This commit is contained in:
Orkun ÇAKILKAYA 2026-09-01 03:38:09 +03:00
parent 50ecb8472f
commit 237e13c95e
25 changed files with 53 additions and 198 deletions

View file

@ -3,6 +3,58 @@
All notable changes to the Ludic toolchain, newest first. Generated from the
changesets under changes/ by x release; do not edit released sections by hand.
## v0.2.0 — 2026-09-01
The types-and-systems release. Ludic grows a real type system — sum types,
`option`/`result`, exact and arbitrarily-big numbers, string-keyed containers and
packed 2D value types — alongside an engine that auto-runs animation, motion and
lighting over components a game merely declares, a full input stack from
rebindable action maps to native gamepads, out-of-band Audio / HTTP / Jobs
standard libraries, and a built-in test framework with line coverage. Every
addition is gated and additive: a program that never touches a feature compiles
byte-for-byte identically, and the C-free bootstrap fixpoint is untouched.
### Language & types
- **feat**: Tagged-union enums (#56) — an `enum` variant may now carry a payload (`enum Tile { Empty, Wall, Door(int), Portal(int, int) }`), making it a sum type. Variants construct by name and `match` destructures them, binding each payload, with exhaustiveness and arity checked so adding a variant surfaces every site to update. Plain enums keep their zero-cost ordinal representation, byte-for-byte unchanged.
- **feat**: `result` + `try`/`else` (#46) — a fallible function returns `ok(payload)` or `err(message)`, and `try EXPR else { … }` recovers a value with the failure message bound to `error`. A plain branch on the tag: no exceptions, no stack unwinding. `is_ok`/`is_err` classify without unwrapping.
- **feat**: `option` (#53) — `some(v)` / `none()`, a maybe-a-value with no magic `-1` sentinel, read with `is_some`/`is_none`/`unwrap_or`.
- **feat**: `panic(msg)` + `assert(cond, msg)` (#8) — a clear, located `file:line: panic:` / `assertion failed:` message and a clean exit 1 instead of a raw segfault; the source location is baked in at compile time.
- **feat**: `IVec2` + `Rect` 2D value types (#1) — by-value spatial types that lower to packed integers, so they copy like scalars and never allocate: an integer 2D vector for grid coordinates and a Q16.16 axis-aligned rectangle for HUD boxes and hitboxes, both exact and platform-identical.
- **feat**: `BigInt` + `Decimal` exact numbers (#52) — arbitrary-precision integers and exact base-10 fixed-point for game economies, so an idle counter never overflows and `0.10 + 0.20` is exactly `0.30`. No `f32`/`f64`; deterministic.
- **feat**: `Huge` + `Angle` + `Percent` (#55) — a display-scale idle/incremental big number (`1.23e45`), an auto-wrapping radian angle over deterministic `Math.*` trig, and a `[0,1]`-clamped fraction for health, volume and interpolation `t`.
- **feat**: `Dict` + `Set` containers (#54) — string-keyed lookups over one open-addressing hash table (FNV-1a, linear probing, tombstones), for resource counts, registries, tags and visited tiles; O(1) average instead of a linear scan.
- **feat**: Value tree + reflection + JSON (#44) — a self-describing `Value` node, `Reflect.serialize`/`apply` to walk an entity's whole component set to and from it bit-exactly, and `Json.encode`/`parse` for compact, stable, diffable text. One-call save/load for entities and the backbone of data-driven tooling.
### Engine, animation & lighting
- **feat**: Engine-owned systems (#43) — the ECS hook that auto-runs a system each frame over a component a game merely declares, no `handler` wired: `SpriteAnim` advances sprite-sheet frames and `Motion` advances value tweens for free. Built on the reflection ABI, so it costs nothing in a game that declares neither.
- **feat**: Animation ergonomics (#48) — an ergonomic layer over those systems: `Anim.clip`/`Anim.play` for named spritesheet clips, `Anim.on_frame`/`fired` frame events, `Motion.to` one-call tweens, and fluent engine-advanced `Tween` handles (`to`/`chain`/`delay`/`parallel`/`stop`). All integer and deterministic under replay.
- **feat**: `Light.*` 2D lighting (#4) — a deterministic software light-accumulation pass over the framebuffer: `ambient` tinting, additive radial `point` lights with linear falloff, and hard shadows cast against rectangular occluders. Integer + Q16.16, identical every run and headless.
- **feat**: ECS-native lighting (#47) — a torch is now just an entity carrying `Light2D`, a wall an `Occluder`, and one `Ambient` sets the night tint; the engine runs the whole light pass at the end of the Render phase with no `Light.*` calls wired by hand.
- **feat**: Lighting render-quality tiers (#49) — cone/flashlight `spot` lights, a `falloff` exponent, `soft` penumbra shadows, colour `gel` cookies, normal-mapped surfaces (N·L) and a `time_of_day` day/night ramp, all on the same deterministic accumulation core and consumable via optional `Light2D` fields.
### Input
- **feat**: Action maps + record/replay (#7) — gameplay reads named, rebindable actions instead of physical keys, and the single per-frame `Input.poll` makes deterministic replay fall out for free: `Input.record` captures the tape and `Input.replay` feeds it back exactly — the seed of lockstep netcode.
- **feat**: Device layer (#50) — multiple simultaneous held keys, analog axes and a normalized vector, the mouse (position/delta/buttons/wheel), gamepads and touch, all injectable on every target (`Input.press`/`set_mouse`/`set_pad`/`set_touch`) and snapshotted whole into the replay tape.
- **feat**: Native hardware bindings (#51) — the device layer's macOS side wired into cocoa.ll: live cursor position, per-frame GameController polling into gamepad buttons/axes (SDL button order), and NSTouch routing, all DCE'd out of a headless build. No API changes.
### Standard library
- **feat**: `Audio.*` (#22) — sound effects and music over a new AVAudioPlayer backend: `load`/`play`/`play_music`/`stop`/`volume`/`pitch`/`is_playing`. Out-of-band (real-time, not part of the simulation) but frame-driven, so a replay fires the same sounds at the same frames; headless builds carry it as dead-stripped no-ops.
- **feat**: `Http.*` (#6) — a poll-based HTTP/HTTPS client for out-of-band data (leaderboards, cloud saves, remote config) that never blocks the frame, over an NSURLConnection transport with system TLS on by default. Pairs with `Json.parse`; the response parser is pure Ludic and tested offline.
- **feat**: Jobs, Promises & opt-in Sync (#14) — a layered concurrency library. `Job.*`/`Promise.*` are the safe default: cooperative futures pumped a little each frame so heavy work spreads out, combined with `Promise.all`/`race`. The advanced `Sync.*` tier adds mutexes, atomics and bounded channels. A deterministic cooperative scheduler — results are collected on the main thread and a Job never touches the world directly, so lockstep and replays stay bit-exact.
### Testing & tooling
- **feat**: Built-in test framework (#12) — a `test "name" { … }` block auto-discovered and run by a synthetic runner (no `entry` to write), with `expect`/`expect_eq`/`expect_near` assertions that report every failure and exit non-zero. `expect_near` carries the tolerance fixed-point game math needs.
- **feat**: Line coverage (#45) — compile with `--coverage` and the compiler instruments each statement with a per-line hit counter dumped at exit; `bin/x test --coverage` aggregates the dumps into a per-file report naming the unreached lines. Flag-gated and additive — an ordinary build stays byte-identical.
### Fixes
- **fix**: `const` of a non-int type is no longer miscompiled. A `const` reference lowered to its initializer's raw integer bits typed as `int`, so `const X: fixed = 10.0` computed as the raw Q16.16 value `655360` instead of `10.0` — silently corrupting fixed-point math (and, in one case, spinning an infinite loop). Const references now emit their initializer with its real type. Every existing const is an `int` literal, so the lowering there is byte-identical and the bootstrap fixpoint and golden renders are unchanged.
## v0.1.0 — 2026-08-30
- **ci**: Continuous integration — Forgejo Actions workflows build the toolchain from the seed, run the regression + editor suites, assert the C-free bootstrap fixpoint, and lint commit messages on every push and pull request.
@ -11,4 +63,3 @@ changesets under changes/ by x release; do not edit released sections by hand.
- **feat**: Native 2D backend — an ECS core with a deterministic fixed-point (Q16.16) runtime, a windowed Cocoa target on macOS and a headless PPM renderer that runs anywhere.
- **feat**: Self-hosted, C-free toolchain — the compiler, runtime, task runner and editor tools are all written in Ludic and built from a checked-in LLVM-IR seed with clang alone; `x bootstrap-cfree` proves the compiler rebuilds itself byte-for-byte.
- **feat**: Versioning and releases — SemVer with `ludicc --version`, a changeset-driven `CHANGELOG.md`, and `x release` to bump, tag, and publish a Forgejo release with source and toolchain artifacts.

View file

@ -1 +1 @@
0.1.0
0.2.0

View file

@ -1,12 +0,0 @@
bump: minor
type: feat
**Animation ergonomics (#48).** An ergonomic layer over the engine-owned
SpriteAnim/Motion systems: `Anim.clip` registers named spritesheet clips and
`Anim.play(entity, "run")` plays one (or `Anim.play(entity, fps, frames, mode)`
directly); `Anim.on_frame` / `Anim.fired` arm and read frame events (the engine
flags the tick a clip lands on a frame, gameplay reacts); `Motion.to` starts a
value tween over the Motion component in one call. New fluent, engine-advanced
`Tween` handles — `Tween.to` / `Tween.chain` / `Tween.delay` build a sequence,
`Tween.value` / `Tween.done` / `Tween.parallel` / `Tween.stop` read and control
it — advanced each Update tick by an engine-owned system. All integer and
deterministic, so animation and motion reproduce exactly under replay/lockstep.

View file

@ -1,15 +0,0 @@
bump: minor
type: feat
**Audio standard library (#22).** `Audio.*` — sound effects and music over a new
platform audio backend (`runtime/native/audio.ll`, AVAudioPlayer on macOS, spliced
and linked with AVFoundation only when a windowed build uses it). `Audio.load`
loads a sound file into a handle; `Audio.play` / `play_sound` fire it one-shot,
`play_music` loops it on a single music channel; `Audio.stop` / `stop_music` /
`stop_all` stop them; `Audio.volume` sets the master volume and `Audio.pitch` the
playback rate, both across every loaded sound; `Audio.is_playing` reports state.
Playback is out-of-band — the audio device is real-time, explicitly not part of
the deterministic simulation — but every trigger is an ordinary frame-driven
call, so a recorded/replayed run fires the same sounds at the same frames.
Headless builds carry the whole API as no-ops (the native `snd_*` calls are
`is_windowed()`-guarded and dead-stripped), so the same game code runs under the
test harness with no audio device.

View file

@ -1,3 +0,0 @@
bump: minor
type: feat
Line coverage for the testing framework — compile with `--coverage` and the compiler instruments each statement with a per-source-line hit counter, dumped at exit (via an `atexit` hook) to the file named by `$LUDIC_COVERAGE` (default `ludic.cov`) as a `FILE <name>` header plus one `<line> <hits>` row per instrumented line. The instrumentation is flag-gated and additive, so an ordinary build — and the compiler's own self-compile — stays byte-identical and the C-free bootstrap fixpoint is untouched. `bin/x test --coverage` compiles the test specs this way, runs them, and aggregates the dumps into a per-file line-coverage report that names the unreached lines. Closes the last open acceptance item of the testing framework (#12).

View file

@ -1,3 +0,0 @@
bump: minor
type: feat
Engine-owned systems over user components (#43) — the ECS hook that runs a system automatically each frame over a component a game merely declares and carries, no `handler` wired. Declaring the well-known `SpriteAnim { ticks, fps, frames, mode, frame }` gives sprite-sheet frame animation (loop / once / ping-pong) that advances `frame` for free; `Motion { ticks, dur, from, to, ease, value, done }` gives value tweening (linear / in / out / in-out) that advances `value`. The systems stand on the reflection ABI, resolving fields by name, so they no-op cleanly when a component or field is absent and cost nothing in a game that declares neither — that build is byte-for-byte unchanged.

View file

@ -1,3 +0,0 @@
bump: minor
type: feat
Error handling for games — `panic(msg)` prints `file:line: panic: <msg>` to stderr and aborts cleanly (exit 1) instead of crashing, and `assert(cond, msg)` does the same, guarded, only when an invariant is false (`file:line: assertion failed: <msg>`). Non-experts get a clear, located message for a broken invariant or an impossible branch, never a raw segfault or a silent wrong result. The source location is baked in at compile time; the message is any string. Recoverable failures as `try`/`else` values are sequenced after the tagged-union type system (#1); this ships the "programmer bug = loud, located panic" half now.

View file

@ -1,11 +0,0 @@
bump: patch
type: fix
**`const` of a non-int type is no longer miscompiled.** A `const` reference used
to lower to its initializer's raw integer bits hardcoded as type `int`, so a
`const X: fixed = 10.0` compared and computed as the raw Q16.16 value `655360`
instead of `10.0` — silently corrupting fixed-point math (and, in one case, spinning
an infinite loop). A const reference now emits its initializer expression with that
expression's real type, so `fixed` / `bool` / `string` consts behave correctly (and
computed const initializers work too). Every existing const is an `int` literal, for
which the lowering is byte-identical, so the bootstrap fixpoint and all golden
renders are unchanged. Covered by selfhost/tests/const.ludic.

View file

@ -1,20 +0,0 @@
bump: minor
type: feat
**HTTP client standard library (#6).** `Http.*` — a poll-based HTTP/HTTPS client
for out-of-band data (leaderboards, cloud saves, remote config, telemetry,
downloads). `Http.get` / `Http.post` / `Http.request` start a request (or
`Http.open` + `Http.set` headers + `Http.body` + `Http.send` to build one up);
`Http.poll` returns `-1` while pending, `0` on error, else the status code, so
the frame never blocks; `Http.status` / `ok` / `text` / `body_len` / `header`
read the reply, and `Http.free` releases it. TLS is the system's, on by default
with certificate verification — an `https://` URL just works. Pairs with the
`Json.*` companion (already shipped in #44): `Json.parse(Http.text(h))`.
The transport (`runtime/native/http.ll`) drives NSURLConnection through the objc
runtime's C ABI on a detached pthread — same hand-written-IR, no-ObjC/no-C style
as cocoa.ll — and is spliced with Foundation only when a program uses `Http.*`
(macOS for now). The response parser (`Http.parse`, header lookup) is pure Ludic
and portable, so the suite exercises it offline with no network. HTTP depends on
the network and wall clock and is explicitly out-of-band: it must never feed the
deterministic lockstep/replay simulation, like `Net.*` and `Time.now`. Also adds
the `\r` string/char escape the protocol needs.

View file

@ -1,3 +0,0 @@
bump: minor
type: feat
Input action maps + deterministic record/replay (#7) — gameplay now reads named actions instead of physical keys, so keys are rebindable and a control scheme is data. `Input.bind(action, key)` binds a key to an action, `Input.down`/`Input.pressed` read it (held vs one-shot edge), and `Input.rebind(action, from, to)` remaps it at runtime for an options screen. `Input.poll` is the single per-frame input read, which makes deterministic replay fall out for free: `Input.record` captures the polled key each frame and `Input.replay` feeds the tape back so a run reproduces exactly — the seed of lockstep netcode. All integer and deterministic over the single-key poll every target provides; the multi-key/gamepad/touch/analog device layer is tracked as a follow-up.

View file

@ -1,14 +0,0 @@
bump: minor
type: feat
**Input device layer (#50).** Beyond one key per frame, gameplay can now read
multiple simultaneous held keys (`Input.key_down` / `key_pressed` /
`key_released`), analog `Input.axis` and a normalized `Input.vector`, the mouse
(`Input.mouse_x/y`, `mouse_dx/dy`, `mouse_down`, `wheel`), gamepads
(`Input.pad_button` / `pad_axis` / `pad_connected`) and touch
(`Input.touch_count` / `touch_x/y`). The held set is fed by the window when
windowed — cocoa.ll now tracks keyDown/keyUp into a held-key bitset and the mouse
buttons/wheel — and by the `Input.press` / `Input.set_mouse` / `Input.set_pad` /
`Input.set_touch` injection on every target (Godot-style action injection, for
replays, AI, and network-fed input). All integer and deterministic, and
`Input.record` / `replay` snapshot the full per-frame device state, extending
#7's single-key tape.

View file

@ -1,14 +0,0 @@
bump: minor
type: feat
**Native input hardware bindings (#51).** The #50 device layer's platform side
is now wired on macOS, feeding the same state buffers the read APIs already use —
no API changes. cocoa.ll reads the live cursor position (`mouseLocationOutsideOfEventStream`,
converted to framebuffer pixels and y-flipped) so windowed games get
`Input.mouse_x/y` without injection; polls GameController.framework each frame
(`GCController.controllers` → extended-gamepad buttons/sticks) into
`Input.pad_button` / `pad_axis` / `pad_connected`, mapping to SDL_GameControllerButton
order; and routes the view's NSTouch phase handlers into `Input.touch_count` /
`touch_x/y`. A windowed build now loads GameController (via `-needed_framework`,
since its classes are reached by name); the whole binding is DCE'd out of a
headless build. `Input.set_pad` / `set_touch` injection still works on every
target, so replays, AI, and network-fed input are unchanged.

View file

@ -1,3 +0,0 @@
bump: minor
type: feat
Jobs, Promises & opt-in Sync — a layered concurrency library (#14). The safe default is `Job.*` / `Promise.*`: a `Job` is a future — start background work with `Job.run(kind, arg)` (a cooperative compute that advances a little each `Job.pump(budget)` and finishes after enough frames, so heavy work spreads out instead of hitching one), or drive one by hand with `Job.defer` + `Job.fulfill` / `Job.fail` / `Job.cancel`; poll it with `done`/`ok`/`failed`/`cancelled`, read `result`/`error`, and count outstanding work with `Job.pending`. `Promise.all`/`Promise.race` combine handle lists into a group job resolved on the main thread, and `Promise.count_done`/`Promise.all_done` give a loading bar its numerator and its ready check. The advanced, opt-in `Sync.*` tier — `mutex`/`lock`/`unlock`/`try_lock`, an atomic counter (`atomic`/`get`/`set`/`add`/`cas`) and a bounded int `channel` (`send`/`recv`/`can_recv`/`len`), plus `cpu_count` — is the "here be dragons" surface for engine-level message passing. The whole thing is a deterministic cooperative scheduler: results are collected on the main thread at a point you choose and a Job never touches the ECS world directly, so lockstep networking and replays stay bit-exact — the same jobs and the same budget reproduce byte-for-byte on every target, and a preemptive OS-thread backend can slot behind this same API later. Ludic has no first-class functions, so a Job carries a compute kind + int argument (or a hand-driven `defer`) rather than a closure, and Promise progress is polled rather than chained through a `then`. Written in Ludic and spliced on demand (like Regex/Dict/Numeric), so a program that never mentions `Job.*`/`Promise.*`/`Sync.*` compiles byte-identically and the C-free bootstrap fixpoint is untouched.

View file

@ -1,3 +0,0 @@
bump: minor
type: feat
ECS-native 2D lighting (#47) — a torch is now just an entity carrying `Light2D { x, y, radius, color, intensity }`, a wall an entity carrying `Occluder { x, y, w, h }`, and one `Ambient { color }` entity sets the night tint. The engine runs the whole deterministic light pass (ambient modulate → carve occluder shadows → accumulate each additive radial light) at the end of the Render phase and presents — no `Light.*` calls wired by hand. A `Light2D`/`Occluder` takes its position from a `Position { x, y }` component when the entity carries one, else from its own `x`/`y`. Built on the engine-owned-system hook, so it costs nothing in a game that declares none of these components.

View file

@ -1,3 +0,0 @@
bump: minor
type: feat
2D lighting — the `Light.*` namespace: a deterministic software light-accumulation pass over the framebuffer. `Light.ambient` modulates the scene toward a tint (night/cave mood), `Light.point` adds a radial glow with linear falloff clamped per channel, and `Light.occlude`/`Light.clear_occluders` cast hard shadows by blocking a light's rays against rectangular occluders. Integer + Q16.16 fixed, so a scene lights identically every run and in a headless render.

View file

@ -1,12 +0,0 @@
bump: minor
type: feat
**Lighting render-quality tiers (#49).** The 2D light pass gains the tiers 3-4
from the original proposal, all on the same deterministic accumulation core:
`Light.spot` cone/flashlight lights (direction + spread), a `Light.falloff`
exponent (linear → quadratic → …), `Light.soft` soft shadows (occluder edges
fade through a penumbra), `Light.gel` colour cookies (centre → rim tint),
normal-mapped surfaces via `Light.normal` + `Light.height` (surfaces shade by
facing, N·L), and a `Light.time_of_day` day/night ambient ramp. The engine
lighting system consumes matching optional `Light2D` fields
(`direction`/`spread`/`falloff`/`softness`/`gel`) too. Every tier is integer +
Q16.16 fixed, so scenes light identically on every run and headless.

View file

@ -1,3 +0,0 @@
bump: minor
type: feat
Recoverable failures as values (#46) — a fallible function returns a `result` (a new value type), built with `ok(payload)` on success or `err(message)` on failure, and the caller recovers a value with `try EXPR else { … }`: on `ok` the whole expression is the payload, on `err` the `else` block runs — with the failure message bound to `error` — and its trailing expression supplies the fallback. It is a plain branch on the result's tag: no exceptions, no hidden control flow, nothing unwinds the stack. `is_ok(r)` / `is_err(r)` classify a result without unwrapping it. Payloads are any i32-width scalar (int/fixed/bool/entity); the whole feature is additive and only kicks in when `ok`/`err`/`try` are used, so programs that don't touch it compile byte-identically. Complements `panic`/`assert` from #8, which handle the unrecoverable programmer-bug half. The optional top-level frame `recover` remains deferred (it needs a frame-abort/unwind mechanism); the full tagged-union/`any` generalization is tracked in #1.

View file

@ -1,3 +0,0 @@
bump: minor
type: feat
Tagged-union enums (#56) — an `enum` variant may now carry a payload (`enum Tile { Empty, Wall, Door(int), Portal(int, int) }`), turning the enum into a sum type. Variants are constructed by name (`Door(3)`, bare `Empty`) and boxed as a tag plus payload slots; `match` destructures them, binding each payload in the arm's scope (`Door(n) => …`, `Portal(x, y) => …`). A tagged `match` is checked for exhaustiveness — it must cover every variant or end in `_`, and constructor/pattern arities are checked too — so adding a variant surfaces every site that must handle it. Plain (all-bare) enums keep their zero-cost compile-time-ordinal `Name.Variant` representation, byte-for-byte unchanged.

View file

@ -1,3 +0,0 @@
bump: minor
type: feat
Built-in testing framework — a `test "name" { … }` block, discovered and run automatically by a synthetic runner (no `entry` to write). Inside a test, `expect(cond)`, `expect_eq(a, b)` and `expect_near(a, b, tol)` assert; on failure they print `file:line: … failed (got …, want …)` and mark the test failed without aborting, so one run reports every failure. The runner prints `ok - name` / `FAIL - name` per test, a `== N passed, M failed ==` summary, and exits non-zero if any test failed — so `ludic spec_test.ludic` drops straight into `bin/x` and CI. `expect_near` carries the tolerance fixed-point and accumulated-integer game math need. Coverage instrumentation is a follow-up.

View file

@ -1,13 +0,0 @@
bump: minor
type: feat
**2D value types — `IVec2` and `Rect` (#1, phase 1).** Two by-value spatial
types that lower to packed integers, so they copy like scalars and never
allocate. `IVec2` is an integer 2D vector (a pair of `int` packed into one
`i64`) for tile and grid coordinates — `IVec2.make/zero/x/y/add/sub/scale/dot`,
the grid distance `IVec2.manhattan`, `IVec2.equal`, and `IVec2.to_vector` to
widen into the fixed-point `Vector`. `Rect` is an axis-aligned rectangle (four
Q16.16 `fixed` components packed into one `i128`) for HUD boxes and hitboxes —
`Rect.make/x/y/w/h`, the derived `Rect.right/bottom/center`, and the
`Rect.contains` (point) and `Rect.intersects` (overlap) tests. Both are exact
and deterministic, bit-identical on every platform, and complement the existing
`Vector` and `Color` types that already cover phase 1's other 2D primitives.

View file

@ -1,13 +0,0 @@
bump: minor
type: feat
**Exact numbers for game economies — `BigInt` + `Decimal` (#52, types phase 2).**
The thing a tycoon or idle game must never round wrong. `BigInt.*` is an
arbitrary-precision integer (sign-magnitude, base-1e9 limbs) with `from`/`parse`,
`add`/`sub`/`mul`/`pow`, integer `div`/`mod`, `cmp`/`eq`/`is_zero`, `to_int` and
`str` — for idle counters and exact huge currencies that overflow a 32/64-bit
int. `Decimal.*` is an exact base-10 fixed-point number (a `BigInt` mantissa
plus a decimal scale) with `from`/`parse`, exact `add`/`sub`/`mul`, `cmp`/`eq`,
`scale`/`rescale` and `str` — so money like `0.10 + 0.20` is exactly `0.30`, no
binary rounding. Both are exact and therefore deterministic; no `f32`/`f64`. The
engine (runtime/native/bignum.ludic) is spliced in on demand, so it costs
nothing when unused and works in plain tools as well as games.

View file

@ -1,13 +0,0 @@
bump: minor
type: feat
**Containers — `Dict` + `Set` (#54, types phase 4).** Everyday string-keyed
lookups backed by one open-addressing hash table (FNV-1a, linear probing,
tombstone deletes, grows at load factor 0.7), spliced in on demand.
`Dict.*` maps string keys to `int` values — `new`/`set`/`get`/`get_or`/`has`/
`remove`/`size`/`clear`/`keys` — for resource counts and id/name registries.
`Set.*` is a set of string members — `new`/`add`/`has`/`remove`/`size`/`clear`/
`members` — for tags, unlocked achievements and visited tiles. O(1) average
lookup instead of a linear list scan. Values are `int` (also holds an `entity`
or any small id); the `Value.*` tree already covers richer/heterogeneous maps,
and `[T; N]` inline fixed arrays remain future work (typed buffers `words` /
`fixeds` / `pointers` and `[]T` slices cover heap-backed arrays today).

View file

@ -1,15 +0,0 @@
bump: minor
type: feat
**Polish numeric types — `Huge` + `Angle` + `Percent` (#55, types phase 5).**
`Huge.*` is an idle/incremental big number (normalized mantissa x 10^exponent)
reaching far past the integer range — `from`/`add`/`sub`/`mul`/`neg`/`cmp`/
`sign`/`mantissa`/`exp`/`str` (scientific `1.23e45`); it is display-scale, not
lockstep-exact (use BigInt/Decimal for exactness). `Angle.*` is an auto-wrapping
radian angle — `from_degrees`/`to_degrees`/`wrap`/`sin`/`cos`/`add`/`diff`
(shortest signed rotation)/`lerp` (shortest arc) — over the deterministic
`Math.*` trig. `Percent.*` is a value clamped to [0,1] — `clamp`/`of`/`lerp`/
`apply` — for health fractions, volumes and interpolation `t`. All spliced in on
demand. Remaining phase-5 items are already covered or need front-end work:
`duration` = `Duration.*`, `rune`/`char` = `Unicode.*`, `i64` = `long`; and the
nominal-safety wrappers `handle` / typed `name` plus the other sized ints need a
type-checking pass, tracked for later.

View file

@ -1,11 +0,0 @@
bump: minor
type: feat
**`option` — a maybe-a-value type (#53, types phase 3).** The companion to
`result` (#46): `some(v)` wraps a present value (any i32-width scalar), `none()`
is the empty case — a missing value with no magic `-1` sentinel — and
`is_some`/`is_none`/`unwrap_or(o, fallback)` test and read it. A heap
`%Option = { i32 present, i32 value }`, additive and gated, so a program that
does not use it compiles byte-identically. With `result`/`ok`/`err`/`try`
already shipped, the concrete option/result safety types of phase 3 are done;
general tagged-union enums (variant payloads + binding `match` + exhaustiveness)
are tracked separately in #56.

View file

@ -1,3 +0,0 @@
bump: minor
type: feat
Generic value tree + reflection serialize/deserialize + JSON bridge (#44) — a self-describing `Value` node (null/int/fixed/bool/str/list/object) with constructors, builders (`Value.add`/`Value.put`) and accessors (`Value.get`/`Value.at`/`Value.count`/`Value.kind`/`Value.as_int`/`Value.as_str`/…). `Reflect.serialize(entity)` walks an entity's whole component set into a value tree (one member per component, each a sub-object of its fields) and `Reflect.apply(entity, value)` writes one back — a `fixed` field becomes a `fixed` node, everything else an `int` node, so the round-trip is bit-exact; the model id rides along under `"@kind"`. `Json.encode`/`Json.parse` bridge the tree to and from compact, stable, diffable JSON text, with `fixed` written as an exact terminating decimal that parses back bit-for-bit. Together they are a one-call save/load for entities and the backbone of data-driven tooling. Written in Ludic and spliced on demand (like Query/Light), so a program that doesn't touch `Value.*`/`Json.*`/`Reflect.serialize` compiles byte-identically and the C-free bootstrap fixpoint is untouched. The general tagged-union/`any` language type remains tracked in #1; this delivers the concrete value tree the serializer needs.