From 237e13c95e48078b2a11271b118ecb5aa99ae895 Mon Sep 17 00:00:00 2001 From: Orkuncakilkaya Date: Tue, 1 Sep 2026 03:38:09 +0300 Subject: [PATCH] chore(release): v0.2.0 --- CHANGELOG.md | 53 ++++++++++++++++++++++++++++- VERSION | 2 +- changes/anim-ergonomics.md | 12 ------- changes/audio-namespace.md | 15 -------- changes/coverage.md | 3 -- changes/engine-systems.md | 3 -- changes/error-handling.md | 3 -- changes/fix-fixed-const-type.md | 11 ------ changes/http-namespace.md | 20 ----------- changes/input-actions.md | 3 -- changes/input-device-layer.md | 14 -------- changes/input-hardware-bindings.md | 14 -------- changes/jobs-concurrency.md | 3 -- changes/light-ecs-components.md | 3 -- changes/light-namespace.md | 3 -- changes/light-tiers.md | 12 ------- changes/recover.md | 3 -- changes/tagged-union-enums.md | 3 -- changes/testing-framework.md | 3 -- changes/types-2d-ivec-rect.md | 13 ------- changes/types-bignum-decimal.md | 13 ------- changes/types-containers.md | 13 ------- changes/types-huge-angle-percent.md | 15 -------- changes/types-option.md | 11 ------ changes/value-tree.md | 3 -- 25 files changed, 53 insertions(+), 198 deletions(-) delete mode 100644 changes/anim-ergonomics.md delete mode 100644 changes/audio-namespace.md delete mode 100644 changes/coverage.md delete mode 100644 changes/engine-systems.md delete mode 100644 changes/error-handling.md delete mode 100644 changes/fix-fixed-const-type.md delete mode 100644 changes/http-namespace.md delete mode 100644 changes/input-actions.md delete mode 100644 changes/input-device-layer.md delete mode 100644 changes/input-hardware-bindings.md delete mode 100644 changes/jobs-concurrency.md delete mode 100644 changes/light-ecs-components.md delete mode 100644 changes/light-namespace.md delete mode 100644 changes/light-tiers.md delete mode 100644 changes/recover.md delete mode 100644 changes/tagged-union-enums.md delete mode 100644 changes/testing-framework.md delete mode 100644 changes/types-2d-ivec-rect.md delete mode 100644 changes/types-bignum-decimal.md delete mode 100644 changes/types-containers.md delete mode 100644 changes/types-huge-angle-percent.md delete mode 100644 changes/types-option.md delete mode 100644 changes/value-tree.md diff --git a/CHANGELOG.md b/CHANGELOG.md index cb8b3e69..3f057b1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. - diff --git a/VERSION b/VERSION index 6e8bf73a..0ea3a944 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.1.0 +0.2.0 diff --git a/changes/anim-ergonomics.md b/changes/anim-ergonomics.md deleted file mode 100644 index b7dc0dab..00000000 --- a/changes/anim-ergonomics.md +++ /dev/null @@ -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. diff --git a/changes/audio-namespace.md b/changes/audio-namespace.md deleted file mode 100644 index 667970b8..00000000 --- a/changes/audio-namespace.md +++ /dev/null @@ -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. diff --git a/changes/coverage.md b/changes/coverage.md deleted file mode 100644 index 2c993df3..00000000 --- a/changes/coverage.md +++ /dev/null @@ -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 ` header plus one ` ` 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). diff --git a/changes/engine-systems.md b/changes/engine-systems.md deleted file mode 100644 index 3b3ec87f..00000000 --- a/changes/engine-systems.md +++ /dev/null @@ -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. diff --git a/changes/error-handling.md b/changes/error-handling.md deleted file mode 100644 index 9559a291..00000000 --- a/changes/error-handling.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: minor -type: feat -Error handling for games — `panic(msg)` prints `file:line: panic: ` 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: `). 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. diff --git a/changes/fix-fixed-const-type.md b/changes/fix-fixed-const-type.md deleted file mode 100644 index e68bd16a..00000000 --- a/changes/fix-fixed-const-type.md +++ /dev/null @@ -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. diff --git a/changes/http-namespace.md b/changes/http-namespace.md deleted file mode 100644 index cd02d8f2..00000000 --- a/changes/http-namespace.md +++ /dev/null @@ -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. diff --git a/changes/input-actions.md b/changes/input-actions.md deleted file mode 100644 index 4450fa96..00000000 --- a/changes/input-actions.md +++ /dev/null @@ -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. diff --git a/changes/input-device-layer.md b/changes/input-device-layer.md deleted file mode 100644 index a5c53d2f..00000000 --- a/changes/input-device-layer.md +++ /dev/null @@ -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. diff --git a/changes/input-hardware-bindings.md b/changes/input-hardware-bindings.md deleted file mode 100644 index 3c64bf0d..00000000 --- a/changes/input-hardware-bindings.md +++ /dev/null @@ -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. diff --git a/changes/jobs-concurrency.md b/changes/jobs-concurrency.md deleted file mode 100644 index 9ef6ba2a..00000000 --- a/changes/jobs-concurrency.md +++ /dev/null @@ -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. diff --git a/changes/light-ecs-components.md b/changes/light-ecs-components.md deleted file mode 100644 index 393d9019..00000000 --- a/changes/light-ecs-components.md +++ /dev/null @@ -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. diff --git a/changes/light-namespace.md b/changes/light-namespace.md deleted file mode 100644 index 1ea54d94..00000000 --- a/changes/light-namespace.md +++ /dev/null @@ -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. diff --git a/changes/light-tiers.md b/changes/light-tiers.md deleted file mode 100644 index 30a2d9e9..00000000 --- a/changes/light-tiers.md +++ /dev/null @@ -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. diff --git a/changes/recover.md b/changes/recover.md deleted file mode 100644 index e9566c11..00000000 --- a/changes/recover.md +++ /dev/null @@ -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. diff --git a/changes/tagged-union-enums.md b/changes/tagged-union-enums.md deleted file mode 100644 index 2e90b5da..00000000 --- a/changes/tagged-union-enums.md +++ /dev/null @@ -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. diff --git a/changes/testing-framework.md b/changes/testing-framework.md deleted file mode 100644 index 2ae3dd54..00000000 --- a/changes/testing-framework.md +++ /dev/null @@ -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. diff --git a/changes/types-2d-ivec-rect.md b/changes/types-2d-ivec-rect.md deleted file mode 100644 index b559858c..00000000 --- a/changes/types-2d-ivec-rect.md +++ /dev/null @@ -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. diff --git a/changes/types-bignum-decimal.md b/changes/types-bignum-decimal.md deleted file mode 100644 index 2122bcca..00000000 --- a/changes/types-bignum-decimal.md +++ /dev/null @@ -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. diff --git a/changes/types-containers.md b/changes/types-containers.md deleted file mode 100644 index 27c31e91..00000000 --- a/changes/types-containers.md +++ /dev/null @@ -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). diff --git a/changes/types-huge-angle-percent.md b/changes/types-huge-angle-percent.md deleted file mode 100644 index 9443004b..00000000 --- a/changes/types-huge-angle-percent.md +++ /dev/null @@ -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. diff --git a/changes/types-option.md b/changes/types-option.md deleted file mode 100644 index 0ca67ddb..00000000 --- a/changes/types-option.md +++ /dev/null @@ -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. diff --git a/changes/value-tree.md b/changes/value-tree.md deleted file mode 100644 index 9d32516a..00000000 --- a/changes/value-tree.md +++ /dev/null @@ -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.