From 0a3df45d203f66f068a916a18df75057950d2395 Mon Sep 17 00:00:00 2001 From: Orkuncakilkaya Date: Sat, 5 Sep 2026 01:52:18 +0300 Subject: [PATCH] chore(release): v0.4.0 --- CHANGELOG.md | 113 +++++++++++++++++++++++++ VERSION | 2 +- changes/ai-seek-pathing.md | 3 - changes/audio-link-windowed.md | 3 - changes/become-anywhere.md | 3 - changes/bootstrap-mkdir-bin.md | 7 -- changes/changelog-structure.md | 14 --- changes/char-escapes.md | 3 - changes/clearcolor-expr.md | 3 - changes/component-access.md | 3 - changes/compound-assign-types.md | 3 - changes/countdown.md | 3 - changes/cursor-confine-mode3.md | 3 - changes/diagnostics-file-line.md | 3 - changes/duplicate-global-diagnostic.md | 3 - changes/engine-enums.md | 3 - changes/engine-ui-events.md | 3 - changes/enum-typed-machines.md | 3 - changes/fmt-paren-spacing.md | 3 - changes/formatter-brackets.md | 3 - changes/global-initializers.md | 3 - changes/half-the-game.md | 3 - changes/interp-braces.md | 3 - changes/ivec2-members.md | 3 - changes/key-constants-namespaces.md | 3 - changes/layer-queries.md | 3 - changes/less-to-write.md | 4 - changes/list-literals.md | 3 - changes/managers.md | 3 - changes/namespace-named-args.md | 3 - changes/os-pid.md | 3 - changes/overlay-phase.md | 3 - changes/prefabs.md | 3 - changes/prefs-package.md | 3 - changes/prelude-symbol-prefix.md | 3 - changes/quiet-builds.md | 8 -- changes/release-ci.md | 14 --- changes/reserved-word-functions.md | 3 - changes/self-in-spawn-hooks.md | 3 - changes/shooter-aim-centre.md | 3 - changes/site-redesign.md | 22 ----- changes/solids-solid2.md | 3 - changes/sprite-atlas-ids.md | 3 - changes/sprite-strip.md | 3 - changes/tileskin-system.md | 3 - changes/ui-close.md | 3 - changes/ui-ids-anywhere.md | 3 - changes/unary-minus-fixed.md | 3 - 48 files changed, 114 insertions(+), 190 deletions(-) delete mode 100644 changes/ai-seek-pathing.md delete mode 100644 changes/audio-link-windowed.md delete mode 100644 changes/become-anywhere.md delete mode 100644 changes/bootstrap-mkdir-bin.md delete mode 100644 changes/changelog-structure.md delete mode 100644 changes/char-escapes.md delete mode 100644 changes/clearcolor-expr.md delete mode 100644 changes/component-access.md delete mode 100644 changes/compound-assign-types.md delete mode 100644 changes/countdown.md delete mode 100644 changes/cursor-confine-mode3.md delete mode 100644 changes/diagnostics-file-line.md delete mode 100644 changes/duplicate-global-diagnostic.md delete mode 100644 changes/engine-enums.md delete mode 100644 changes/engine-ui-events.md delete mode 100644 changes/enum-typed-machines.md delete mode 100644 changes/fmt-paren-spacing.md delete mode 100644 changes/formatter-brackets.md delete mode 100644 changes/global-initializers.md delete mode 100644 changes/half-the-game.md delete mode 100644 changes/interp-braces.md delete mode 100644 changes/ivec2-members.md delete mode 100644 changes/key-constants-namespaces.md delete mode 100644 changes/layer-queries.md delete mode 100644 changes/less-to-write.md delete mode 100644 changes/list-literals.md delete mode 100644 changes/managers.md delete mode 100644 changes/namespace-named-args.md delete mode 100644 changes/os-pid.md delete mode 100644 changes/overlay-phase.md delete mode 100644 changes/prefabs.md delete mode 100644 changes/prefs-package.md delete mode 100644 changes/prelude-symbol-prefix.md delete mode 100644 changes/quiet-builds.md delete mode 100644 changes/release-ci.md delete mode 100644 changes/reserved-word-functions.md delete mode 100644 changes/self-in-spawn-hooks.md delete mode 100644 changes/shooter-aim-centre.md delete mode 100644 changes/site-redesign.md delete mode 100644 changes/solids-solid2.md delete mode 100644 changes/sprite-atlas-ids.md delete mode 100644 changes/sprite-strip.md delete mode 100644 changes/tileskin-system.md delete mode 100644 changes/ui-close.md delete mode 100644 changes/ui-ids-anywhere.md delete mode 100644 changes/unary-minus-fixed.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 888871c5..2d85e0ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,119 @@ type. Preview the next one with `x release --dry-run`. A released section may carry a hand-written summary paragraph above its groups (v0.2.0 has one); the generated bullets below it are not edited by hand. +## v0.4.0 — 2026-09-05 + +### Features + +- **Prefabs.** `prefab Grunt: Creature { Stats { hp: 30 }, Weapon { def_id: 1 } }` names a model with preset fields; `spawn Grunt { Position { x: 40 } }` spawns it, the spawn's own fields winning over the presets. Prefabs chain (`prefab Grunt: Foe` where `Foe` is a prefab) so shared presets live once. `spawn` is now also an expression yielding the new entity (`let e = spawn Grunt { … }`), and `Prefab.spawn(name: "Grunt")` spawns one chosen at runtime by name (-1 when none matches). +- **`countdown` fields.** A component field declared `frames_left: countdown = 0` is an `int` the engine steps toward 0 once per Update for every live entity carrying the component (never below 0). Roll timers, invulnerability frames, hit flashes and cooldowns need no hand-written "decrement each frame" handler: set the field, test it. +- A `machine` over an enum-typed store maps its states to the enum by name: with `enum HeroState { Idle, Rolling }` and `var hero_state: HeroState = HeroState.Idle`, `machine hero_state { state Idle { … } state Rolling { … } }` dispatches on `HeroState.Idle` / `HeroState.Rolling` — no `state Idle = HeroState.Idle` repetition, and a state that names no variant is a compile error. A bare enum is now a first-class `int`-sized type for `var`, params, fields and returns (`llty`), and `Enum.Variant` folds in a global initializer. +- A handler inside a scene `layer` may carry `@Queries(these: […], on: Model)`, so a scene can own its per-entity systems (`@Queries(these: [Particle]) handler AgeSparks phase Update { … }` runs once per matching entity only while that scene is active). Any other annotation on a layer handler is reported. +- A program-scope `var` may be initialized with any expression: `var run: Progress = new Progress`, `var speed: int = BASE_SPEED * 2`, `var origin: IVec2 = IVec2.zero()`. Initializers the compiler cannot fold run once at startup (`@L_init_globals`, after the runtime boots and before the `Start` phase), in declaration order. Previously such an initializer was silently replaced by `0` / `null`. +- Engine managers for what every action game hand-rolls: **`Fx.sparks` / `Fx.number` / `Fx.clear`** — engine-owned sparks and floating damage numbers, moved and aged each Update and drawn after the sprites, with no component, model, handler or draw call in the game; **`Audio.define(name:, path:)` + `Audio.play(name:)` / `Audio.play_music(name:)` / `Audio.named`** — a sound bank by name (the handle form still works); **`Camera.shake_for(amount:, frames:)`** — a timed shake the engine decays; **`Assets.enqueue`** now loads `.wav` / `.mp3` into the sound bank and `.ttf` / `.ttc` into a font table (**`Assets.font(name:)`**) alongside images, so one loading scene covers everything; **`Prop.count()`** — how many live entities carry a component. +- Every compiler diagnostic is reported as `file:line: error: message` — the file the line really lives in, even through imports — so editors can jump to it. Unexpected characters are errors (they used to be skipped silently), and defining one `function` twice is reported in source terms instead of failing in the IR assembler. +- Less to write for a game. **`Map` cell API** — `Map.get/set/fill/rect/border/random_cell/is_solid/is_solid_at/width/height`: a game edits the engine's tilemap in place and asks it what is solid (from the `Solids` config) instead of keeping its own grid. **`Sprite` does the small animation work**: `move_id` is the strip drawn while the entity moves, `face: 1` turns it toward its movement, and the `flash` / `blink` countdowns give a white hit flash and an invulnerability blink with no handler. **`scene X shows Menu`** — the engine opens the menu (and frees the cursor) on enter, draws it last in Overlay, and closes it on exit. **`import "dir/*.ludic"`** imports a directory in name order. **`IVec2.distance2/within/heading/along/step`** and **`Angle.diff_degrees`** cover the geometry every action game rewrites; **`List.sample`** draws distinct random picks; **`Input.move_i`** is the standard top-down movement intent; **`AimMode.Auto`** aims with the mouse, or the right stick while a pad is connected; **`Weapon.set_rate` / `Weapon.rate`** change a fire rate in place; projectiles now die on `Solids` tiles by themselves; **`Screen.bar`** draws a meter. + Handlers inside a scene's layers are scene-qualified (`Play_Draw`), so two scenes may both name a handler `Draw`; `enable` / `disable` of a scene's own handler by its bare name still works from inside that scene. +- The engine's numeric parameters have names. ludic.core: `BodyPolicy { Platformer, TopDown }`, `BoundsPolicy { Clamp, Wrap, Bounce, Kill }`, `AnimMode { Loop, Once, PingPong }`; ludic.shooter: `AimMode { Mouse, RightStick, MoveDirection, NearestEnemy }`, `WeaponPattern { Single, Cone, Ring, Spiral }`; ludic.npcai: `BrainModel { StateMachine, Utility, BehaviourTree }`, `AiState { Patrol, Chase, Attack, Flee }`; ludic.gameplay: `StatKind { MaxHp, Attack, Defense, Speed }`, `ModifyOp { Flat, Percent }`; ludic.rpg: `StatusKind { Poison, Regen }`; the input runtime: `CursorMode { Normal, Hidden, Locked, Confined }`. `Body { policy: BodyPolicy.TopDown }` reads as what it is; the old integers still work. The input runtime also names the gamepad buttons: `PadButton { A, B, X, Y, LeftShoulder, RightShoulder, Back, Start }` for `Input.bind_pad(button:)`. +- The second "write less" round, all generic. **ludic.gameplay**: `Stats` carries the build stats every action game bolts on — `damage_pct`, `crit_pct`, `leech_pct` / `leech_hp`, `thorns`, `fire_rate_pct` — and `Combat.damage` applies them itself (a `Crit` event fires; thorns never reflect thorns); `Stats.add(e, stat, amount)` changes a base stat in place and `Stats.scale_hp(e, percent)` scales hp and max_hp; `StatKind` names every code. **ludic.shooter**: `Dash { frames, speed, cooldown_frames }` with `Dash.start(e, dx, dy)` / `Dash.active(e)` — a dodge roll with i-frames the package guards; `Melee { range, half_arc, damage, knockback, frames, cooldown_frames, arc }` with `Melee.swing(e)` (hits every hostile in the arc, knocks back, fires `MeleeHit`) / `Melee.ready` / `Melee.active`; projectiles drawn by the engine in their weapon's colour (`Weapon.set_color`); `TopDown { reticle, reticle_length }` draws the aim line and a mouse cross; the weapon system honours `Stats.fire_rate_pct`. **ludic.dungeon** (new package): `Dungeon.arena / open_arena / random_style / set_exit / entry_point / opposite / at_edge`, with `Side` and `RoomStyle` — arena rooms with mirrored cover, door lanes and exits by side, built into the engine tilemap. **Compiler**: `scene Splash lasts N then Next` (a timed scene), `button … goto: Scene` (a click changes scene, no listener to write). **Runtime**: `Map.random_cell_far`, `Sprite.draw_meter` (hearts / pips), `Assets.enqueue_dir`, `Collider.center`, `Prefs.max`. Also `scene X loads then Y` (the loading scene: pumped, drawn, `AssetsReady` fired), `Prop.despawn_all()`, `Map.to_tile`, and `Brain { hunt_blind }` in ludic.npcai (seek the nearest hostile without line of sight). `Prefab.spawn_at(name:, at:)` spawns and places; `Weapon.reset(id)` restores a definition (no pierce, no homing, its fire rate). Five regression examples cover the additions (`examples/library/prefabs`, `component_access`, `scene_menus`, `managers`, `combat_kit`). `Random.weighted(weights:)` draws an index by weight; `ui` widgets inherit `font` / `size` / `fg` / `align` from their panel; the input runtime names `MouseButton { Left, Right, Middle }`. +- `@ClearColor(expr)` takes any constant expression, so a named palette colour (`@ClearColor(COLOR_FLOOR)`) works as well as a hex literal. +- `Ai.seek` + path-aware `brain_seek` — when a Solids tilemap is present the NPC-AI routes a blocked straight line around obstacles with `Grid.a_star`, so foes flow around pillars instead of getting stuck. +- `Key.*` compile-time key constants (`Key.Space`, `Key.Escape`, `Key.A`, `Key.Up`, ...), folded like `Color.*`; and `Font.*` / `Ui.* / File.*` namespaces so `png_load`/`font_load`/file I/O are namespaced. +- `Os.pid()` returns the process id, for scratch files that concurrent runs of one tool must not share. +- `Overlay` render phase — runs after the engine Render systems (sprites, lights) and before present, so a game's HUD / menus are never painted under an actor. Byte-identical when unused. +- `Prop.of(entity)` and `Prop.has(entity)` — typed access to one entity's component from an entity handle, the same binding a query loop makes. `Hero.of(player).iframes = 20` reads and writes fields directly (no `World.prop_id` / `World.field_id` / `World.get` reflection chain); `Prop.has(e)` is true when `e` is in range, alive, and carries the property, so `-1` is a safe "no entity". A package that declares a real `prop_of` / `prop_has` function keeps it. +- `Solids.solid2` — an optional second solid glyph (e.g. a closed door) the move system also blocks. +- `Sprite.strip(sheet, col, row, count, rows)` — register N consecutive animation frames in one call (the base id for `SpriteAnim`). +- `Sprite` component draws atlas ids (#90) — `Sprite.atlas = 1` routes `esys_sprite` through `atlas_draw_ex` (scale/flip/tint) so atlas cells / multi-cell spans (tall characters) use the engine sprite-render system, not just the 16x16 table. +- `Ui.close()` — deactivate the retained UI (no menu open); the readable form of `Ui.open(id: -1)`. +- `[a, b, c]` list literals build a slice in place; the first element fixes the element type, later elements must match, and `[]` is an error (use `new []T`). Tables of records read as `[Row { … }, Row { … }]`. +- `ludic.prefs` package — `Prefs.*`, a human-readable `key=value` text store for scores / options (the right tool for "remember my best run"; a whole-world `Save.write` is not). +- `v.x` and `v.y` read the components of an `IVec2` value (a local, a global, a record field, or a call result) — the readable form of `IVec2.x(v)` / `IVec2.y(v)`. The compiler's static typing now also follows function return types, namespace calls, `Prop.of(e)` and record fields, so `@Computed` fields expand in those positions too. +- engine tilemap-render system (`TileSkin`, shipped from ludic.core) — one entity per glyph paints the whole `Map.*` grid each Render frame *before* sprites, so a game stops hand-looping the map. `esys_tileskin` registered ahead of `esys_sprite`. +- engine-driven retained UI — a program with a `ui` block has its navigation ticked by the frame loop automatically (from the frame key) and an activation now emits a `UiClicked { id }` event, so scenes react with `@On(UiClicked)` instead of polling `Ui.clicked`. New `Ui.*` namespace (`Ui.open/tick/clicked/set_text/render/build`). + +### Fixes + +- A `UI_Name` handle can be read from any code — a plain function, an `@On(UiClicked)` listener, a global initializer — not only from handlers and scene hooks. The widget table it indexes is now built on first use instead of when `@ui_build` is emitted, which came after functions and listeners and crashed the compiler on such a reference. +- A `var` declared twice — including a game `var` whose name the spliced engine runtime already uses (`ui_font`, `grid`, …) — is now a compile error naming the variable and, when it is the runtime's, saying so (`variable ui_font is also a variable of the engine runtime; choose another name`). Previously the two became one LLVM global and clang reported a redefinition in generated IR. The same check covers `property` names (`property Cell is also a property of the engine runtime; choose another name`); before, the first declaration silently won and field lookups failed with a confusing message. +- A `{` or `}` inside a string literal within an interpolation hole (`` `{f("{")}` ``) is text, not structure; the hole scanner used to miscount it. +- A windowed build that reaches the audio runtime indirectly — through the atlas / `Assets.*` preload queue (which feeds `.wav`/`.mp3` into the sound bank) or the engine sprite-render system, without any `Audio.*` call in the game — now links the native audio backend (`audio.ll` + AVFoundation). Previously `ludicc -o` failed at link with undefined `snd_*` symbols for any windowed game declaring a `Sprite` component; the import of `runtime/native/audio.ludic` now flags the backend link itself. +- Character literals accept the same escapes as strings (`'\''`, `'\\'` and `'\"'` were silently read as 0); an unterminated character literal is now an error. +- Hand-written runtime preludes (string, Os.*, Fs.*, Crypto.*, …) now live under their own `@lp_` symbol prefix, so a user `function` named `is_ws`, `str_eq`, `path_join` and the like no longer collides with them at link time. +- Named arguments now work on namespace functions (`@Namespace(Foo)` and `namespace Foo { export function … }`), not only on builtins and bare functions: `Weapon.def(name: "pistol", fire_rate: 9, damage: 14, speed: 8, spread: 0, pellets: 1, pattern: 0)` reorders to the declared parameter order like any other call. Previously every named call on a namespace function failed with "wrong number of arguments". +- The documented bootstrap works on a fresh clone. `bin/` is gitignored and not + checked in, so `clang selfhost/ludicc.seed.ll -o bin/ludicc` — the first command + in the README, in COMPILING, in CONTRIBUTING and on the site — failed with + `ld: open() failed, errno=2 for 'bin/ludicc'`. Every copy now begins with + `mkdir -p bin`, which is what CI had been doing all along. +- Unary minus keeps its operand type: `-f` on a `fixed` is a `fixed` (it was typed `int`, which broke mixed arithmetic and comparisons). +- `become Scene` now works from an `@On(Event)` listener, a global handler, or a plain function (#91). Code outside a scene's own layers cannot know the leaving scene at compile time, so the compiler emits `@L_scene_leave()` — a dispatch on the live scene id that runs its `on exit` — and calls it there. Previously a listener's `become` reused the last emitted handler's scene (or crashed), and a global handler's `become` skipped the leaving scene's `on exit` entirely. +- `ludic-fmt` keeps `rows[i]`, `new []int`, `s[a..b]`, `emit(…)`, `~x` and list-literal braces tight, and recognises `<< >> & | ^ ~` as operators. +- `ludic-fmt` no longer glues an opening parenthesis to a preceding operator: `let moving = (a or b)` stays as written instead of becoming `let moving =(a or b)`. +- `self()` inside an `@OnSpawn(Model)` or `@OnAttach(Property)` body is now the entity being constructed. Previously it was the entity of the innermost query loop — or the constant 0 when the spawn happened outside any loop — so a hook such as `@OnSpawn(Hero) handler Remember { player = self() }` silently recorded entity 0. +- `x += y` / `-=` / `*=` / `/=` now lower exactly like `x = x op y`: a Q16.16 `fixed` multiplies and divides through the 64-bit path, a string `+=` concatenates, and an `int` added to a `long` widens (they previously emitted raw integer arithmetic on the LLVM type). +- `x release` keeps a changeset's markdown intact. Bodies used to go through + `tr '\n' ' '`, which flattened every multi-line changeset into one paragraph — + nested bullets came out as inline `" - "` runs and a release read as a single + unbroken wall of text. A section is now grouped by change type (**Features**, + **Fixes**, **Performance**, …) with one bullet per changeset and continuation + lines indented to stay inside it. + + - `x release --dry-run` renders the next section to stdout and writes nothing, + so a release can be read before it is cut. + - `x changelog-section ` prints one release's section from + `CHANGELOG.md`; `x changelog-render` re-renders a section from a directory of + changesets. The v0.1.0 and v0.3.0 sections were re-rendered with these. +- cursor `mode 3` (confined) now keeps the OS cursor **associated** (absolute position preserved) and hidden, instead of dissociating it like `mode 2` (lock/relative). Only true-lock `mode 2` uses relative deltas now; `win_mouse` reports the absolute position on `mode 3` and clamps it to the framebuffer. And `mode 3` now **physically confines** the cursor: each frame the platform layer warps it back to the window's content rect (`CGWarpMouseCursorPosition`) whenever it strays past the edge, so clicks can't land outside and the window keeps focus. This lets a top-down game hide + confine the cursor while `aim_mode 0` (mouse aim) keeps resolving to where the reticle points — previously any confine/lock mode silently broke absolute mouse aim, and a confined cursor still escaped the window (#89 follow-up). +- reserved words (`new`, `match`, `spawn`, ...) can no longer name a function — the compiler errors instead of miscompiling. +- the shooter aims / homes / fires from a body's **centre** (Position + Collider offset + half-size) instead of the Position anchor, so auto-aim and homing target what is drawn, not a corner. + +### Documentation + +- The generated site is redesigned around reading rather than launching: a warm + paper ground with a serif display face, one ink-blue accent, and rules instead + of floating cards. Colour is reserved for code. Dark mode is the same design + with the ground inverted, driven entirely by tokens under one + `prefers-color-scheme` block, and the landing page's scroll-reveal animations, + gradient headline, glowing badge and emoji feature icons are gone. + + - Stylesheets are linked files (`base.css` + `site.css`/`docs.css`) instead of + being inlined into all 900+ pages, which cuts the published site from 16 MB to + 5 MB and means a design change no longer requires regenerating to be seen. + - Fonts are the platform's own; the site makes no webfont request. + - `api.css` was dead — the generator never referenced it — and is removed along + with `item.css`, which `docs.css` replaces. + - A page no longer flashes its own title on every plain visit; only a deep link + highlights its target, and under `prefers-reduced-motion` the highlight no + longer stays on the element permanently. + - The copy leads with what is verifiable — ahead-of-time compiled, an ECS in the + syntax, deterministic fixed-point, no C in a build — and the "get started" + steps now begin with the clang-plus-seed bootstrap, without which `bin/x` does + not exist on a clean checkout. + +### Build + +- `x` no longer prints a clang warning on every build. Each `clang` invocation the + task runner makes now passes `-Wno-override-module`, the same flag `ludicc` + already passes for its own link step: the emitted IR names no target triple, so + clang substitutes the host's and says so — four times per `x build`, with + nothing to act on. (The comment in `selfhost/main.ludic` claimed the opposite, + that the IR *does* carry a triple; it does not.) + +### CI + +- Releases are published by CI from a tag instead of by hand from a laptop. The + new `release` workflow triggers on a `v*` tag, builds the toolchain from the IR + seed, runs `x test`, `x test-tools` and `x bootstrap-cfree` against the tagged + tree, and only then creates the Forgejo release. It refuses to publish when the + tag and `VERSION` disagree or `CHANGELOG.md` has no section for that version. + + `x publish [vX.Y.Z]` is the command behind it and works locally too: it builds + `dist/` (a source tarball from the tag, this host's toolchain, and a + `SHA256SUMS` covering both — releases previously shipped no checksums) and takes + the release notes from that version's `CHANGELOG.md` section, so the notes and + the changelog cannot drift. Re-running it only adds assets the release is + missing, which is how a macOS build gets attached to a Linux-built release. ## v0.3.0 — 2026-09-02 ### Features diff --git a/VERSION b/VERSION index 0d91a54c..1d0ba9ea 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.3.0 +0.4.0 diff --git a/changes/ai-seek-pathing.md b/changes/ai-seek-pathing.md deleted file mode 100644 index dc003c49..00000000 --- a/changes/ai-seek-pathing.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: minor -type: feat -`Ai.seek` + path-aware `brain_seek` — when a Solids tilemap is present the NPC-AI routes a blocked straight line around obstacles with `Grid.a_star`, so foes flow around pillars instead of getting stuck. diff --git a/changes/audio-link-windowed.md b/changes/audio-link-windowed.md deleted file mode 100644 index dc4b9696..00000000 --- a/changes/audio-link-windowed.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: patch -type: fix -A windowed build that reaches the audio runtime indirectly — through the atlas / `Assets.*` preload queue (which feeds `.wav`/`.mp3` into the sound bank) or the engine sprite-render system, without any `Audio.*` call in the game — now links the native audio backend (`audio.ll` + AVFoundation). Previously `ludicc -o` failed at link with undefined `snd_*` symbols for any windowed game declaring a `Sprite` component; the import of `runtime/native/audio.ludic` now flags the backend link itself. diff --git a/changes/become-anywhere.md b/changes/become-anywhere.md deleted file mode 100644 index 65b1f45a..00000000 --- a/changes/become-anywhere.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: patch -type: fix -`become Scene` now works from an `@On(Event)` listener, a global handler, or a plain function (#91). Code outside a scene's own layers cannot know the leaving scene at compile time, so the compiler emits `@L_scene_leave()` — a dispatch on the live scene id that runs its `on exit` — and calls it there. Previously a listener's `become` reused the last emitted handler's scene (or crashed), and a global handler's `become` skipped the leaving scene's `on exit` entirely. diff --git a/changes/bootstrap-mkdir-bin.md b/changes/bootstrap-mkdir-bin.md deleted file mode 100644 index 3e7ec1e8..00000000 --- a/changes/bootstrap-mkdir-bin.md +++ /dev/null @@ -1,7 +0,0 @@ -bump: patch -type: fix -The documented bootstrap works on a fresh clone. `bin/` is gitignored and not -checked in, so `clang selfhost/ludicc.seed.ll -o bin/ludicc` — the first command -in the README, in COMPILING, in CONTRIBUTING and on the site — failed with -`ld: open() failed, errno=2 for 'bin/ludicc'`. Every copy now begins with -`mkdir -p bin`, which is what CI had been doing all along. diff --git a/changes/changelog-structure.md b/changes/changelog-structure.md deleted file mode 100644 index e3f392b6..00000000 --- a/changes/changelog-structure.md +++ /dev/null @@ -1,14 +0,0 @@ -bump: minor -type: fix -`x release` keeps a changeset's markdown intact. Bodies used to go through -`tr '\n' ' '`, which flattened every multi-line changeset into one paragraph — -nested bullets came out as inline `" - "` runs and a release read as a single -unbroken wall of text. A section is now grouped by change type (**Features**, -**Fixes**, **Performance**, …) with one bullet per changeset and continuation -lines indented to stay inside it. - -- `x release --dry-run` renders the next section to stdout and writes nothing, - so a release can be read before it is cut. -- `x changelog-section ` prints one release's section from - `CHANGELOG.md`; `x changelog-render` re-renders a section from a directory of - changesets. The v0.1.0 and v0.3.0 sections were re-rendered with these. diff --git a/changes/char-escapes.md b/changes/char-escapes.md deleted file mode 100644 index d269c1ef..00000000 --- a/changes/char-escapes.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: patch -type: fix -Character literals accept the same escapes as strings (`'\''`, `'\\'` and `'\"'` were silently read as 0); an unterminated character literal is now an error. diff --git a/changes/clearcolor-expr.md b/changes/clearcolor-expr.md deleted file mode 100644 index c237085d..00000000 --- a/changes/clearcolor-expr.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: patch -type: feat -`@ClearColor(expr)` takes any constant expression, so a named palette colour (`@ClearColor(COLOR_FLOOR)`) works as well as a hex literal. diff --git a/changes/component-access.md b/changes/component-access.md deleted file mode 100644 index 38b35cd7..00000000 --- a/changes/component-access.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: minor -type: feat -`Prop.of(entity)` and `Prop.has(entity)` — typed access to one entity's component from an entity handle, the same binding a query loop makes. `Hero.of(player).iframes = 20` reads and writes fields directly (no `World.prop_id` / `World.field_id` / `World.get` reflection chain); `Prop.has(e)` is true when `e` is in range, alive, and carries the property, so `-1` is a safe "no entity". A package that declares a real `prop_of` / `prop_has` function keeps it. diff --git a/changes/compound-assign-types.md b/changes/compound-assign-types.md deleted file mode 100644 index f016c533..00000000 --- a/changes/compound-assign-types.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: patch -type: fix -`x += y` / `-=` / `*=` / `/=` now lower exactly like `x = x op y`: a Q16.16 `fixed` multiplies and divides through the 64-bit path, a string `+=` concatenates, and an `int` added to a `long` widens (they previously emitted raw integer arithmetic on the LLVM type). diff --git a/changes/countdown.md b/changes/countdown.md deleted file mode 100644 index d1498358..00000000 --- a/changes/countdown.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: minor -type: feat -**`countdown` fields.** A component field declared `frames_left: countdown = 0` is an `int` the engine steps toward 0 once per Update for every live entity carrying the component (never below 0). Roll timers, invulnerability frames, hit flashes and cooldowns need no hand-written "decrement each frame" handler: set the field, test it. diff --git a/changes/cursor-confine-mode3.md b/changes/cursor-confine-mode3.md deleted file mode 100644 index 49f9074c..00000000 --- a/changes/cursor-confine-mode3.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: patch -type: fix -cursor `mode 3` (confined) now keeps the OS cursor **associated** (absolute position preserved) and hidden, instead of dissociating it like `mode 2` (lock/relative). Only true-lock `mode 2` uses relative deltas now; `win_mouse` reports the absolute position on `mode 3` and clamps it to the framebuffer. And `mode 3` now **physically confines** the cursor: each frame the platform layer warps it back to the window's content rect (`CGWarpMouseCursorPosition`) whenever it strays past the edge, so clicks can't land outside and the window keeps focus. This lets a top-down game hide + confine the cursor while `aim_mode 0` (mouse aim) keeps resolving to where the reticle points — previously any confine/lock mode silently broke absolute mouse aim, and a confined cursor still escaped the window (#89 follow-up). diff --git a/changes/diagnostics-file-line.md b/changes/diagnostics-file-line.md deleted file mode 100644 index 69aad87b..00000000 --- a/changes/diagnostics-file-line.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: minor -type: feat -Every compiler diagnostic is reported as `file:line: error: message` — the file the line really lives in, even through imports — so editors can jump to it. Unexpected characters are errors (they used to be skipped silently), and defining one `function` twice is reported in source terms instead of failing in the IR assembler. diff --git a/changes/duplicate-global-diagnostic.md b/changes/duplicate-global-diagnostic.md deleted file mode 100644 index dd776d45..00000000 --- a/changes/duplicate-global-diagnostic.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: patch -type: fix -A `var` declared twice — including a game `var` whose name the spliced engine runtime already uses (`ui_font`, `grid`, …) — is now a compile error naming the variable and, when it is the runtime's, saying so (`variable ui_font is also a variable of the engine runtime; choose another name`). Previously the two became one LLVM global and clang reported a redefinition in generated IR. The same check covers `property` names (`property Cell is also a property of the engine runtime; choose another name`); before, the first declaration silently won and field lookups failed with a confusing message. diff --git a/changes/engine-enums.md b/changes/engine-enums.md deleted file mode 100644 index 087ce264..00000000 --- a/changes/engine-enums.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: minor -type: feat -The engine's numeric parameters have names. ludic.core: `BodyPolicy { Platformer, TopDown }`, `BoundsPolicy { Clamp, Wrap, Bounce, Kill }`, `AnimMode { Loop, Once, PingPong }`; ludic.shooter: `AimMode { Mouse, RightStick, MoveDirection, NearestEnemy }`, `WeaponPattern { Single, Cone, Ring, Spiral }`; ludic.npcai: `BrainModel { StateMachine, Utility, BehaviourTree }`, `AiState { Patrol, Chase, Attack, Flee }`; ludic.gameplay: `StatKind { MaxHp, Attack, Defense, Speed }`, `ModifyOp { Flat, Percent }`; ludic.rpg: `StatusKind { Poison, Regen }`; the input runtime: `CursorMode { Normal, Hidden, Locked, Confined }`. `Body { policy: BodyPolicy.TopDown }` reads as what it is; the old integers still work. The input runtime also names the gamepad buttons: `PadButton { A, B, X, Y, LeftShoulder, RightShoulder, Back, Start }` for `Input.bind_pad(button:)`. diff --git a/changes/engine-ui-events.md b/changes/engine-ui-events.md deleted file mode 100644 index cfd7a20d..00000000 --- a/changes/engine-ui-events.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: minor -type: feat -engine-driven retained UI — a program with a `ui` block has its navigation ticked by the frame loop automatically (from the frame key) and an activation now emits a `UiClicked { id }` event, so scenes react with `@On(UiClicked)` instead of polling `Ui.clicked`. New `Ui.*` namespace (`Ui.open/tick/clicked/set_text/render/build`). diff --git a/changes/enum-typed-machines.md b/changes/enum-typed-machines.md deleted file mode 100644 index bf01c09f..00000000 --- a/changes/enum-typed-machines.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: minor -type: feat -A `machine` over an enum-typed store maps its states to the enum by name: with `enum HeroState { Idle, Rolling }` and `var hero_state: HeroState = HeroState.Idle`, `machine hero_state { state Idle { … } state Rolling { … } }` dispatches on `HeroState.Idle` / `HeroState.Rolling` — no `state Idle = HeroState.Idle` repetition, and a state that names no variant is a compile error. A bare enum is now a first-class `int`-sized type for `var`, params, fields and returns (`llty`), and `Enum.Variant` folds in a global initializer. diff --git a/changes/fmt-paren-spacing.md b/changes/fmt-paren-spacing.md deleted file mode 100644 index 280b8598..00000000 --- a/changes/fmt-paren-spacing.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: patch -type: fix -`ludic-fmt` no longer glues an opening parenthesis to a preceding operator: `let moving = (a or b)` stays as written instead of becoming `let moving =(a or b)`. diff --git a/changes/formatter-brackets.md b/changes/formatter-brackets.md deleted file mode 100644 index b3517e43..00000000 --- a/changes/formatter-brackets.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: patch -type: fix -`ludic-fmt` keeps `rows[i]`, `new []int`, `s[a..b]`, `emit(…)`, `~x` and list-literal braces tight, and recognises `<< >> & | ^ ~` as operators. diff --git a/changes/global-initializers.md b/changes/global-initializers.md deleted file mode 100644 index 730ece1e..00000000 --- a/changes/global-initializers.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: minor -type: feat -A program-scope `var` may be initialized with any expression: `var run: Progress = new Progress`, `var speed: int = BASE_SPEED * 2`, `var origin: IVec2 = IVec2.zero()`. Initializers the compiler cannot fold run once at startup (`@L_init_globals`, after the runtime boots and before the `Start` phase), in declaration order. Previously such an initializer was silently replaced by `0` / `null`. diff --git a/changes/half-the-game.md b/changes/half-the-game.md deleted file mode 100644 index 4d8c4e1e..00000000 --- a/changes/half-the-game.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: minor -type: feat -The second "write less" round, all generic. **ludic.gameplay**: `Stats` carries the build stats every action game bolts on — `damage_pct`, `crit_pct`, `leech_pct` / `leech_hp`, `thorns`, `fire_rate_pct` — and `Combat.damage` applies them itself (a `Crit` event fires; thorns never reflect thorns); `Stats.add(e, stat, amount)` changes a base stat in place and `Stats.scale_hp(e, percent)` scales hp and max_hp; `StatKind` names every code. **ludic.shooter**: `Dash { frames, speed, cooldown_frames }` with `Dash.start(e, dx, dy)` / `Dash.active(e)` — a dodge roll with i-frames the package guards; `Melee { range, half_arc, damage, knockback, frames, cooldown_frames, arc }` with `Melee.swing(e)` (hits every hostile in the arc, knocks back, fires `MeleeHit`) / `Melee.ready` / `Melee.active`; projectiles drawn by the engine in their weapon's colour (`Weapon.set_color`); `TopDown { reticle, reticle_length }` draws the aim line and a mouse cross; the weapon system honours `Stats.fire_rate_pct`. **ludic.dungeon** (new package): `Dungeon.arena / open_arena / random_style / set_exit / entry_point / opposite / at_edge`, with `Side` and `RoomStyle` — arena rooms with mirrored cover, door lanes and exits by side, built into the engine tilemap. **Compiler**: `scene Splash lasts N then Next` (a timed scene), `button … goto: Scene` (a click changes scene, no listener to write). **Runtime**: `Map.random_cell_far`, `Sprite.draw_meter` (hearts / pips), `Assets.enqueue_dir`, `Collider.center`, `Prefs.max`. Also `scene X loads then Y` (the loading scene: pumped, drawn, `AssetsReady` fired), `Prop.despawn_all()`, `Map.to_tile`, and `Brain { hunt_blind }` in ludic.npcai (seek the nearest hostile without line of sight). `Prefab.spawn_at(name:, at:)` spawns and places; `Weapon.reset(id)` restores a definition (no pierce, no homing, its fire rate). Five regression examples cover the additions (`examples/library/prefabs`, `component_access`, `scene_menus`, `managers`, `combat_kit`). `Random.weighted(weights:)` draws an index by weight; `ui` widgets inherit `font` / `size` / `fg` / `align` from their panel; the input runtime names `MouseButton { Left, Right, Middle }`. diff --git a/changes/interp-braces.md b/changes/interp-braces.md deleted file mode 100644 index 578e44e1..00000000 --- a/changes/interp-braces.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: patch -type: fix -A `{` or `}` inside a string literal within an interpolation hole (`` `{f("{")}` ``) is text, not structure; the hole scanner used to miscount it. diff --git a/changes/ivec2-members.md b/changes/ivec2-members.md deleted file mode 100644 index 564565b1..00000000 --- a/changes/ivec2-members.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: minor -type: feat -`v.x` and `v.y` read the components of an `IVec2` value (a local, a global, a record field, or a call result) — the readable form of `IVec2.x(v)` / `IVec2.y(v)`. The compiler's static typing now also follows function return types, namespace calls, `Prop.of(e)` and record fields, so `@Computed` fields expand in those positions too. diff --git a/changes/key-constants-namespaces.md b/changes/key-constants-namespaces.md deleted file mode 100644 index cd9cb26e..00000000 --- a/changes/key-constants-namespaces.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: minor -type: feat -`Key.*` compile-time key constants (`Key.Space`, `Key.Escape`, `Key.A`, `Key.Up`, ...), folded like `Color.*`; and `Font.*` / `Ui.* / File.*` namespaces so `png_load`/`font_load`/file I/O are namespaced. diff --git a/changes/layer-queries.md b/changes/layer-queries.md deleted file mode 100644 index 88b97710..00000000 --- a/changes/layer-queries.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: minor -type: feat -A handler inside a scene `layer` may carry `@Queries(these: […], on: Model)`, so a scene can own its per-entity systems (`@Queries(these: [Particle]) handler AgeSparks phase Update { … }` runs once per matching entity only while that scene is active). Any other annotation on a layer handler is reported. diff --git a/changes/less-to-write.md b/changes/less-to-write.md deleted file mode 100644 index 8467a809..00000000 --- a/changes/less-to-write.md +++ /dev/null @@ -1,4 +0,0 @@ -bump: minor -type: feat -Less to write for a game. **`Map` cell API** — `Map.get/set/fill/rect/border/random_cell/is_solid/is_solid_at/width/height`: a game edits the engine's tilemap in place and asks it what is solid (from the `Solids` config) instead of keeping its own grid. **`Sprite` does the small animation work**: `move_id` is the strip drawn while the entity moves, `face: 1` turns it toward its movement, and the `flash` / `blink` countdowns give a white hit flash and an invulnerability blink with no handler. **`scene X shows Menu`** — the engine opens the menu (and frees the cursor) on enter, draws it last in Overlay, and closes it on exit. **`import "dir/*.ludic"`** imports a directory in name order. **`IVec2.distance2/within/heading/along/step`** and **`Angle.diff_degrees`** cover the geometry every action game rewrites; **`List.sample`** draws distinct random picks; **`Input.move_i`** is the standard top-down movement intent; **`AimMode.Auto`** aims with the mouse, or the right stick while a pad is connected; **`Weapon.set_rate` / `Weapon.rate`** change a fire rate in place; projectiles now die on `Solids` tiles by themselves; **`Screen.bar`** draws a meter. -Handlers inside a scene's layers are scene-qualified (`Play_Draw`), so two scenes may both name a handler `Draw`; `enable` / `disable` of a scene's own handler by its bare name still works from inside that scene. diff --git a/changes/list-literals.md b/changes/list-literals.md deleted file mode 100644 index f6c52f81..00000000 --- a/changes/list-literals.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: minor -type: feat -`[a, b, c]` list literals build a slice in place; the first element fixes the element type, later elements must match, and `[]` is an error (use `new []T`). Tables of records read as `[Row { … }, Row { … }]`. diff --git a/changes/managers.md b/changes/managers.md deleted file mode 100644 index eff1e320..00000000 --- a/changes/managers.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: minor -type: feat -Engine managers for what every action game hand-rolls: **`Fx.sparks` / `Fx.number` / `Fx.clear`** — engine-owned sparks and floating damage numbers, moved and aged each Update and drawn after the sprites, with no component, model, handler or draw call in the game; **`Audio.define(name:, path:)` + `Audio.play(name:)` / `Audio.play_music(name:)` / `Audio.named`** — a sound bank by name (the handle form still works); **`Camera.shake_for(amount:, frames:)`** — a timed shake the engine decays; **`Assets.enqueue`** now loads `.wav` / `.mp3` into the sound bank and `.ttf` / `.ttc` into a font table (**`Assets.font(name:)`**) alongside images, so one loading scene covers everything; **`Prop.count()`** — how many live entities carry a component. diff --git a/changes/namespace-named-args.md b/changes/namespace-named-args.md deleted file mode 100644 index acdd6543..00000000 --- a/changes/namespace-named-args.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: patch -type: fix -Named arguments now work on namespace functions (`@Namespace(Foo)` and `namespace Foo { export function … }`), not only on builtins and bare functions: `Weapon.def(name: "pistol", fire_rate: 9, damage: 14, speed: 8, spread: 0, pellets: 1, pattern: 0)` reorders to the declared parameter order like any other call. Previously every named call on a namespace function failed with "wrong number of arguments". diff --git a/changes/os-pid.md b/changes/os-pid.md deleted file mode 100644 index 1c0fa425..00000000 --- a/changes/os-pid.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: minor -type: feat -`Os.pid()` returns the process id, for scratch files that concurrent runs of one tool must not share. diff --git a/changes/overlay-phase.md b/changes/overlay-phase.md deleted file mode 100644 index 2ebea552..00000000 --- a/changes/overlay-phase.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: minor -type: feat -`Overlay` render phase — runs after the engine Render systems (sprites, lights) and before present, so a game's HUD / menus are never painted under an actor. Byte-identical when unused. diff --git a/changes/prefabs.md b/changes/prefabs.md deleted file mode 100644 index 6dd518dc..00000000 --- a/changes/prefabs.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: minor -type: feat -**Prefabs.** `prefab Grunt: Creature { Stats { hp: 30 }, Weapon { def_id: 1 } }` names a model with preset fields; `spawn Grunt { Position { x: 40 } }` spawns it, the spawn's own fields winning over the presets. Prefabs chain (`prefab Grunt: Foe` where `Foe` is a prefab) so shared presets live once. `spawn` is now also an expression yielding the new entity (`let e = spawn Grunt { … }`), and `Prefab.spawn(name: "Grunt")` spawns one chosen at runtime by name (-1 when none matches). diff --git a/changes/prefs-package.md b/changes/prefs-package.md deleted file mode 100644 index 4ce0b4b2..00000000 --- a/changes/prefs-package.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: minor -type: feat -`ludic.prefs` package — `Prefs.*`, a human-readable `key=value` text store for scores / options (the right tool for "remember my best run"; a whole-world `Save.write` is not). diff --git a/changes/prelude-symbol-prefix.md b/changes/prelude-symbol-prefix.md deleted file mode 100644 index 859d4069..00000000 --- a/changes/prelude-symbol-prefix.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: patch -type: fix -Hand-written runtime preludes (string, Os.*, Fs.*, Crypto.*, …) now live under their own `@lp_` symbol prefix, so a user `function` named `is_ws`, `str_eq`, `path_join` and the like no longer collides with them at link time. diff --git a/changes/quiet-builds.md b/changes/quiet-builds.md deleted file mode 100644 index fc723fbc..00000000 --- a/changes/quiet-builds.md +++ /dev/null @@ -1,8 +0,0 @@ -bump: patch -type: build -`x` no longer prints a clang warning on every build. Each `clang` invocation the -task runner makes now passes `-Wno-override-module`, the same flag `ludicc` -already passes for its own link step: the emitted IR names no target triple, so -clang substitutes the host's and says so — four times per `x build`, with -nothing to act on. (The comment in `selfhost/main.ludic` claimed the opposite, -that the IR *does* carry a triple; it does not.) diff --git a/changes/release-ci.md b/changes/release-ci.md deleted file mode 100644 index 93e3d7c1..00000000 --- a/changes/release-ci.md +++ /dev/null @@ -1,14 +0,0 @@ -bump: minor -type: ci -Releases are published by CI from a tag instead of by hand from a laptop. The -new `release` workflow triggers on a `v*` tag, builds the toolchain from the IR -seed, runs `x test`, `x test-tools` and `x bootstrap-cfree` against the tagged -tree, and only then creates the Forgejo release. It refuses to publish when the -tag and `VERSION` disagree or `CHANGELOG.md` has no section for that version. - -`x publish [vX.Y.Z]` is the command behind it and works locally too: it builds -`dist/` (a source tarball from the tag, this host's toolchain, and a -`SHA256SUMS` covering both — releases previously shipped no checksums) and takes -the release notes from that version's `CHANGELOG.md` section, so the notes and -the changelog cannot drift. Re-running it only adds assets the release is -missing, which is how a macOS build gets attached to a Linux-built release. diff --git a/changes/reserved-word-functions.md b/changes/reserved-word-functions.md deleted file mode 100644 index 76b7271f..00000000 --- a/changes/reserved-word-functions.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: patch -type: fix -reserved words (`new`, `match`, `spawn`, ...) can no longer name a function — the compiler errors instead of miscompiling. diff --git a/changes/self-in-spawn-hooks.md b/changes/self-in-spawn-hooks.md deleted file mode 100644 index acc20ec2..00000000 --- a/changes/self-in-spawn-hooks.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: patch -type: fix -`self()` inside an `@OnSpawn(Model)` or `@OnAttach(Property)` body is now the entity being constructed. Previously it was the entity of the innermost query loop — or the constant 0 when the spawn happened outside any loop — so a hook such as `@OnSpawn(Hero) handler Remember { player = self() }` silently recorded entity 0. diff --git a/changes/shooter-aim-centre.md b/changes/shooter-aim-centre.md deleted file mode 100644 index e063d422..00000000 --- a/changes/shooter-aim-centre.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: patch -type: fix -the shooter aims / homes / fires from a body's **centre** (Position + Collider offset + half-size) instead of the Position anchor, so auto-aim and homing target what is drawn, not a corner. diff --git a/changes/site-redesign.md b/changes/site-redesign.md deleted file mode 100644 index 774babdb..00000000 --- a/changes/site-redesign.md +++ /dev/null @@ -1,22 +0,0 @@ -bump: minor -type: docs -The generated site is redesigned around reading rather than launching: a warm -paper ground with a serif display face, one ink-blue accent, and rules instead -of floating cards. Colour is reserved for code. Dark mode is the same design -with the ground inverted, driven entirely by tokens under one -`prefers-color-scheme` block, and the landing page's scroll-reveal animations, -gradient headline, glowing badge and emoji feature icons are gone. - -- Stylesheets are linked files (`base.css` + `site.css`/`docs.css`) instead of - being inlined into all 900+ pages, which cuts the published site from 16 MB to - 5 MB and means a design change no longer requires regenerating to be seen. -- Fonts are the platform's own; the site makes no webfont request. -- `api.css` was dead — the generator never referenced it — and is removed along - with `item.css`, which `docs.css` replaces. -- A page no longer flashes its own title on every plain visit; only a deep link - highlights its target, and under `prefers-reduced-motion` the highlight no - longer stays on the element permanently. -- The copy leads with what is verifiable — ahead-of-time compiled, an ECS in the - syntax, deterministic fixed-point, no C in a build — and the "get started" - steps now begin with the clang-plus-seed bootstrap, without which `bin/x` does - not exist on a clean checkout. diff --git a/changes/solids-solid2.md b/changes/solids-solid2.md deleted file mode 100644 index 25fc619c..00000000 --- a/changes/solids-solid2.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: minor -type: feat -`Solids.solid2` — an optional second solid glyph (e.g. a closed door) the move system also blocks. diff --git a/changes/sprite-atlas-ids.md b/changes/sprite-atlas-ids.md deleted file mode 100644 index 57a5ad4f..00000000 --- a/changes/sprite-atlas-ids.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: minor -type: feat -`Sprite` component draws atlas ids (#90) — `Sprite.atlas = 1` routes `esys_sprite` through `atlas_draw_ex` (scale/flip/tint) so atlas cells / multi-cell spans (tall characters) use the engine sprite-render system, not just the 16x16 table. diff --git a/changes/sprite-strip.md b/changes/sprite-strip.md deleted file mode 100644 index 611b1234..00000000 --- a/changes/sprite-strip.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: minor -type: feat -`Sprite.strip(sheet, col, row, count, rows)` — register N consecutive animation frames in one call (the base id for `SpriteAnim`). diff --git a/changes/tileskin-system.md b/changes/tileskin-system.md deleted file mode 100644 index cccc3584..00000000 --- a/changes/tileskin-system.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: minor -type: feat -engine tilemap-render system (`TileSkin`, shipped from ludic.core) — one entity per glyph paints the whole `Map.*` grid each Render frame *before* sprites, so a game stops hand-looping the map. `esys_tileskin` registered ahead of `esys_sprite`. diff --git a/changes/ui-close.md b/changes/ui-close.md deleted file mode 100644 index ac4157be..00000000 --- a/changes/ui-close.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: patch -type: feat -`Ui.close()` — deactivate the retained UI (no menu open); the readable form of `Ui.open(id: -1)`. diff --git a/changes/ui-ids-anywhere.md b/changes/ui-ids-anywhere.md deleted file mode 100644 index 86001290..00000000 --- a/changes/ui-ids-anywhere.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: patch -type: fix -A `UI_Name` handle can be read from any code — a plain function, an `@On(UiClicked)` listener, a global initializer — not only from handlers and scene hooks. The widget table it indexes is now built on first use instead of when `@ui_build` is emitted, which came after functions and listeners and crashed the compiler on such a reference. diff --git a/changes/unary-minus-fixed.md b/changes/unary-minus-fixed.md deleted file mode 100644 index 7473c543..00000000 --- a/changes/unary-minus-fixed.md +++ /dev/null @@ -1,3 +0,0 @@ -bump: patch -type: fix -Unary minus keeps its operand type: `-f` on a `fixed` is a `fixed` (it was typed `int`, which broke mixed arithmetic and comparisons).