Compare commits
No commits in common. "main" and "v0.19.0" have entirely different histories.
2063 changed files with 185667 additions and 694834 deletions
1
.gitattributes
vendored
1
.gitattributes
vendored
|
|
@ -1 +0,0 @@
|
||||||
packages/*/lib/** filter=lfs diff=lfs merge=lfs -text
|
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -55,8 +55,3 @@ __pycache__/
|
||||||
assets/polyhaven/hdri/
|
assets/polyhaven/hdri/
|
||||||
assets/polyhaven/textures/
|
assets/polyhaven/textures/
|
||||||
assets/polyhaven/models/
|
assets/polyhaven/models/
|
||||||
# `ludic run` beside an example writes its binary into a build/ there
|
|
||||||
examples/**/build/
|
|
||||||
|
|
||||||
# a package native/build.sh writes its objects under the package (phase 15)
|
|
||||||
packages/*/build/
|
|
||||||
|
|
|
||||||
78
CHANGELOG.md
78
CHANGELOG.md
|
|
@ -7,84 +7,6 @@ change type. Preview the next one with `ludic dev release --dry-run`.
|
||||||
A released section may carry a hand-written summary paragraph above its groups
|
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.2.0 has one); the generated bullets below it are not edited by hand.
|
||||||
|
|
||||||
## v0.22.0 — 2026-09-18
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **Every key on the keyboard is bindable** — the F-row, Home/End/PageUp/PageDown,
|
|
||||||
Insert/Delete, Caps Lock and the numpad reach a game as codes 132-166.
|
|
||||||
|
|
||||||
Until now the platform gave them no code at all, so a game's rebinding screen could
|
|
||||||
not take one and nothing said why. Windows asked the active layout what they type and
|
|
||||||
got nothing back (`w_vk_char` answers 0 for a key with no character); macOS let them
|
|
||||||
fall through to `charactersIgnoringModifiers`, which reports `NSF1FunctionKey` and its
|
|
||||||
neighbours at 0xF704 and up - outside the 256-bit held set either way.
|
|
||||||
|
|
||||||
- **The codes are the same on both platforms**: 132-143 are F1-F12, 144-149 are Home,
|
|
||||||
End, PageUp, PageDown, Insert and Delete, 150 is Caps Lock, 152-161 are the numpad
|
|
||||||
digits and 162-166 its `*`, `+`, `-`, `.` and `/`. The numpad's Enter is Enter.
|
|
||||||
- **`Key.F1`, `Key.Home`, `Key.Numpad0`** and the rest fold at compile time like the
|
|
||||||
other named keys.
|
|
||||||
- **`Input.key_label`** names them - "F1", "Home", "CapsLock", "Num 0", "Num /" - and
|
|
||||||
does *not* ask the layout, because a key that types nothing is called the same thing
|
|
||||||
on every layout.
|
|
||||||
## v0.21.1 — 2026-09-17
|
|
||||||
|
|
||||||
### Fixes
|
|
||||||
|
|
||||||
- A windowed game opens under its package's `app name`. `ludic build`, `ludic run` and
|
|
||||||
`ludic bundle` pass it to the compiler (`ludicc --title <name>`), so `game_title()` - the
|
|
||||||
window's first title - is the name the player knows rather than the `program` name, which
|
|
||||||
showed for up to two seconds before the renderer retitled the window. Without an `app name`
|
|
||||||
it is the `program` name, as before.
|
|
||||||
- `Crypto.random_bytes`, `Crypto.random_hex`, `Crypto.random_u32` and `Uuid.*` draw from the
|
|
||||||
system CSPRNG on Windows (`RtlGenRandom`, advapi32). They read `/dev/urandom`, which Windows
|
|
||||||
does not have, and every byte came back zero.
|
|
||||||
## v0.21.0 — 2026-09-17
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **A launcher's runtime** — start a program, step aside while it runs, and download large
|
|
||||||
files with a progress bar.
|
|
||||||
|
|
||||||
- **`Process.*`** — `Process.spawn(path, args)` starts a program directly (no shell) with this
|
|
||||||
process's environment and working directory and returns at once; `Process.poll(h)` is `-1`
|
|
||||||
while it runs, then its exit code (`128 + signal` for a signal death, `137` after
|
|
||||||
`Process.kill`); `Process.free` lets a handle go. `posix_spawn` on macOS
|
|
||||||
(`runtime/native/process.ll`); `CreateProcessW` on Windows (`process_win.ll`), with each
|
|
||||||
argument quoted by the MSVC rules and no console window. Linked only into a program that
|
|
||||||
uses `Process.*`.
|
|
||||||
- **`Http.save_to(h, path)`** — before `Http.send`, streams the response body straight into a
|
|
||||||
file (written at `path` itself, created on send) instead of memory; `Http.text` of such a
|
|
||||||
request is empty and `Http.body_len` is the bytes written. **`Http.received(h)`** and
|
|
||||||
**`Http.expected(h)`** (the `Content-Length`, or `-1`) are live while it is pending. macOS
|
|
||||||
streams through an `NSURLSession` with a delegate built at run time, Windows through the
|
|
||||||
WinHTTP read loop. Freeing a pending request now cancels it and parks its slot until the
|
|
||||||
worker has let go, rather than freeing what the worker is still writing.
|
|
||||||
- **`App.window_hide()` / `App.window_show()`** — take the game's window off the screen and
|
|
||||||
bring it back without closing it or its GL / Vulkan surface; the run goes on while it is
|
|
||||||
hidden. No-ops headless and before there is a window.
|
|
||||||
|
|
||||||
### Fixes
|
|
||||||
|
|
||||||
- A game's window takes the title it asks for. The runtime opens a game's window before `main`,
|
|
||||||
titled with the `program` name, and `Gl.open` / render3d's `gl_open` and `gvk_open` attached
|
|
||||||
to it without passing their title on, so a `program Valley` that opened "Maroon Lake" showed
|
|
||||||
"Valley". `win_set_title` (cocoa.ll `setTitle:`, win32.ll `SetWindowTextW`, UTF-8 to UTF-16)
|
|
||||||
now retitles it, and a second `win_open` on macOS retitles the open window instead of making
|
|
||||||
another, as it already did on Windows.
|
|
||||||
## v0.20.0 — 2026-09-17
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- The JetBrains plugin (1.6.0) has a Ludic tool window for the package, like the Gradle one:
|
|
||||||
- an overview of the package (what it is, entry, version, dependencies, scripts, hooks, app);
|
|
||||||
- its commands, scripts and hooks, run by double-click, with each command's hooks shown;
|
|
||||||
- its dependencies against package.lock.ludic: the locked version, whether it is fetched
|
|
||||||
and linked, the indirect ones, and Fetch, Update, Verify, Vendor, Add and Remove;
|
|
||||||
- the app preview the `app` lines describe, and the asset roots with missing ones marked.
|
|
||||||
The bar over package.ludic now appears only when something needs doing (fetch, or no
|
|
||||||
toolchain), instead of carrying every command.
|
|
||||||
## v0.19.0 — 2026-09-17
|
## v0.19.0 — 2026-09-17
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@
|
||||||
> ```bash
|
> ```bash
|
||||||
> # one-time bootstrap: clang assembles the seed, then ludicc compiles bin/ludic
|
> # one-time bootstrap: clang assembles the seed, then ludicc compiles bin/ludic
|
||||||
> mkdir -p bin && clang selfhost/ludicc.seed.ll -o bin/ludicc
|
> mkdir -p bin && clang selfhost/ludicc.seed.ll -o bin/ludicc
|
||||||
> bin/ludicc --unsafe --globals tools/ludic-cli/dev.ludic -o bin/ludic-dev
|
> bin/ludicc tools/ludic-cli/dev.ludic -o bin/ludic-dev
|
||||||
> bin/ludic-dev build # the whole toolchain into bin/
|
> bin/ludic-dev build # the whole toolchain into bin/
|
||||||
> # (ludicc, ludic, ludic-fmt, ludic-lsp)
|
> # (ludicc, ludic, ludic-fmt, ludic-lsp)
|
||||||
> bin/ludicc examples/games/snake.ludic -o bin/snake # the compiler, directly
|
> bin/ludicc examples/games/snake.ludic -o bin/snake # the compiler, directly
|
||||||
|
|
@ -73,9 +73,6 @@ point at a different LLVM toolchain if you have one.
|
||||||
| a windowed native executable | `ludicc game.ludic -o build/game` |
|
| a windowed native executable | `ludicc game.ludic -o build/game` |
|
||||||
| a headless executable | `ludicc game.ludic --headless -o build/game` |
|
| a headless executable | `ludicc game.ludic --headless -o build/game` |
|
||||||
| the IR, to read | `ludicc src.ludic --emit-llvm -o src.ll` |
|
| the IR, to read | `ludicc src.ludic --emit-llvm -o src.ll` |
|
||||||
| the schema an editor reads (records, registries and their entries, consts) | `ludicc src.ludic --emit-schema schema.json` |
|
|
||||||
| every error, as a JSON array on stdout | `ludicc src.ludic --check --diagnostics=json` |
|
|
||||||
| the same, with an unsaved buffer on stdin standing for one of its files | `ludicc src.ludic --check --diagnostics=json --stdin-file lib/a.ludic < buf` |
|
|
||||||
| a shared library † | `ludicc lib.ludic --shared -o build/liblib.dylib` |
|
| a shared library † | `ludicc lib.ludic --shared -o build/liblib.dylib` |
|
||||||
| a game that runs in a browser † | `ludicc game.ludic --target wasm32-unknown-unknown -o build/web/game.wasm` |
|
| a game that runs in a browser † | `ludicc game.ludic --target wasm32-unknown-unknown -o build/web/game.wasm` |
|
||||||
| an object file † | `ludicc src.ludic -c -o src.o` |
|
| an object file † | `ludicc src.ludic -c -o src.o` |
|
||||||
|
|
@ -353,7 +350,6 @@ The self-hosted `ludicc`/`ludic` (built with `bin/ludic-dev build-cli`) accept:
|
||||||
--windowed force a windowed (Cocoa) build
|
--windowed force a windowed (Cocoa) build
|
||||||
--headless force a headless build (stdin input, out.ppm output)
|
--headless force a headless build (stdin input, out.ppm output)
|
||||||
--emit-llvm stop at LLVM IR — write it and exit, no clang
|
--emit-llvm stop at LLVM IR — write it and exit, no clang
|
||||||
--check every check a build makes (types, modules, uses, layers, ports, binds); write nothing
|
|
||||||
--fmt lex + parse only; exit 0 if it parses, 1 on a parse error
|
--fmt lex + parse only; exit 0 if it parses, 1 on a parse error
|
||||||
(the check-docs gate; canonical formatting not yet restored)
|
(the check-docs gate; canonical formatting not yet restored)
|
||||||
--save-temps keep the intermediate .ll
|
--save-temps keep the intermediate .ll
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ From a clean checkout, one line lifts the toolchain off the seed:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mkdir -p bin && clang selfhost/ludicc.seed.ll -o bin/ludicc
|
mkdir -p bin && clang selfhost/ludicc.seed.ll -o bin/ludicc
|
||||||
bin/ludicc --unsafe --globals tools/ludic-cli/dev.ludic -o bin/ludic-dev
|
bin/ludicc tools/ludic-cli/dev.ludic -o bin/ludic-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
That gives you `bin/ludic-dev`, the contributor tool: it replaces every
|
That gives you `bin/ludic-dev`, the contributor tool: it replaces every
|
||||||
|
|
|
||||||
1604
LANGUAGE.md
1604
LANGUAGE.md
File diff suppressed because it is too large
Load diff
|
|
@ -144,7 +144,6 @@ cached in a content-addressed store:
|
||||||
```bash
|
```bash
|
||||||
ludic add git.workshopsoft.io/user/pkg # resolve, fetch, link into ludic_modules/
|
ludic add git.workshopsoft.io/user/pkg # resolve, fetch, link into ludic_modules/
|
||||||
ludic get # install from package.ludic, write the lock
|
ludic get # install from package.ludic, write the lock
|
||||||
ludic remove git.workshopsoft.io/user/pkg # the inverse of add
|
|
||||||
ludic verify # check locked packages against the store
|
ludic verify # check locked packages against the store
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
0.22.0
|
0.19.0
|
||||||
|
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
bump: patch
|
|
||||||
type: fix
|
|
||||||
`Actor.cast_hidden` separates being DRAWN from CASTING. `ac_visible` rejected any hidden
|
|
||||||
actor from the shadow pass as well as the scene pass, so a game that hides the player's
|
|
||||||
own body - first person, or a viewfinder held to the eye - lost that player's shadow
|
|
||||||
entirely. An actor hidden because the camera is inside its head is still standing in the
|
|
||||||
sun; set this on it and it keeps its shadow. Everything else still stops casting when it
|
|
||||||
is hidden.
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
bump: patch
|
|
||||||
type: fix
|
|
||||||
A leaf is not matte.
|
|
||||||
|
|
||||||
Foliage roughness was pinned to 1.0 and grazing Fresnel switched off, so nothing green in
|
|
||||||
the game had a highlight anywhere: the glint off waxy leaves and wet needles, which is
|
|
||||||
most of what makes a real stand look alive rather than painted, was simply absent.
|
|
||||||
|
|
||||||
The reason it was switched off is real. A crown is card quads, and at a grazing angle the
|
|
||||||
card's normal is a lie, so a plain specular lobe frosted whole crowns white against the
|
|
||||||
sky. So the sheen comes back as its own term gated on exactly that: it fades out as the
|
|
||||||
card turns edge-on, which is where its normal stops meaning anything. A tight lobe for the
|
|
||||||
glint, a weak wide one for the waxy rim, and nothing at all at the angles that frosted.
|
|
||||||
|
|
||||||
Thin-leaf translucency reaches further with it - a backlit stand glows for as far as you
|
|
||||||
can see it, not 140 m - and a dense crown passes 0.45 of it rather than 0.3. The distance
|
|
||||||
cap that stops a two-pixel clump card becoming a lime disc stays.
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feat
|
|
||||||
Anti-aliasing that exists, and a lens for the viewfinder.
|
|
||||||
|
|
||||||
THE SHIPPING DEFAULT HAD NO ANTI-ALIASING AT ALL. The temporal resolve was removed (for
|
|
||||||
good reasons - it reprojected water through the surface plane and dragged the mirror image
|
|
||||||
behind the camera), the setting's first option went on saying "Temporal", and MSAA defaults
|
|
||||||
to one sample. Every machine without DLSS - which is every Mac - drew a frame full of grass
|
|
||||||
blades and needle cards with nothing smoothing a single edge.
|
|
||||||
|
|
||||||
FXAA now, in the sharpen pass, because that pass already reads this pixel's neighbourhood
|
|
||||||
and runs last on the LDR image. It has no history, so it cannot drag or smear a reflection.
|
|
||||||
Measured on edge pixels: 25.5% less single-pixel staircase.
|
|
||||||
|
|
||||||
One trap worth recording. The unsharp mask's delta is computed from the RAW image and only
|
|
||||||
then applied to the anti-aliased colour. Taking the centre from the FXAA result and the
|
|
||||||
neighbours from the raw texture measures a difference that is half smoothing and half
|
|
||||||
signal, so the mask sharpens exactly the edges FXAA just softened - measured, that first
|
|
||||||
version was 29% WORSE than no anti-aliasing at all.
|
|
||||||
|
|
||||||
And depth of field, for the photo mode: a disc of taps whose radius is the pixel's circle
|
|
||||||
of confusion, signed so the two sides of the focal plane differ and normalised by the focus
|
|
||||||
distance, because a lens focused at two metres throws a background out far harder than one
|
|
||||||
focused at two hundred. A tap only counts if it is at least as out of focus as the pixel
|
|
||||||
it is blurring into, which is what keeps a sharp foreground from haloing into a blurred
|
|
||||||
background. It runs between the scene and the bloom so a blurred highlight still blooms,
|
|
||||||
and the whole pass is skipped when the aperture is shut - in ordinary play there is no lens
|
|
||||||
and this never draws.
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feature
|
|
||||||
**Actions and reducers.** `action PickUp { item: int }` is a typed record of something that
|
|
||||||
happened; `reducer Bag on PickUp(b: mut Bag, a: PickUp) { ... }`, in the module that owns the state,
|
|
||||||
says what it means for that one state - a reducer takes exactly its state and the action, and a
|
|
||||||
second state is refused; `dispatch PickUp { item: 7 }` queues one from anywhere. The queue is drained
|
|
||||||
at the end of every phase of the frame loop, after every phase of ludic.base's `core_tick_all`, and
|
|
||||||
where a program calls `drain_actions()`: in dispatch order, each action's reducers in the order of
|
|
||||||
their states' names, an action a reducer dispatches queued behind (a queue still growing after 64
|
|
||||||
rounds stops the program, naming the action). `ludic deps` reports `widest_function` - the most
|
|
||||||
states any function or entry point of the program takes - and `--check` ratchets it.
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feat
|
|
||||||
An aspen leaf hangs on a flattened stalk and turns in air a spruce never feels, and the
|
|
||||||
kit had no way to say so. `layer_flutter(l, v)` gives a scatter layer a per-leaf tremble:
|
|
||||||
the vertex stage offsets each leaf by a phase taken from its own place on the card, so
|
|
||||||
neighbouring leaves are never in step, and writes the result out as a varying the
|
|
||||||
fragment stage uses to flash the leaf's pale underside as it turns. The flash is the part
|
|
||||||
that reads - a still frame of a tremble is a still frame of nothing. One uniform, one
|
|
||||||
varying, no extra pass, and every other layer leaves it at zero.
|
|
||||||
|
|
||||||
Also fixes the sway itself, which was measured in METRES: `hgt * hgt * 0.35` is right for
|
|
||||||
a 40 cm flower and puts ten metres of sideways into a 14 m trunk, so every tall tree in
|
|
||||||
the valley stood bent over like a fishing rod. It is a fraction of the model's own height
|
|
||||||
now, so the tip moves a few per cent of the tree whatever the tree is and the base does
|
|
||||||
not move at all.
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feature
|
|
||||||
**`ludic.anim` carries ozz-animation (0.17.0, MIT) as a native library**, the second package to do
|
|
||||||
so after `ludic.physics`. Its skeleton and each clip are built at LOAD from the numbers the package
|
|
||||||
already reads - a skin's parents and rest pose, a clip's flattened channels - so there is no bake
|
|
||||||
step and no new file. `anim_oz_skel(sk)`, `anim_oz_clip(c)`, `anim_oz_ctx(s)` and
|
|
||||||
`anim_oz_sample(ctx, clip, t, rot, pos, n)` are the first step (Maroon Lake's phase 19.1): a sampled
|
|
||||||
rotation agrees with `anim_mix` to within 1e-4 a component. `anim_play` is unchanged. The library is
|
|
||||||
built by `native/build.sh` from the pinned release, and on Windows it imports KERNEL32 alone.
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feature
|
|
||||||
**`@Asset(kind, map)`: a path under each map's directory.** A field whose file lives in the map's own folder
|
|
||||||
(a grass kind's density picture, `ground/blades.png`) says so, and `ludicc --check` looks for it in every
|
|
||||||
map - a @PerMap row's in its map, a game-wide row's in all of them - refusing a map that lacks it unless the
|
|
||||||
field is `@Asset(kind, map, optional)`. The schema marks the attribute `"scope": "map"`.
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
bump: patch
|
|
||||||
type: fix
|
|
||||||
**An attribute before `export` is kept.** `@ToClients export event E`, `@Sync export property P`,
|
|
||||||
`@Owned export model M` and the rest lost the attributes written in front of `export`: the parser read
|
|
||||||
them, then parsed the declaration afresh and forgot them - so an exported remote event was silently
|
|
||||||
local; and `export @ToClients event` was refused outright. Attributes and `export` now read in
|
|
||||||
either order into the same declaration.
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feature
|
|
||||||
**A bake's inputs can follow the data.** `bake_expand(inputs, map)` gives a Bakes row's inputs as they are
|
|
||||||
hashed: `{map}` put as the map's key, and each input with a `*` put as the paths it matches, sorted as whole
|
|
||||||
paths byte by byte, dot-names left out, a glob matching nothing gone. `bake_maps(first_input)` is the maps a
|
|
||||||
`{map}` row covers: each directory under assets/maps holding its first input (`bake_maps_in` under another
|
|
||||||
root). A runner hashes `bake_inputs_hash(bake_expand(row.inputs, map))`, the same path the check takes, so
|
|
||||||
the two cannot disagree on stale; a game's Python tools are its checked twin.
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feature
|
|
||||||
**Bakes you can look at, and three more of the renderer's textures read from one.** `ludic.lab` writes
|
|
||||||
raw 8-bit pixels (1 to 4 channels) as a PNG (`lab_png_write`, `png_write.ludic`, importable alone with its
|
|
||||||
own `LabPngState`) and turns float textures into honest previews (`png_convert.ludic`: R32F min..max as
|
|
||||||
grey, RG16F as red and green x255, HDR RGBA16F as x/(1+x) then sRGB). `ludic.render3d` takes three bakes
|
|
||||||
the game names (`bake_load.ludic`) and makes each as before when one is missing or stale: an impostor's
|
|
||||||
atlases as BC7 with their baked mips, through the compressed upload (`impostor_from_baked`,
|
|
||||||
`impostor_fill_bc7`; a fog opening past its cards reads the bake again instead of painting), the sky's image-based light at the start yaw per prefilter width (`sky_baked_in`; any other
|
|
||||||
turn of the sky is convolved), and the grass carpet (`carpet_from_baked`, `carpet_bytes`).
|
|
||||||
`impostor_from_bytes` returns null, keeping nothing, when the bytes are not the impostor's shape.
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feature
|
|
||||||
**Textures a game baked at build time are read before the PNG.** `png_decode` first takes
|
|
||||||
`assets/baked/png/<path>.tex` (a game's `ludic bake` output: the samples, ready to upload), and a cut-out
|
|
||||||
load (`tex_load_ex` with dilate) first takes `assets/baked/cutouts/<path>.bc7` (padded as `tex_dilate`
|
|
||||||
pads, then BC7 with its mips) - so a boot decodes, pads and converts nothing it can take ready-made. Both
|
|
||||||
are ludic.base's baked form, read by hand (baked_tex.ludic: the "LBAK" header, the key and version); a
|
|
||||||
missing or stale one falls back to the PNG as before. `tex_load_dds_at` reads a .dds at an offset.
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feature
|
|
||||||
**`bind Purse { money: g_money }` - a port member bound to a variable.** A member that takes nothing
|
|
||||||
may name a global instead of a function; the compiler writes the getter in the bind's file, so the
|
|
||||||
one-line wrapper is gone. A member that takes something is refused a variable.
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
bump: patch
|
|
||||||
type: fix
|
|
||||||
**`ludic build` keeps its LLVM IR out of the project.** The intermediate `.ll` was written beside
|
|
||||||
the binary (`build/<name>.ll`) and deleted after linking, so a project's tree held one for the
|
|
||||||
length of every build, and two builds at once deleted each other's - which surfaced as a
|
|
||||||
`clang: no such file` that read exactly like a compile error. It now goes to the run's own
|
|
||||||
temporary directory and goes with it. `--save-temps` still keeps it at `build/<name>.ll`.
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
bump: patch
|
|
||||||
type: fix
|
|
||||||
**A function named like a built-in a call always takes is refused.** `function words(st, k)` compiled,
|
|
||||||
and every call to it became the built-in `words(n)` - n zeroed ints, with a pointer for n - and LLVM
|
|
||||||
refused the IR far from the cause. A top-level function whose name a call always takes as the
|
|
||||||
compiler's own (`words`, `keep`, `print`, `save`, `load`, `key`, ...; the table is
|
|
||||||
`selfhost/check/check_builtins.ludic`, held to `emit_call` by `ludic-dev syntax --check`) is now an
|
|
||||||
error at its declaration, as `run` already was. Every other built-in (`buffer`, `floats`, `double`,
|
|
||||||
...) yields to a function the program declares, in the checker as it already did in codegen.
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feature
|
|
||||||
**`ludic build --check` / `ludicc --check`: check without building.** The parse, the type checker
|
|
||||||
and the module rules (`export`, `uses`, layers, ports, registries) run, and nothing is emitted or
|
|
||||||
linked - about three seconds on Maroon Lake where a build takes about a minute. In this mode the
|
|
||||||
checker asks the module rules at each reference it resolves, since the emitter that usually asks
|
|
||||||
them does not run.
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feat
|
|
||||||
**Check an unsaved buffer.** `ludic build --check --diagnostics=json --stdin-file <path>` (and
|
|
||||||
`ludicc --check --stdin-file <path>`) checks the program as usual, but wherever the compiler would open
|
|
||||||
`<path>` - the entry, an import reached through a barrel, a component's `.xml` / `.lss`, an `.lres` -
|
|
||||||
it reads the text on stdin instead, so an editor's diagnostics follow typing without a save. Paths are
|
|
||||||
matched after normalising both (separators, relative to the working directory, `.` / `..` folded).
|
|
||||||
Diagnostics carry the file's usual name with lines and columns in the buffer; a `<path>` the program
|
|
||||||
never opens is reported as one warning.
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
bump: patch
|
|
||||||
type: fix
|
|
||||||
**A chunked table's keys are unique across its map, and not interned.** A row's id is `(map, key)`, so a
|
|
||||||
tree moved into another chunk keeps it, and `ludicc --check` refuses a key written in two of a map's
|
|
||||||
chunk files, naming both. The keys are no longer interned: interning every key a player walked past would
|
|
||||||
have filled the bounded intern table and kept them all for good. A chunk slot keeps its keys in its own
|
|
||||||
buffers, rewritten in place when the slot is refilled; `intern(row.key)` keeps one past `_out`.
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
bump: patch
|
|
||||||
type: feat
|
|
||||||
Things sit ON the ground rather than hovering over it. The screen-space GI pass takes
|
|
||||||
a second, much tighter set of taps (a 0.40 m radius that grows with distance, with a
|
|
||||||
range check so a far surface behind a near one cannot darken it) and folds the result
|
|
||||||
into the ambient occlusion it already had. The wide radius answers "how enclosed is
|
|
||||||
this", which a trunk meeting grass barely registers; the tight one answers "is
|
|
||||||
something touching here", which is the shadow the eye looks for to place an object.
|
|
||||||
It is eight taps on a buffer the pass had already bound.
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feature
|
|
||||||
**Namespaces are declared in Ludic.** `alias meth(labels) = target` in a `namespace` block makes
|
|
||||||
`Ns.meth(...)` a call to `target`, taking named arguments by those labels; with no label list the
|
|
||||||
target's own parameter names are the labels. The engine's 41 table-driven namespaces - `Http`,
|
|
||||||
`Udp`, `Process`, `Json`, `Value`, `Screen`, `Input`, `Audio`, `World`, `Tiled` and the rest, 438
|
|
||||||
methods - moved out of the compiler into `runtime/native/namespaces.ludic`, and a package owns an
|
|
||||||
API the same way. The code a program compiles to is unchanged byte for byte, and the checker now
|
|
||||||
checks an alias call's arguments against its target.
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feature
|
|
||||||
**`def Recipes from "recipes.lres"` - a game fills a package's open registry from its own resource
|
|
||||||
file.** The entries are checked against the registry's record as the file is read, with errors at
|
|
||||||
the resource file's line, and they are defs of the module that wrote the line: the registry must be
|
|
||||||
open to it, and they sit in the stable order (the declaring module's entries, then other modules'
|
|
||||||
by name, and file order within a file).
|
|
||||||
|
|
@ -1,134 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feature
|
|
||||||
**Default parameters, components, views and templates.** A parameter can have a default (`pad: float = 8.0`).
|
|
||||||
A call leaves out what it does not change, and may pass its first arguments by position and the
|
|
||||||
rest by name.
|
|
||||||
|
|
||||||
A `view` declaration is the one bridge between a program and its UI. It names the fields a
|
|
||||||
template may read, the functions it may ask and the `on` events it may send, and it writes
|
|
||||||
`view_<name>() -> UiView`.
|
|
||||||
|
|
||||||
A `component Name { prop, state, fields, functions, on events }` declaration beside `Name.xml` and
|
|
||||||
`Name.lss` is a UI component. Its template and styles are compiled in (with `@import` inlined), each
|
|
||||||
mounted instance keeps its own props and state, styles are scoped to it, and a parent's `class`,
|
|
||||||
`style` and `id` land on its root.
|
|
||||||
|
|
||||||
`ludic.ui` is now a template runtime. Screens and components are XML files loaded at run time,
|
|
||||||
with:
|
|
||||||
- `{expression}` bindings;
|
|
||||||
- `<if>`, `<else>` and `<each>`;
|
|
||||||
- props, `<slot/>` and per-instance `<state>`;
|
|
||||||
- `on-press` actions that send events, `set` state or `emit` to the component's user;
|
|
||||||
- component libraries (`export="true"`, `<import src as>`);
|
|
||||||
- HTML's elements (`div`, `p`, `h1`-`h6`, `ul`/`li`, `img`, `hr`, ...), with a default stylesheet;
|
|
||||||
- HTML's attributes: `id`, `class`, `style`, `hidden`, `disabled` and `onclick`, with any other
|
|
||||||
attribute kept for selectors;
|
|
||||||
- the CSS box model (padding and margin in 1-4 values, borders, `px` and `%`) and flex layout
|
|
||||||
(`flex-grow`, `justify-content`, `align-items`/`align-self`, `flex-wrap`, min and max sizes)
|
|
||||||
under CSS's property names;
|
|
||||||
- stylesheets, in a `<style>` or an `.lss` file (a Ludic StyleSheet) that others import and that
|
|
||||||
can `@import` more;
|
|
||||||
- CSS's selectors: `#id`, compound classes, `[attr=value]`, descendant and `>` combinators,
|
|
||||||
`:hover`, `:disabled`, `:first-child`, `:last-child`, `:nth-child`, `:not` and more, weighed by
|
|
||||||
specificity.
|
|
||||||
|
|
||||||
- more CSS: custom properties and `var()`, `position` with insets and `z-index`, `em`/`rem`/`vw`/`vh`,
|
|
||||||
`@media`, wrapping text and ellipsis, `overflow`, `+`/`~`, `:nth-child(an+b)`, `:checked`, `:active`;
|
|
||||||
- more React: keyed lists, `<let>`, `<provide>` context, `<fragment>`, named slots, `on-mount` and
|
|
||||||
`on-unmount`;
|
|
||||||
- native elements a program draws itself (`ui_native`, `ui_fire`), and form controls;
|
|
||||||
- errors with file and line, hot reload (`ui_reload`), and an inspector-style dump.
|
|
||||||
|
|
||||||
The runtime is a UI framework, not only a template engine:
|
|
||||||
- it takes input itself: focus and keyboard navigation, the pointer, scroll boxes, `autofocus`;
|
|
||||||
- it has built-in controls (button, checkbox, radio, range, select, text, key), styled as CSS
|
|
||||||
parts;
|
|
||||||
- `ludic.ui/render3d.ludic` is a render3d backend, with textures, atlases, nine-slices, clipping
|
|
||||||
and scale;
|
|
||||||
- more CSS: `rgba()`/`#rrggbbaa`, `border-radius`, `outline`, `box-shadow`, `background-image`,
|
|
||||||
`border-image`, group `opacity`, `@keyframes` / `animation` / `transition`;
|
|
||||||
- HTML mixed content, and boolean attributes;
|
|
||||||
- `popover` (a top layer that keeps the pointer and keys, with light dismissal), `title` tooltips,
|
|
||||||
and `<progress>` / `<meter>`;
|
|
||||||
- importing `ludic.ui/render3d.ludic` installs the backend, and atlases take rows;
|
|
||||||
- hooks for the program's language, sounds and clock.
|
|
||||||
|
|
||||||
What a game's screens found missing, now in `ludic.ui`:
|
|
||||||
- `<input type="number" min max step>`: typed digits, Enter or leaving it commits them clamped, the
|
|
||||||
arrows step it;
|
|
||||||
- `<input type="key">` listens for any key (Tab and the arrows included) once Enter or a click starts
|
|
||||||
it; Esc stops it, Backspace clears it, `shown` names the value, and `ui_capturing()` tells the host;
|
|
||||||
- `note="..."` under any control's label (`.ui-note`); a range's `decimals`, `format="percent"` and
|
|
||||||
`unit`; a track laid out as a row, with the range's fill as tall as it;
|
|
||||||
- popovers anchored beside an element (`anchor="id"`, or a bare `anchor` for the element before it,
|
|
||||||
`placement`), flipped to the other side and kept on the screen;
|
|
||||||
- `flex-shrink` (a scroll box in a column takes the room its siblings leave), `flex: grow shrink`,
|
|
||||||
`order`, and text in a row wrapping in the room its siblings leave;
|
|
||||||
- `calc()` over px, %, em, rem, vw, vh and `var()`; `width: 0` and `height: 0` mean 0;
|
|
||||||
- `text-shadow`; tooltips of several lines; `ui_opacity()` for a native's draw;
|
|
||||||
- `border-image` drawn as painted with no background colour, tinted by one, and not at all under
|
|
||||||
`transparent`; a picture file drawn untinted (an atlas cell still takes `color`);
|
|
||||||
- the render3d backend loads a picture again when its file changes (`ui_image_reload`), draws a path
|
|
||||||
with a drive letter as a path, and slices a nine-slice by its texture's own width and height;
|
|
||||||
- a component root that is itself a component takes every user's class, style and id, and the
|
|
||||||
sheets that style it are weighed together by specificity;
|
|
||||||
- a component's event may be called `set`; a `string` prop given a number reads it as text; two
|
|
||||||
components of one name are an error naming both files;
|
|
||||||
- the scrollbar is `.ui-scrollbar` and `.ui-thumb`: a press on the thumb holds it where it was taken,
|
|
||||||
a press on the track jumps the thumb's middle there, and neither presses what is under the bar;
|
|
||||||
- a popover's own controls take its presses whatever lies under it, a press outside only closes it,
|
|
||||||
and while one is up the scroll boxes outside it do not take the pointer;
|
|
||||||
- the first gamepad moves the focus (d-pad, left stick), steps ranges and selects, and presses (A)
|
|
||||||
and goes back (B); a held direction, on the pad or the arrow keys, repeats after 0.42 s and then
|
|
||||||
every 0.11 s on the ui clock (`UiInput.held_*`, `pad_a`, `pad_b` for a host);
|
|
||||||
- pointer events: `on-pointerdown` / `pointermove` / `pointerup` / `drag` / `wheel` with `event.x`,
|
|
||||||
`y`, `dx`, `dy`, `button` and `wheel`, and `ui_native_input(tag, fn)` for a native; a press captures
|
|
||||||
the pointer until release; the pointer hits the topmost element in painting order, and
|
|
||||||
`pointer-events: none` lets it through;
|
|
||||||
- `on-down` and `on-up` on a button (the pointer, Enter or A), with `:active` true while it is held
|
|
||||||
there rather than whenever the pointer is down over it;
|
|
||||||
- an anchored popover's `align="start|center|end"`, and `within="id"` (by default the nearest
|
|
||||||
scroll box around it) for the bounds it is flipped against and kept inside;
|
|
||||||
- `text-fit: shrink MIN` shrinks a line to its box, then cuts it with an ellipsis; `line-height`;
|
|
||||||
an `em` reads the font size the element ends with (a `font-size` later in the rule, or in a later
|
|
||||||
rule), not the one it had so far;
|
|
||||||
- `min()`, `max()` and `clamp()`, in `calc()` or on their own; `top` / `right` / `bottom` / `left`
|
|
||||||
as a percentage or a `calc()` of one, of the containing block;
|
|
||||||
- a nine-slice's corners are clamped to half the box in each direction on its own and cut on whole
|
|
||||||
pixels (`ui_nine_cuts`), so a small key cap has no seam;
|
|
||||||
- `scroll-top="{px}"` holds a scroll box at an offset, with `on-scroll` when the player moves it;
|
|
||||||
`ui_scroll_set(id, px)` moves one once;
|
|
||||||
- `linear-gradient(...)` backgrounds; `aspect-ratio`; `object-fit` for pictures (the renderer's
|
|
||||||
`image_w` / `image_h`) and `ui_object_fit` for natives;
|
|
||||||
- `translate="no"` keeps an element's text as written; a title of several lines is translated whole,
|
|
||||||
else line by line;
|
|
||||||
- `<input type="key">` takes a mouse button (`UI_MOUSE_LEFT` / `RIGHT` / `MIDDLE`, 256-258) and is
|
|
||||||
`:capturing` while it listens;
|
|
||||||
- a `title` shows for the keyboard's focus too, after the same half second; a focus ring drawn
|
|
||||||
through a renderer with no `rect` no longer crashes;
|
|
||||||
- a component with no stylesheet of its own reads a theme's `:root` variables from around it (it
|
|
||||||
did; now a test says so);
|
|
||||||
- `ui_scale()` and `ui_box("id")`, the scale and an element's laid-out box, for a host;
|
|
||||||
- `on-submit` on a text field (Enter or A; the focus and text stay unless `clear-on-submit`);
|
|
||||||
- `zoom` on any element, and a length over a length in `calc()` is a plain number;
|
|
||||||
- `on-hold` every frame a button is held, with `event.dt` and `event.t`;
|
|
||||||
- a transition lands exactly on its end value (it had stopped a rounding error short of it, at every
|
|
||||||
frame rate).
|
|
||||||
|
|
||||||
render3d gains `tex_width` / `tex_height`, and the XML reader keeps text runs among elements in
|
|
||||||
order (`mixed`).
|
|
||||||
|
|
||||||
A `view` field set to a literal or a named function's result needs no type.
|
|
||||||
|
|
||||||
Screens are drawn through a registered renderer. `Value` gains a float kind.
|
|
||||||
|
|
||||||
Also:
|
|
||||||
- A program's function named like one of the runtime's is refused; it had been silently taking the
|
|
||||||
runtime's own calls. So is one named like a compiler built-in (`run`, `exit`, `free`, `fill`,
|
|
||||||
...): every call to a program's own `run` compiled into C's `system()`, and clang failed on the IR.
|
|
||||||
- An index is evaluated before the slice's elements are read. A `xs[f()]` whose `f` grew `xs`
|
|
||||||
read stale memory.
|
|
||||||
- A runtime error names the file its expression is in, not the program's.
|
|
||||||
|
|
||||||
Two declarations with one name (a package's private global and a program's, say) are reported as
|
|
||||||
such before type checking. They used to surface as a page of type errors about the wrong type.
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
bump: patch
|
|
||||||
type: feature
|
|
||||||
**`ludic deps --writes` warns about a write through a local alias.** `let t = thing_cur` and then
|
|
||||||
`t.used = 1` writes another module's record just as `thing_cur.used = 1` does; a local bound straight
|
|
||||||
from another module's global (or from such a local) is now followed within its function and each
|
|
||||||
write through it listed as a warning. A reference that arrives from a function's result is not.
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
bump: patch
|
|
||||||
type: fix
|
|
||||||
**`ludic deps`: a reach counts every state apart, however the states are numbered.** The reach and
|
|
||||||
write-reach bitsets packed 60 states to a word, but an `int` is 32 bits, so `1 << 45` came back as bit
|
|
||||||
13 and states 32 apart shared a bit: a function taking both counted one, fewer than it takes, and the
|
|
||||||
counts (`widest_reach`, `widest_write_reach`, `--reach`, `--wreach`) rose and fell with how a program's
|
|
||||||
states happened to be numbered. The sets now hold 30 to a word. On Maroon Lake `widest_reach` goes
|
|
||||||
58 -> 76 and `widest_write_reach` 54 -> 64 - the real numbers, which the old count hid.
|
|
||||||
`examples/state/reach_wide.ludic` (40 states, S00 and S32 taken together) holds it.
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feat
|
|
||||||
**`ludic deps` sees through fn values, and lists the widest functions.** A step list or a registry of
|
|
||||||
fn values takes no state and still reaches every state its steps take; `widest_reach` is the most
|
|
||||||
states any function can come to - by a call, a `fn f` it writes, or a global holding fn values it
|
|
||||||
reads - reported beside `widest_function` with how many of them it does not take itself
|
|
||||||
(`the widest reach: app_boot (src/app/boot.ludic:30), 72 states (72 through calls and fn values it
|
|
||||||
does not take)`). `--widest N` lists the N functions that take the most states with what each
|
|
||||||
reaches; `--reach N` orders them by reach. A baseline written before this has no `widest_reach` and
|
|
||||||
does not hold it until it is rewritten.
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feat
|
|
||||||
**`ludic deps` says what a function can come to CHANGE** (`widest_write_reach`, and `--wreach N`
|
|
||||||
lists the functions by it): the states it reaches as `mut`, through calls, `fn` values and step
|
|
||||||
lists. Reach itself is sharper: `Port.member()` reaches that member's binding only, and
|
|
||||||
`Registry[i].field` (or a local holding `Registry[i]`) reaches that field only - a question asked of
|
|
||||||
a port or a table that also holds verbs no longer reaches the verbs.
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feature
|
|
||||||
**`ludic.devlink`: the interface's verbs.** A `DevlinkUi` port, every member defaulting to "not offered":
|
|
||||||
`ui_screen` (the screen's root class and its components), `ui_model "<Class>" "<out>"` and `ui_tree "<out>"`
|
|
||||||
(the game writes a component's model or the whole tree to a file, no `..`, and the answer names it, so a
|
|
||||||
datagram stays small) and `ui_override "<path>" "<file>"` (a template or stylesheet read from another file
|
|
||||||
and reloaded keeping state; `""` clears one, `"" ""` all). Answered at once; nothing made per frame.
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feature
|
|
||||||
**`ludic.devlink`: an editor's live link into a running dev build** (protocol v1, frozen with Ludic
|
|
||||||
Studio). Loopback UDP through the `DevlinkNet` port, one request a frame parsed in place from one fixed
|
|
||||||
8 KB buffer and answered into another; every verb a `DevlinkWorld` member defaulting to "not offered":
|
|
||||||
`ping`, `hello` (the build's schema hash as 16 hex digits), `cam_get` / `cam_set` / `cam_release`, `goto`,
|
|
||||||
`map_load`, `time`, `weather`, `shot`, `pause` / `resume` / `step`, the slow three answered later by id.
|
|
||||||
A socket is opened only when `enabled()` says so (a game binds `dev_tools`), a sender off this machine is
|
|
||||||
dropped, and a connected co-op session refuses everything but `ping` and `hello`.
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
bump: patch
|
|
||||||
type: performance
|
|
||||||
**Cut-out edge padding runs on every core.** `tex_dilate`'s passes hand their rows, sixteen at a time, to
|
|
||||||
`Job.parallel_for`: within a pass a row writes only its own still-masked texels and reads only
|
|
||||||
neighbours the mask already let go, so the bytes are the ones the single-threaded loop made. The worker
|
|
||||||
is handed plain buffers in a `DilateJob` and makes nothing. `tex_dilate_bytes` is the slice-taking
|
|
||||||
form (safe_api.ludic), and `examples/rendering/dilate.ludic` holds the result against the old loop
|
|
||||||
(prints DILATE OK). It was 206 ms of the main thread in a Maroon Lake boot.
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
bump: patch
|
|
||||||
type: fix
|
|
||||||
**A dispatched action no longer allocates a record each time.** `dispatch A { ... }` made a fresh
|
|
||||||
record for the queue, and Ludic frees nothing, so a system dispatching every frame (an input's
|
|
||||||
`Move`, a frame's time) grew the program by a record a frame. The queue now keeps a list per
|
|
||||||
action: a dispatch takes the next one (making one only when all are queued), fills every field
|
|
||||||
as `new` would - given, or its default - and `drain_actions()` hands them all back once the queue
|
|
||||||
is empty. A reducer reads its action only during the drain, so nothing sees a record after it is
|
|
||||||
reused; keep what must last in the state, not the action. `ludic.base`'s `actions_test` holds a
|
|
||||||
reused record getting its defaults back.
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feat
|
|
||||||
**A name is defined once, for every kind of declaration.** Two functions with one name were
|
|
||||||
already an error; two `var`s or `const`s (or an enum and a const), or two `property` / `event`
|
|
||||||
records, kept the first definition silently. A game lost months to it: two files both said
|
|
||||||
`KEY_LEFT`, one meaning an arrow key's code and one a binding slot, and the menus read the slot.
|
|
||||||
They are now an error that names both files and lines. `examples/rejected/` holds the two cases,
|
|
||||||
checked by a new `reject_case` in the test runner (an example the compiler must refuse).
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feature
|
|
||||||
**The built-in ECS grows.** Every component was a fixed array of 1024 slots, so a game past 1024
|
|
||||||
entities could not have them (and until the last release silently corrupted memory trying). The
|
|
||||||
per-entity stores are heap blocks now, doubled by `L_grow` as entities outgrow them, the new slots
|
|
||||||
zero: 100 000 entities spawn and query. `Prop.has` bounds against the live capacity and
|
|
||||||
`Pool.capacity` answers it. A snapshot (`save`/`load`, `world_save`/`world_load`) records its slot
|
|
||||||
count first and a load grows to it before reading the stores back, so a snapshot's size follows the
|
|
||||||
world's instead of a fixed 1024. A mod's registered components grow with the rest.
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feat
|
|
||||||
**A schema for editors, and every error as JSON.** `ludicc --emit-schema out.json` (and `ludic
|
|
||||||
schema [file] [-o FILE]`) writes what the compiler resolved once the program type-checks: every
|
|
||||||
record with its fields' types, defaults, doc comments and places; every registry with its record,
|
|
||||||
prefix, resource file, openness and its entries in their final order after the open-registry merge
|
|
||||||
(key, constant, index, file:line:col of the entry and of each field value, and which file brought
|
|
||||||
which entries in); every const; and every function a `fn` value can name, with the `fn_type` a field sees (its states stripped).
|
|
||||||
Deterministic, `"schema_version": 1`. Fields and registries carry editor attributes on the existing
|
|
||||||
`@` syntax - `@Ref(Registry)`, `@OneOf(PREFIX_)`, `@Range(lo, hi)`, `@Unit("m/s")`, `@Asset("gltf")`,
|
|
||||||
`@Color`, `@Node(field)`, `@Clip(field)`, `@Material(field)`, `@Tint(SLOT)`, `@Derived`, `@Text`, `@Multiline`, `@Key`, and `@AppendOnly` / `@ByKey` on
|
|
||||||
a registry - which change nothing but go into the schema; `@Ref` naming no registry is an error, and
|
|
||||||
so is `@Node` / `@Clip` naming a field that is not a glTF (`@Asset("gltf")`, or an `@Ref` to one), and
|
|
||||||
a listing `@OneOf` (`@OneOf(A, B)`, not a prefix `@OneOf(P_)`) naming a constant that does not exist, and `@Tint` naming no constant. A field may now carry several attributes. `ludicc --check
|
|
||||||
--diagnostics=json` (`ludic build --check --diagnostics=json`) prints every error as one JSON array
|
|
||||||
of `{file, line, col, severity, message}` on stdout; tokens and nodes now know their column.
|
|
||||||
|
|
||||||
`Build.schema_hash()` answers FNV-1a 64 of the program's own schema (the bytes `ludic schema` prints),
|
|
||||||
computed only when a program names it, and 0 under `ludicc --release`, which `ludic bundle` now passes.
|
|
||||||
|
|
||||||
A target no part of the program declares (an `@Ref` registry, an `@Tint` or listed `@OneOf` constant) is
|
|
||||||
a warning and `"unresolved": true` in the schema, so a package can name the game's registry; a name of
|
|
||||||
another kind is an error. `@OneOf` on a string field takes words, and every registry row's value is
|
|
||||||
checked against them.
|
|
||||||
|
|
||||||
The schema has a `components` list: each UI component's module, place, doc, template and stylesheet
|
|
||||||
paths, its `props` and `state` (type, default as written, place, doc), `states_read` (the states its
|
|
||||||
header names, apart from its model), `derived` fields with their types, and the `functions` and
|
|
||||||
`events` its template calls with their parameters (states and instance stripped), and the
|
|
||||||
registered native tags its template uses. A `natives` list gives every `ui_native` /
|
|
||||||
`ui_native_input` call with a literal tag: the tag, the function called, its handler and its place.
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
bump: patch
|
|
||||||
type: fix
|
|
||||||
**A function named like an engine namespace method's target is refused where that method is
|
|
||||||
called.** `Random.range` is `rng_range`, so a package's own `rng_range(a, b, c)` silently took
|
|
||||||
every `Random.range(1, 6)` (and the checker then asked for its third argument). It is now an error
|
|
||||||
naming the function, the namespace method and the call.
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
bump: patch
|
|
||||||
type: fix
|
|
||||||
**`expect_eq` on strings compares their text.** It lowered to an integer compare of two pointers,
|
|
||||||
which the IR refused; now two strings with the same text are equal (a null only to a null), and a
|
|
||||||
failure prints both: `expect_eq failed (got "camp", want "lake")`.
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
bump: patch
|
|
||||||
type: fix
|
|
||||||
**`expect_eq` and `expect_near` take floats.** On a float or a double they compared with an integer
|
|
||||||
instruction, and the build failed in clang ("defined with type 'float' but expected 'i32'"); they
|
|
||||||
compare as floats now (the wider kind of the two) and a failure prints the numbers.
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feat
|
|
||||||
**`ludic fmt` for editors.** `ludic fmt --lint --json` prints the violations `--lint` reports as one JSON
|
|
||||||
array on stdout, `[{"file", "line", "col", "rule", "message"}]` ordered by file, line and column (the
|
|
||||||
summary on stderr, `--lint`'s exit status, and the baseline never rewritten). `ludic fmt -` formats
|
|
||||||
stdin to stdout under the project found from the working directory (the nearest `package.ludic`
|
|
||||||
upwards), and refuses a buffer that does not read as Ludic - an open string, a bracket never closed or
|
|
||||||
closed by the wrong one - with exit 2 and `<name>:<line>:<col>: error: ...` on stderr.
|
|
||||||
`--stdin-name <path>` makes the buffer that file: the project is found from its directory, and
|
|
||||||
`ludic fmt - --lint --json --stdin-name <path>` judges it against that file's baseline and `lint
|
|
||||||
paths`, reporting it under the name given. Hooks around `fmt` and `get` read nothing from stdin and
|
|
||||||
write to stderr when the command's stdout is a program's (`--json`, `-`).
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feature
|
|
||||||
**`friend module lab of fishing, data` - a friend of some modules, not all.** A scoped friend sees
|
|
||||||
the private names of the modules it names and only the exports of every other; `friend module lab`
|
|
||||||
alone still sees everything.
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feat
|
|
||||||
**Functions are values (L2).** `fn(int, float) -> bool` is a type, `fn name` is any top-level
|
|
||||||
function's value (it used to be only a thread worker's address), and a call through a local, a
|
|
||||||
global, a record field, a slice element, a parameter or a result of a function type is an
|
|
||||||
indirect call. Two different function types do not mix, a call through one checks its argument
|
|
||||||
count, and a value may be `null`. A registry can hold behaviour and a package can take
|
|
||||||
callbacks. `Job.parallel_for` still checks that its worker takes (int, pointer) and returns
|
|
||||||
nothing. `ludic-dev selfhost-build` now says why it failed instead of exiting 1 silently.
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
bump: patch
|
|
||||||
type: feature
|
|
||||||
**The blades' density window can be filled without a GPU.** grass_density.ludic's gb_frame is split: gb_window_fill
|
|
||||||
fills the GB_TILES x GB_TILES window of density tiles round the camera's (zeros off the map) and sets its corner,
|
|
||||||
and gb_frame sends it. The same bytes as before; a test reads gb_win after gb_window_fill.
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feature
|
|
||||||
**Generic records and functions.** `property Pool<T> { items: []T }`, `function first<T>(xs: []T)
|
|
||||||
-> T` and `function map<T, U>(xs: []T, f: fn(T) -> U) -> []U`; a type writes an instance as
|
|
||||||
`Pool<Thing>`, nested as deep as needed. A call's type arguments come from its arguments, or from
|
|
||||||
the declared type its result is written into, and are refused with the parameter named when
|
|
||||||
neither says. Each instance is compiled once as an ordinary record or function. `ludic-fmt` keeps
|
|
||||||
`Pool<Thing>` together while still spacing `a < b`.
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feat
|
|
||||||
**`ludic.render3d`: painted ground layers grow solid things, with ids, and the trample is data.**
|
|
||||||
`ground_fill`'s candidate is its own function, `ground_candidate` (pure `gf_*` steps with the density read
|
|
||||||
between them, into a caller-held `GroundCand`), and `ground_fill` draws exactly its answers - the same
|
|
||||||
operations in the same order as before, so every cover layer grows bit for bit what it did. A layer with
|
|
||||||
`solid: true` is filled at `step0` with band 0's hashes whatever the camera, a far band drawing a stable
|
|
||||||
subset; each thing has an int id from (layer, chunk, cell) (`ground_solid_id`), and `ground_solid_list` /
|
|
||||||
`ground_solid_at` answer a chunk's things or one by id for physics, the nav bake and saves.
|
|
||||||
`r3d_ground_clearing(x, z, r_in, r_out, floor)` hands the trample over as discs the editor can see, beside
|
|
||||||
the `r3d_on_ground_trample` callback, which still works. `tests/ground_fill_test.ludic` holds all of it to
|
|
||||||
`tests/ground_fill_golden.json` (written by `tests/gen/ground_fill_golden.ludic`), the file the studio's
|
|
||||||
TypeScript generator is tested against.
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
bump: patch
|
|
||||||
type: fix
|
|
||||||
**`Http.text` and `Http.header` return copies.** They handed back the handle's own buffer (and on macOS the
|
|
||||||
response object's string), which `Http.free` then released: a text read before the free and used after it
|
|
||||||
was garbage or empty - maroon-lake's map list wrote a 0-byte maps.json. Each call now returns a string that
|
|
||||||
is the caller's to keep. Read a body once per response.
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feature
|
|
||||||
**English left is an error (phase 26.9).** Under a `lang` line, a template's own words, a text
|
|
||||||
attribute's, a quoted choice that reads as words and a `@Text` row still holding English now refuse
|
|
||||||
the build, where they were warnings; `ludic deps` still counts them as `english_left`. Hole counts and
|
|
||||||
undescribed splits stay warnings. ludic.ui's own words - the key field's "Right click", "Middle
|
|
||||||
click", "Left click" and "press a key..." - are keys (`ui_tk(ui_st, k"ui.right_click", plain)`,
|
|
||||||
`ui.*` in the program's `.po`), with their plain English for a program that binds no translator.
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
bump: patch
|
|
||||||
type: fix
|
|
||||||
**Text keys below a row, padding, and `tr`'s cast.** A `@Text Key` in a record nested in a registry
|
|
||||||
row (and in each item of a list of them) is filled with its derived key, `<registry>.<row>.<field>.<i>.<field>`,
|
|
||||||
as a top-level one is, and a `@Text []Key` a row leaves out takes `<...>.0`, `.1`, ... for as many as
|
|
||||||
the source `.po` has - so no `.lres` spells a key. `field: null` is no text. `trf` / `trn`'s trailing
|
|
||||||
`""` arguments are padding and not counted against the English's holes. And `string(x)` of a string or
|
|
||||||
a `Key` is no allocation to the escape analysis: it is `x` itself, so a `tr(key)` that returns it
|
|
||||||
passes `arena strict` (a template's lone hole still copies).
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: change
|
|
||||||
**ludic.i18n draws plain text as it is: the English path is gone (phase 26.9).** `L` makes a key, a
|
|
||||||
key glued into text, or a line bracketed inside another; anything else - a player's name, a chat
|
|
||||||
line, a number - is drawn as it is, in every language, so a player named "Settings" stays
|
|
||||||
"Settings". Removed with it: the lookup of English words (exact lines, patterns with holes, a
|
|
||||||
paragraph a sentence at a time, padding), `Ln` (use `trn(kn"...")`), `i18n_pattern_count`, and an
|
|
||||||
English argument's own lookup inside a key's hole. A language `.po` is read for its keys and
|
|
||||||
plurals only. A game still on English msgids draws them untranslated until they are keys.
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feature
|
|
||||||
**`ludic.i18n`: keys (phase 26).** A key names what a text is for, and `en.po` says it in English like any
|
|
||||||
other language. A key is a string with a marker byte (`I18N_KEY`, `I18N_PLURAL`), its arguments after
|
|
||||||
byte 31, so the code that makes text never takes `I18nState`: `tr(k)`, `trf(k, a, b, c, d)` and
|
|
||||||
`trn(k, n, a, b, c)` build it, and `L` makes it into text where it is drawn - the language in use, else
|
|
||||||
en.po (read the first time a key is asked for), else the key itself, `[[key]]` in a developer's build
|
|
||||||
(`i18n_loud`). Holes take their arguments in the language's order, a key argument made first; plural
|
|
||||||
keys go by each language's rule. A string with no marker takes the old English path, so a game can
|
|
||||||
move over a file at a time.
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
bump: patch
|
|
||||||
type: fix
|
|
||||||
**A key used only in a listener counts as used.** The key check walked the program's declarations but
|
|
||||||
not the bodies kept beside them - `@On` listeners, the lifecycle hooks, tests, computed fields and
|
|
||||||
scenes - so a key used only there was never checked against en.po and `ludic schema` listed it
|
|
||||||
unused. They are walked now, as every other pass walks them.
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feature
|
|
||||||
**Inflate in a caller-owned context.** `ZInflate` holds everything one inflate works in - the bit reader,
|
|
||||||
the RFC's length and distance tables, and every Huffman table and scratch list a block builds - made once
|
|
||||||
by `z_inflate_new()` and rebuilt in place, so an inflate allocates nothing and each thread that inflates
|
|
||||||
holds a context of its own. `z_inflate_in(z, src, len, out, cap)`, `z_uncompress_in` (zlib) and
|
|
||||||
`z_gunzip_in` (gzip) take the context; `z_inflate` / `z_uncompress` / `z_gunzip` keep their signatures,
|
|
||||||
working in the one context `RtInflateState` holds, so no caller changes.
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
bump: patch
|
|
||||||
type: fix
|
|
||||||
**The most negative int is emitted as itself.** The compiler's `itoa` negated a negative value before
|
|
||||||
taking its digits, and `-(-2147483648)` overflows back to itself, so a literal such as `0x80000000` in an
|
|
||||||
int (`b & 0x80000000`) was written into the IR as a bare `-` and the build failed in LLVM. The digits are
|
|
||||||
now taken off the value as it is. Needs a reseed to reach `bin/ludicc`.
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
bump: patch
|
|
||||||
type: fix
|
|
||||||
**`Json.parse` decodes an escaped string in one pass.** A string with escapes in it was joined a character
|
|
||||||
at a time, and every shorter copy was kept: a long escaped text took gigabytes (ui-preview's story model
|
|
||||||
reached 6.5 GB in three seconds). It is now decoded into one buffer sized from the text. ui-preview's
|
|
||||||
protocol-v1.md states how an `@import` path is resolved and which key a `file` override answers to.
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
bump: patch
|
|
||||||
type: fix
|
|
||||||
**`Json.parse` decodes `\uXXXX`.** An escaped code point is UTF-8 now - a surrogate pair joined into one, a
|
|
||||||
lone surrogate or bad hex as U+FFFD - where the backslash was dropped and the hex kept as text ("iu015f"),
|
|
||||||
and `\t`, `\r`, `\b` and `\f` are the characters they name. Python's `json.dump` writes non-ASCII this way.
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feature
|
|
||||||
**`Json.write_file(value, path)` saves without keeping anything.** It writes the text
|
|
||||||
`Json.encode` would give straight to the file (through `path.tmp` and a rename), built in a buffer
|
|
||||||
the runtime keeps, so a game that saves often holds nothing more for it. `Json.encode` itself is
|
|
||||||
built the same way and makes only the string it returns: it used to join its text with `+`, keeping
|
|
||||||
every piece and every number's text. `examples/lang/json_saves.ludic`: 1000 saves, 0 bytes.
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
bump: patch
|
|
||||||
type: feature
|
|
||||||
**ludic.lab: 16-bit PNGs** — `lab_png_write16_from(st, path, w, h, channels, px, at)` writes 16-bit samples (two bytes each, most significant first) the way `lab_png_write_from` writes 8-bit ones, so a test can write a height map render3d's decoder reads back as R16; `lp_png_bytes(stride, h)` is the size of one.
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feat
|
|
||||||
Three things a crown and a hand light were getting wrong.
|
|
||||||
|
|
||||||
**An aspen's trunk quaked with its leaves.** `layer_flutter`'s mask was the vertex's
|
|
||||||
HEIGHT alone — anything more than 1.2 m up trembled — so on a ten metre aspen the whole
|
|
||||||
bole moved, and a tree that is supposed to shiver read as a column of cloth. What
|
|
||||||
separates a leaf from a bole is not height, it is distance from the model's own centre
|
|
||||||
line: the leaves are out at the edge of the crown and the trunk is on the axis. The mask
|
|
||||||
is radial now, and small plants keep the old one, because a flower is all leaf.
|
|
||||||
|
|
||||||
**Foliage in the camera's face.** A third-person camera walks into a crown and the branch
|
|
||||||
between it and the body is an opaque wall a metre from the lens; in first person it is the
|
|
||||||
whole screen. `#define NEAR_FADE` dithers tree foliage out inside arm's length of the
|
|
||||||
camera — in the depth PREPASS, so the lit pass never sees those pixels either and the
|
|
||||||
equal-depth optimisation is untouched. `gl_Position.w` is the view depth for a perspective
|
|
||||||
projection, so it costs one varying and no uniform. Blades, cards and flowers are left
|
|
||||||
alone on purpose: they live at the player's feet, they are always that close, and fading
|
|
||||||
them opens a hole in the meadow. Nor is the BOLE ever faded: a trunk and its needle cards
|
|
||||||
are one mesh drawn by one program, so the first cut dissolved the trunk into a dither
|
|
||||||
pattern as you walked up to it - a solid tree you can see through reads as a fault, where
|
|
||||||
a branch getting out of your way reads as the camera being polite. The mask is the same
|
|
||||||
signal the quake uses. The shadow pass keeps every leaf, or a tree would stop shading the
|
|
||||||
ground it stands on as you walked up to it.
|
|
||||||
|
|
||||||
**A hand light had the reach of a candle, whatever it was meant to be.** `handLight`'s
|
|
||||||
falloff was an inverse square windowed off between 26 and 6 metres, with both numbers
|
|
||||||
hard-coded: two per cent of its own near field by ten metres out, so carrying a torch at
|
|
||||||
night lit your boots and nothing else. `daylight_hand` takes a `reach` in metres now and
|
|
||||||
the falloff is a gentle power out to it — about nine tenths at a metre, half at half the
|
|
||||||
reach, a tenth at nine tenths of it, nothing past it. A pool of light with a gradient in
|
|
||||||
it rather than a hotspot with a cliff.
|
|
||||||
|
|
||||||
**And the wheel on macOS did nothing, or everything.** `scrollingDeltaY` is a double, and
|
|
||||||
the Cocoa event pump truncated it to an int PER EVENT before accumulating: a trackpad or a
|
|
||||||
Magic Mouse sends a stream of fractions of a line, every one of which truncated to zero, so
|
|
||||||
the wheel was dead; a notched mouse sends three to ten lines at once, so it jumped. The
|
|
||||||
fraction is accumulated now, a precise delta is scaled from points to notches, and the
|
|
||||||
remainder carries to the next frame - one gesture, one step, on both kinds of mouse.
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
bump: patch
|
|
||||||
type: fix
|
|
||||||
**A template literal inside another's `{…}` hole, and integer literals past 2^31 - 1.** The first
|
|
||||||
crashed the compiler (the outer literal ended at the inner one's backtick); a hole is now read as
|
|
||||||
code, so strings, chars and templates inside it are taken whole. The second was read as a wrapped
|
|
||||||
negative int; a decimal literal past `2147483647`, or a hex one of more than eight digits, is now a
|
|
||||||
`long` with its value, and giving one to an `int` is refused. Eight hex digits or fewer are still a
|
|
||||||
32-bit pattern.
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feat
|
|
||||||
The air and the light are the hour's, not one constant apiece.
|
|
||||||
|
|
||||||
Aerial perspective is a curve now. A low sun shines through far more air than a high
|
|
||||||
one and shines ALONG the ground rather than down onto it, so density, height falloff
|
|
||||||
and forward scatter all ride the sun's own elevation, and overcast thickens the air
|
|
||||||
while flattening the scatter. The term that was missing entirely is distance
|
|
||||||
DESATURATION: a surface is pulled toward its own luminance faster than the fog itself
|
|
||||||
arrives. Without it, blending a saturated ridge toward a saturated blue noon sky left
|
|
||||||
a saturated ridge, and a midday frame had a mountain three kilometres off reading as
|
|
||||||
vividly as a bench two metres from the camera.
|
|
||||||
|
|
||||||
The grade is the hour's too. White balance, the shadows' floor, contrast and
|
|
||||||
saturation were nine literals bound at the draw; daylight.ludic writes them now.
|
|
||||||
Noon is the case worth naming: direct sun is warm-white and the only thing filling a
|
|
||||||
midday shadow is a blue sky, so noon gets a cool balance over a blue-lifted shadow
|
|
||||||
with hard contrast between it and the lit ground, and dawn and dusk get the reverse.
|
|
||||||
Gain is deliberately left alone - the grade is `c * gain + lift * (1 - c)`, so it
|
|
||||||
warms the whole frame rather than the highlights.
|
|
||||||
|
|
||||||
The visible sky is relit. It is one HDRI turned on its axis so its sun sits where the
|
|
||||||
hour wants it, and turning a photograph does not change what colour it was taken at -
|
|
||||||
so a sunset had a mid-morning blue overhead. An analytic sky supplies the chroma while
|
|
||||||
the photograph keeps the luminance, so the cloud stays where it is and goes orange at
|
|
||||||
dusk, and the zenith goes deep blue at noon, with no second sky shipped. It fades out
|
|
||||||
under the horizon, where the night's own tint, stars and moon take over, and eases off
|
|
||||||
under heavy cloud.
|
|
||||||
|
|
||||||
And the ground bounce follows the ground: the colour a surface is filled with from
|
|
||||||
below is the map's now, crossing from meadow to rock at its treeline, instead of one
|
|
||||||
green constant everywhere including above the scree.
|
|
||||||
|
|
||||||
R3D_NOAIR=1 restores all of it to what it was, so a before-and-after comes from one
|
|
||||||
binary at one hour. Measured at 400 frames: no cost on either backend.
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feat
|
|
||||||
Light you can see: sun shafts, and mist that lies in the valley.
|
|
||||||
|
|
||||||
Everything before this made the air a COLOUR APPLIED TO A SURFACE. Nothing put light in
|
|
||||||
the space between surfaces, so a basin at dawn had no shafts, no pooled mist and no rays
|
|
||||||
off a ridge at any hour, however the fog was tuned.
|
|
||||||
|
|
||||||
A half-resolution march from the camera to the depth buffer, asking the SAME shadow the
|
|
||||||
rest of the frame asks - the cascades, the baked height-field shadow and the cloud mask -
|
|
||||||
so a shaft is cast by the actual trees and the actual ridge, and a passing cloud dims its
|
|
||||||
own rays. Henyey-Greenstein scattering, because real air throws light forward, which is
|
|
||||||
why a low sun fills a valley when you look into it and does almost nothing when you look
|
|
||||||
away. Density and a separate ground-hugging MIST layer ride the sun's elevation from
|
|
||||||
daylight.ludic, so the mist forms in the cold at either end of the day and burns off by
|
|
||||||
mid-morning.
|
|
||||||
|
|
||||||
It composites into the HDR scene with the bloom pyramid's own tent upsample under ONE/ONE
|
|
||||||
blending - which is exactly what was wanted and already existed - and before bloom, so a
|
|
||||||
shaft blooms like the bright thing in the air it is. Into post_hdr and not post_scene:
|
|
||||||
post_scene is the copy the water refracts, and shafts added there would sit under the lake.
|
|
||||||
|
|
||||||
The tuning that mattered was the SKY term. It is added at every step, so at 0.06 it
|
|
||||||
accumulated into a flat grey wash that lifted lit and shadowed air by the same amount -
|
|
||||||
which is the contrast a shaft is made of. The valley came out as one pale sheet with no
|
|
||||||
rays in it. At 0.012 the sun's term dominates and there is light to see rather than fog.
|
|
||||||
|
|
||||||
R3D_NOVOL=1 switches it off for an A/B. Off in Settings skips the pass whole rather than
|
|
||||||
marching once, because a feature is off when it costs nothing.
|
|
||||||
|
|
||||||
Measured, 400 frames at 07:00: OpenGL 7.1 -> 7.3 s, Vulkan 7.2 -> 7.4 s. Backends agree
|
|
||||||
to 0.08/255.
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feature
|
|
||||||
**`ludic-fmt` enforces a project's style.** `lint` lines in `package.ludic` state it - one statement
|
|
||||||
per line, file, function and comment-block limits (a file's opening comment its own) - and
|
|
||||||
`ludic-fmt --check <files>` and `ludic-fmt --lint` (the project's paths) fail what breaks them, at the
|
|
||||||
line. A baseline file is the ratchet that lets a rule arrive in an existing codebase: the counts a
|
|
||||||
file had may stay but not grow, and they are lowered as they are fixed.
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
bump: patch
|
|
||||||
type: fix
|
|
||||||
**A `return` inside an `@On` listener ends that listener, not the whole dispatch.** Listeners are
|
|
||||||
compiled into one `@ev_<E>` function, and `return` branched to its exit: every listener declared
|
|
||||||
after the one that returned, and every foreign listener, never heard the event. The shape it
|
|
||||||
broke is the common one - a listener per kind that returns early for every kind but its own -
|
|
||||||
where only the first-declared kind was ever answered. `examples/events/answer.ludic` holds it.
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feature
|
|
||||||
**`ludic deps` - the module graph as the compiler resolved it.** It compiles the program with
|
|
||||||
`LUDIC_DEPS` set, and the compiler records every reference its visibility pass sees (from module,
|
|
||||||
to module) and every assignment to another module's global. It prints the modules, dependencies,
|
|
||||||
largest cycle, cross-module writes and globals written from outside; `--graph`, `--dot`, `--writes`,
|
|
||||||
`--uses MOD`, `--check FILE` and `--baseline FILE`.
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feature
|
|
||||||
**ludic.lab - the visual lab as a package.** A scene is an entry in the open registry `LabScenes`,
|
|
||||||
shown on a plate: a flat lit disc, a small sky of the package's own with the sun at one hour, and a
|
|
||||||
frame clock. Headless, each of its cameras (`lab_shot`, `lab_shot_at`) settles and is written as
|
|
||||||
`build/lab/<scene>/<shot>.png`; in a window N and P step through them. `tools/lab/run.sh` and
|
|
||||||
`sheet.py` make a contact sheet. ludic.render3d gains `r3d_plate_mode(true)` - no terrain, grass or
|
|
||||||
water is made - and `r3d_sky_path`; the example scene takes about 120 MB resident. `quit()` in a
|
|
||||||
program with no frame loop no longer fails to link.
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feat
|
|
||||||
**A state machine as data: `@Machine(Record.field)`** (phase 27.1). A registry marked
|
|
||||||
`@Machine(Deer.mood)` is the transitions of a machine over that enum field of the records a state's
|
|
||||||
`Table<Deer>` holds: its record has `from` and `to` (the enum's variants), `on: string` (an action's
|
|
||||||
name, `""` for a transition the tick asks), `guard: fn(Row<Deer>, reads...) -> bool` and
|
|
||||||
`enter: fn(Row<Deer>, reads...) -> void`; the states are the enum's variants and the start is the
|
|
||||||
field's default. Its rows are data - an `.lres` or `def`s - so the studio edits the machine as a graph.
|
|
||||||
|
|
||||||
- **The compiler writes the machine.** For each action an `on` names, a row reducer in the
|
|
||||||
registry's file (`reducer Deer in Herd.deer on Spook`, beside any the program writes, after them):
|
|
||||||
the row's state, the first transition from it on that action whose guard passes, the field set,
|
|
||||||
`enter` run. When a row leaves a state on a guard alone, `state DeerStepsMachine` (the kept row
|
|
||||||
view) and `deer_steps_tick(m: mut DeerStepsMachine, s: mut Herd, reads...)`, one transition a row
|
|
||||||
a tick, called from the program's system. Guards and enters are called by name; nothing allocates.
|
|
||||||
- **The table is the whole machine.** The field is written by nothing else: an assignment to it or
|
|
||||||
a `machine` block's `become` over it outside the written code is a type error (`Deer.mood is the
|
|
||||||
machine DeerSteps's (@Machine(Deer.mood)) - it changes only by a transition in its table`); a new
|
|
||||||
row takes its state in its `new`. A guard and an enter take the row first, are the record's
|
|
||||||
module's, and keep a row reducer's rules (`r.rec` and `r.h` only, a `@Column` field only through a
|
|
||||||
`@RowVerb`); a guard writes nothing through its row.
|
|
||||||
- **The graph is checked**, each an error naming its row: a state never reached from the start, a
|
|
||||||
state with no way out, an `on` naming no action or an action with no `@Target`, a self-transition
|
|
||||||
with no guard, and two ways out of a state on one trigger behind an unguarded first. Also refused:
|
|
||||||
`@Machine` on anything but a registry, a field that is not a plain enum with a default, a
|
|
||||||
`@Column` field, no table (or two) of the record, a transitions record of another shape, and a
|
|
||||||
machine outside its table's state's module.
|
|
||||||
- **`ludic schema`'s code section** gains `machines` (`registry`, `record`, `field`, `enum`, `table`,
|
|
||||||
`start`, `states`, `actions`, `tick`, `module`, `at`); the registry carries `@Machine` among its
|
|
||||||
attributes. `ludic deps` names a machine's reducer `reducer Deer in Herd.deer on Spook (machine
|
|
||||||
DeerSteps)`. vocab: `@Machine`; docs `annot-machine`; LANGUAGE.md "A machine as data".
|
|
||||||
- Examples `actions/machine` (with `deer_steps.lres`); rejects for an unreachable state, no way out,
|
|
||||||
an unknown action, an action with no row, a self-transition with no guard, an ambiguous trigger, a
|
|
||||||
guard that writes, the field written by hand, `@Machine` off a registry and a field that is no
|
|
||||||
enum. The `machine` block stays for a machine that is only code.
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feature
|
|
||||||
**Map-scoped tables: `@PerMap` and `@Chunked(n)` registries** — a registry whose rows live in each map's directory and are read when the map loads, or a chunk at a time.
|
|
||||||
|
|
||||||
`@PerMap @ByKey registry Props of PropRow from "props.lres"` reads `<maps root>/<map>/props.lres` (the root is `package.ludic`'s new `maps "assets/maps"` line), and `@PerMap @Chunked(64) registry Instances of InstRow from "instances/{cx}_{cz}.lres"` one file per chunk. The compiler writes the table's `state` and its verbs in the declaring module (`props_load`, `props_clear`, `props_find`, `props_path`; `instances_in`, `_out`, `_slot`, `_find`, `_clear`, `_path`) and a typed fill over a small runtime `.lres` reader (`runtime/native/lres.ludic`, spliced on demand): a row names any int or float constant by name (resolved at load), a `fn` value by name, nested records and lists. Rows, their lists and the records in them are pooled and refilled in place, and `_find` is an allocation-free hash, so a load allocates nothing past the table's high water and a frame may bring a chunk in. `@Ref(T)` into a map table is a string key, checked against the same map. `ludicc --check` type-checks every map directory's tables against their records with file:line:col diagnostics (`--no-maps` to skip). The schema gives each registry a `"scope"` (`"map"` / `"game"`) and `"chunk"`, and a map `@Ref` `"scope": "map"`; `@Unit` now has canonical ASCII spellings (`deg`, `m/s2`, `N.m`, ...), listed as the schema's `"units"`, and any other spelling is a warning naming the right one.
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
bump: patch
|
|
||||||
type: fix
|
|
||||||
**`ludic build --check` reads the maps only for the package's entry.** A partial program - a unit test, a
|
|
||||||
molecule, a bake's runner - lacks the game's constants, so checking every map's tables against it refused
|
|
||||||
rows the game reads fine (`TH_*`, `NPCK_*` unknown). The maps are now checked with the `entry` package.ludic
|
|
||||||
names (as `ludic build --check` with no file, and tests/pack.sh, do); `--maps` checks them anyway, `--no-maps`
|
|
||||||
never. `ludicc` itself is unchanged: it reads them unless told `--no-maps`.
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
bump: patch
|
|
||||||
type: fix
|
|
||||||
**A `Math.*` call has a type the checker knows.** `Math.max`, `Math.sqrt` and the rest are computed
|
|
||||||
inline by the emitter, and the checker gave every one of them the unknown type, which agrees with
|
|
||||||
everything - so `dispatch Notify { a1: Math.max(5, n) }` put an int into a string field, passed L4
|
|
||||||
and failed in LLVM (`%t63 defined with type i32 but expected ptr`). The checker now gives each the
|
|
||||||
type the emitter does: a float or double first argument is that type (`sign` an int); otherwise
|
|
||||||
`min`, `max`, `abs` and `clamp` keep the first argument's type, `sign`, `floor`, `ceil`, `round`,
|
|
||||||
`posmod`, `wrap` and `ping_pong` are ints and the rest fixed. The bare `min` / `max` / `abs` /
|
|
||||||
`clamp` builtins the same. Where the arguments cannot be told, the result stays unknown.
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feature
|
|
||||||
**A loaded model can be let go of: `model_release(model)`.** Its meshes are freed at once, and
|
|
||||||
each texture once no other model uses it - the glTF loader shares textures between models (one
|
|
||||||
path loaded once, a LOD chain borrowing its LOD0's material), so every cached texture now counts
|
|
||||||
the primitives using it, and the last one frees it and forgets it, with any remembered material
|
|
||||||
that named it. A game can unload a species' kit or a map's models when nothing of them is left,
|
|
||||||
and loading one again afterwards is a fresh load. A scatter layer takes room for what it holds
|
|
||||||
instead of its whole capacity up front (it doubles as it fills, to its cap), which in Maroon Lake
|
|
||||||
was 0.4 GB of never-filled instance arrays. `examples/rendering/release.ludic` is the check.
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feature
|
|
||||||
**Layers: `module flow in layer app uses base, items`.** The modules of one layer use each other
|
|
||||||
freely and may go round; anything outside the layer is still held to the module's `uses` (a
|
|
||||||
layered module with no `uses` reaches nothing outside its layer), and a cycle is refused unless it
|
|
||||||
stays inside one layer. `ludic deps` shows the layers and counts the largest cycle without the edges
|
|
||||||
inside them, printing the count with them beside it.
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feature
|
|
||||||
**Modules.** `module NAME` in a barrel makes its directory a module, and a module's
|
|
||||||
declarations are private to it unless they say `export`: a function, global, record or event of
|
|
||||||
another module used without one is an error naming the module and where to mark it. `friend
|
|
||||||
module NAME` sees everything (a test harness), a file in no module is public, and a package keeps
|
|
||||||
its own module. `LUDIC_VIS_REPORT=1` lists every violation instead of stopping, so an existing
|
|
||||||
program can be given its exports by a script before the rule applies to it.
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feature
|
|
||||||
**`module fishing uses base, data` - a module declares what it may reach, and the compiler holds it
|
|
||||||
to that.** A reference from a module that says `uses` into a module it does not name is refused,
|
|
||||||
exported or not, with the use and the fix in the message ("fishing uses items.inv_add (...): add
|
|
||||||
'uses items' to fishing's module line, or take it through a port"). A module with no `uses` keeps
|
|
||||||
the old rule, a package's module is always usable, a friend is not held to it, a cycle in the
|
|
||||||
declared graph is refused, and `LUDIC_VIS_REPORT=1` lists the violations as `uses:` lines.
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
bump: patch
|
|
||||||
type: fix
|
|
||||||
**A frame that draws more than any before no longer grows the heap on the Mac.** MoltenVK's command
|
|
||||||
pooling kept every command object a frame had ever recorded - about 650 bytes for each draw beyond the
|
|
||||||
busiest frame so far, for as long as the game ran. render3d turns it off
|
|
||||||
(`MVK_CONFIG_USE_COMMAND_POOLING=0`, unless the environment already says otherwise) before the first
|
|
||||||
Vulkan call; the objects are made and freed with their command buffer, at no measured cost.
|
|
||||||
`examples/rendering/steady.ludic` ramps a frame from 20 to 200 actors and fails on what pooling left.
|
|
||||||
A buffer written through its mapping is also read once at the start of the next frame's commands,
|
|
||||||
so MoltenVK makes its Metal buffer then rather than at its first draw, however much later that is.
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feat
|
|
||||||
**A package can carry a native library.** `native "<target>" "<path>"` in a package's
|
|
||||||
`package.ludic` names a C/C++ library per target (`macos-arm64`, `windows-x64`, ...). The
|
|
||||||
compiler records the libraries of every package a program imports, and every link - `ludicc -o`,
|
|
||||||
`ludic build`, `ludic test`, `ludic bundle` - links them: on macOS with an rpath to the package
|
|
||||||
and to `Contents/Frameworks`, where `ludic bundle` places and signs them; on Windows through the
|
|
||||||
import library, the `.dll` copied beside the executable. `tools/native/lib.sh` builds a library
|
|
||||||
from a pinned, checksummed source; `ludic.nativeecho` is the worked example, and
|
|
||||||
`packages/README.md` says what a shim may pass across.
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
bump: patch
|
|
||||||
type: feat
|
|
||||||
**A native library's licence ships with it.** `ludic bundle` copies each linked package's
|
|
||||||
`native/LICENSE*` files beside the `.exe` on Windows and into `Contents/Resources` on macOS, so
|
|
||||||
Jolt Physics' MIT notice travels with every copy of the game. `tools/native/lib.sh` passes lld-link
|
|
||||||
`-implib`, which Git Bash leaves alone (it rewrote `/implib:` into a Windows path).
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
bump: patch
|
|
||||||
type: fix
|
|
||||||
**The foliage's near-fade programs have SPIR-V.** The three `NEAR_FADE` variants were missing
|
|
||||||
from `variants.list`, so on Vulkan the trees' and cards' programs failed to build.
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feat
|
|
||||||
**A `numbers float` file is strict about where its numbers come from.** Two rules, both found
|
|
||||||
converting a renderer and a game from float bits in an `int` to real floats:
|
|
||||||
|
|
||||||
- A decimal literal takes the kind of what it meets. `factor == 0.0` against a `fixed` parameter,
|
|
||||||
`half(3.0)` into a `fixed` one and `var z: fixed = -1.5` are `fixed` literals there, rather
|
|
||||||
than a `fixed` and a `float` that do not mix.
|
|
||||||
- A computed integer never becomes a float implicitly. `count < limit` with an `int` on one side
|
|
||||||
and a `float` on the other is an error that asks for `float(x)`: in a file whose numbers are
|
|
||||||
floats, an `int` meeting one is almost always raw bits, and promoting it compares the bits as a
|
|
||||||
number. It caught a uniform fed an integer as float bits, and a test comparing a tint's bits
|
|
||||||
with its value. Integer constants still promote, and `float(x)` is always allowed.
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feat
|
|
||||||
**`numbers float` — a module whose decimal literals are `float`.** A bare `1.5` is `fixed`
|
|
||||||
unless a float is in sight, which is the right default for deterministic code and the wrong
|
|
||||||
one for a renderer or a game whose numbers are all IEEE: `Math.sqrt(2.0)` came out in `fixed`,
|
|
||||||
and every literal needed a `float(...)` round it. A file that says `numbers float` (at the top,
|
|
||||||
or inside a `program` block) gets float literals, and passes the mode on to every non-runtime
|
|
||||||
file it imports, so one line in a barrel or an entry file covers a whole package. Runtime files
|
|
||||||
under `runtime/` are never switched, so the engine's own fixed-point code keeps its meaning.
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
bump: patch
|
|
||||||
type: fix
|
|
||||||
**One MoltenVK in a process.** On a Mac with the Vulkan SDK installed, the runtime opened the SDK's loader
|
|
||||||
(/usr/local/lib/libvulkan.1.dylib) before MoltenVK, and the loader loaded the SDK's own MoltenVK as its
|
|
||||||
driver - beside ludic.render3d's, which every program is linked against: two copies, each with its pools,
|
|
||||||
and the program drew through the SDK's ("MVKBlockObserver is implemented in both"). MoltenVK is opened
|
|
||||||
first now (dlopen hands back the linked copy), and the loader only when a layer is asked for
|
|
||||||
(VK_INSTANCE_LAYERS, VK_LOADER_LAYERS_ENABLE, R3D_VK_LOADER) - then pinned to ludic.render3d's MoltenVK
|
|
||||||
through VK_DRIVER_FILES and lib/macos-arm64/MoltenVK_icd.json, unless the caller named a driver.
|
|
||||||
examples/rendering/steady.ludic's stream round warms up over 300 cells and takes the least of three
|
|
||||||
windows of 150, as the frame round does: one sample of it read +75 KB on a run whose twin read -5 KB.
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feat
|
|
||||||
One wind, and everything in the valley is moved by it.
|
|
||||||
|
|
||||||
The grass had a gust built from two sines and the trees had NO gust term at all - just a
|
|
||||||
per-instance wobble - so the meadow rippled and the canopy above it swayed to an unrelated
|
|
||||||
rhythm and nothing ever crossed the valley. A real gust is a WAVE: you watch it come over
|
|
||||||
the grass, it reaches you, and it goes on into the trees behind you.
|
|
||||||
|
|
||||||
wind.glsl is prepended to EVERY stage, so the grass's vertex shader, the crown's vertex
|
|
||||||
shader and the water's fragment shader read the same field at the same world position and
|
|
||||||
cannot drift apart. No uniforms are declared in it on purpose - u_time and u_wind already
|
|
||||||
exist in several of those files and redeclaring them is a compile error in whichever stage
|
|
||||||
includes both - so the caller passes what it already has.
|
|
||||||
|
|
||||||
Two numbers mattered. The gust's WAVELENGTH has to fit inside a view or nothing is ever
|
|
||||||
seen to travel: the first cut was 209 m crest to crest, longer than the meadow you can see,
|
|
||||||
so the whole frame sat in one phase and read as everything breathing together. It is 74 m
|
|
||||||
now and a front crosses a normal view in a couple of seconds. And `stiff` lets one field
|
|
||||||
move a blade a long way and a bole hardly at all.
|
|
||||||
|
|
||||||
R3D_DEBUG_WIND=1 paints the field on the ground, which is the only way to SEE a gust in a
|
|
||||||
still: two shots a moment apart show the fronts and show them moving.
|
|
||||||
|
|
||||||
Measured: no cost. 400 frames, GL 6.9 s, VK 7.2 s.
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feature
|
|
||||||
**`export open registry Items of Item` - a registry other modules may add to, in a stable order.**
|
|
||||||
A `def` from another module into a registry that is not open is refused, and a def now goes
|
|
||||||
through visibility like any reference (the registry exported, its module in the definer's `uses`).
|
|
||||||
An open registry's index order is the declaring module's entries first, then every other module's
|
|
||||||
by module name, each in the order it is read - independent of the order a barrel imports them in.
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
bump: patch
|
|
||||||
type: fix
|
|
||||||
**`Os.platform()` and `Os.arch()` allocate nothing after the first call.** Each call malloc'd an
|
|
||||||
8 KB `uname` buffer and let it go, and a game asks the platform every frame in places (a launcher's
|
|
||||||
wait, an update panel, a renderer's present). The buffer is made once and kept.
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
bump: patch
|
|
||||||
type: fix
|
|
||||||
**A name that meets a package's export says whose it is; ludic.ui's `UiAct` is its own.** A game's
|
|
||||||
`UiAct` collided with ludic.ui's, which no program uses - it is private to ludic.ui now, so the game
|
|
||||||
may have one. A real clash - a type named like one a package exports - is still refused, and the
|
|
||||||
message names the package and the way out: `'UiNode' is defined twice (...): ludic_ui exports it, and
|
|
||||||
exported names are one namespace - rename this one, or declare it without export inside a module of
|
|
||||||
your own`.
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feat
|
|
||||||
**`ludic.physics`: Jolt Physics as a package.** Shapes (box, sphere, capsule, cylinder, a
|
|
||||||
boulder's dome, a heightfield, a mesh), still, kinematic and dynamic bodies that can be removed
|
|
||||||
for real, rays, a foot's landing height, a push out of what a body stands in, overlaps, buoyancy
|
|
||||||
against a `PhysWater` port with its current, and hard contacts and splashes as facts - stepped at a
|
|
||||||
fixed 1/60 s, deterministic across machines. Jolt v5.6.0 is built here from its pinned tag
|
|
||||||
(`native/build.sh`) through the phase-15 route.
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
bump: patch
|
|
||||||
type: fix
|
|
||||||
**A validation run sees the game's MoltenVK.** With a layer asked for, the loader was pinned to
|
|
||||||
ludic.render3d's MoltenVK only when the shell named no driver - but the SDK's setup-env.sh exports both
|
|
||||||
VK_DRIVER_FILES and VK_ICD_FILENAMES at the SDK's own MoltenVK, so a shell with one left set loaded that
|
|
||||||
one beside ours. The pin now overwrites VK_DRIVER_FILES and clears VK_ICD_FILENAMES; R3D_VK_ANY_DRIVER=1
|
|
||||||
keeps the shell's driver for a run that means to try another.
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feat
|
|
||||||
**`ludic remove <module>` and `ludic get --json`.** `remove` is the inverse of `add`: the `require`
|
|
||||||
leaves `package.ludic`, the lock keeps what the remaining requires still reach (read from the store's
|
|
||||||
copies of each package's manifest, no network), and each package leaving the lock loses the
|
|
||||||
`ludic_modules/` link `add` made - never the shared store entry. A module that is not required is
|
|
||||||
refused; source still importing a removed package is a warning. `ludic get --json` prints what the run
|
|
||||||
changed in the lock as one JSON object on stdout - `{"added", "removed", "changed", "unchanged"}`, an
|
|
||||||
entry as the lock records it (`name`, `version`, `hash`, `kind`, `provides`), a change as `{name, from,
|
|
||||||
to, from_hash, to_hash}` - with the resolver's own lines on stderr.
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feature
|
|
||||||
**Ports: `port Clock { now: fn() -> int, day: fn() -> int = fn first_day }` in a module and
|
|
||||||
`bind Clock { now: fn game_hours }` where the program is put together.** A module asks for what it
|
|
||||||
needs through `Clock.now()` without naming - or `uses`-ing - the module that answers. A port used
|
|
||||||
and never bound, a bind that leaves out a member with no default, a member the port does not have
|
|
||||||
and a second bind are refused; the binder must see the (exported) port.
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feature
|
|
||||||
**A module's private records and events are its own.** A `property` or `event` a module does not
|
|
||||||
export no longer collides with the same spelling in another module: each private one is compiled
|
|
||||||
under its module's name and its module's types, `new`, `emit` and `@On` follow it. Exported ones are
|
|
||||||
one namespace, and two events of one spelling are now refused (the first used to take the other's
|
|
||||||
emits silently). Generic records, entity components and component or view records stay global.
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feature
|
|
||||||
**A module's private names are its own.** A function, var or const a module does not export no
|
|
||||||
longer collides with the same spelling in another module, in a file in no module or in the
|
|
||||||
runtime: each private one is compiled under its module's name (`seed$shop`) and its module's
|
|
||||||
references follow it. Exported names are still one namespace, and two exports of one name are
|
|
||||||
still refused. Records and events still share one namespace. A program with no such clash compiles
|
|
||||||
exactly as before.
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
bump: patch
|
|
||||||
type: fix
|
|
||||||
**`ludic migrate state --prune` keeps a state declared after a plain parameter, and a parameter named twice is refused.**
|
|
||||||
A function like `home_keep(r: Records, save_st: mut Save)` was taken to lack `save_st`, and the
|
|
||||||
prune gave it a second one at the front (`home_keep(save_st: mut Save, r: Records, save_st: mut
|
|
||||||
Save)`) - which `ludic build --check` let through and clang then refused. The migration now counts a
|
|
||||||
state anywhere in the list as declared, and the checker refuses a function that names two parameters
|
|
||||||
alike (`add names two parameters n`).
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feat
|
|
||||||
**A queue keeps its own count, so a package verb takes only its own state; `ludic migrate state --prune`.**
|
|
||||||
ludic.base's `Queue<T>` carries its name and pending count itself: `queue_new(name)`, `q_push(q, v)`,
|
|
||||||
`q_drain(q)`, `q_clear(q)` take no `BaseState`, and neither does any package verb that pushes a fact -
|
|
||||||
`wallet_earn(wallet_st, n)`, `inv_add(inventory_st, ...)` - so a reducer on a package's state can call
|
|
||||||
its own package's verbs. `core_undrained(tags)` names the queues given to it (`q_tag(q)`) that still
|
|
||||||
hold facts. `ludic migrate state --prune` takes out each state parameter a function no longer uses
|
|
||||||
(nor anything it calls) and the argument that fills it, including an argument for a parameter the
|
|
||||||
callee has dropped: run it over a game to follow this change.
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: fix
|
|
||||||
**A renderer that cannot draw says so and stops.** A `LUDIC_HOME` handed over with its `~` unexpanded
|
|
||||||
(`env LUDIC_HOME=~/x`) left render3d without its shaders: "vulkan cannot start" was printed, and the run
|
|
||||||
went on, logging "no SPIR-V variant" for every program - a world with no terrain, shadows or impostors.
|
|
||||||
Now `r3d_home` puts a leading `~` under `$HOME`; the SPIR-V manifest ends with `E <count>` (written by
|
|
||||||
`ludic-dev shaders`) and one without it, or miscounted, is refused; and every such failure is a fault
|
|
||||||
(`gpu_fault.ludic`): `r3d_open` and each `r3d_load_step` return false on one, a program with no variant is
|
|
||||||
listed, and a game asks `r3d_fault()` and ends with `r3d_fault_exit(code)`, which prints the reason and
|
|
||||||
every missing program. `tests/manifest_test.ludic` holds the parse, the refusals, `r3d_tilde` and that
|
|
||||||
every program in variants.list resolves in the shipped manifest.
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
bump: patch
|
|
||||||
type: fix
|
|
||||||
**A bake reads what was drawn.** `gpu_tex_read_all` (every bake's `bake_pack`) copied a texture on a
|
|
||||||
submit of its own without submitting the frame's draws first, so it read whatever the image held before
|
|
||||||
them: the sky bake's BRDF table came out all zeros for one light width and right for another, depending on
|
|
||||||
whether something else had flushed. It flushes first now, and so does `gpu_tex_write_all`.
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
bump: patch
|
|
||||||
type: fix
|
|
||||||
**Every program render3d can ask for is compiled.** heightgen with no survey and with SMOOTH were never in
|
|
||||||
variants.list, so a smoothed or noise-only terrain (examples/rendering/chunks, smooth, reload) had no
|
|
||||||
height field on Vulkan - silently until render3d's faults were made loud. Both are compiled now (61
|
|
||||||
programs). A program's run-time defines are pure functions in program_defs.ludic (the height field, the
|
|
||||||
ground's tiers, the blades), and tests/manifest_test reads every r3d_program / gpu_program / ac_prog_new call
|
|
||||||
in render3d's source, holds each literal one and every value of each computed one to the shipped manifest,
|
|
||||||
and fails a new computed call site until it is given its function.
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
bump: minor
|
|
||||||
type: feat
|
|
||||||
Rain that leaves something behind.
|
|
||||||
|
|
||||||
Rain fell and the ground did not change: the weather was a curtain of particles in front
|
|
||||||
of a dry valley, and a shower was something you waited out rather than something that
|
|
||||||
happened to the place.
|
|
||||||
|
|
||||||
One scalar, u_wet, 0 dry to 1 soaked, doing the three things a wet surface does. It
|
|
||||||
darkens the albedo. It drops the roughness hard, which is what makes a soaked meadow read
|
|
||||||
as SOAKED rather than merely dark - the sky glances off it. And it pools: a wet ground
|
|
||||||
does not darken evenly, water finds the low places and the flat ground holds what a slope
|
|
||||||
sheds, so the puddle mask is the macro noise the materials already use gated on slope,
|
|
||||||
and a puddle takes the world's up for its normal rather than the ground's relief.
|
|
||||||
|
|
||||||
It joins the shore's existing wet band rather than fighting it - the same term, from a
|
|
||||||
different cause.
|
|
||||||
|
|
||||||
Measured: 34.1% of the frame changes between dry and soaked, and no cost - GL 7.1 s
|
|
||||||
either way over 400 frames.
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue