ludic/changes
2026-09-30 00:19:24 +03:00
..
a-hidden-body-still-stands-in-the-sun.md fix(render3d): a body hidden from the camera still stands in the sun 2026-09-19 21:32:11 +03:00
a-leaf-is-not-matte.md fix(render3d): a leaf is not matte 2026-09-19 16:41:33 +03:00
a-lens-and-an-edge.md feat(render3d): anti-aliasing that exists, and a lens for the viewfinder 2026-09-19 17:38:25 +03:00
actions.md feat(lang): 0.R1 - actions and reducers 2026-09-25 19:13:46 +03:00
an-aspen-quakes.md feat(render3d): an aspen quakes, and a tree is no longer bent like a bow 2026-09-19 21:13:42 +03:00
anim-ozz.md feat(ludic.anim): ozz-animation 0.17.0 underneath - a skin's skeleton and each clip built at load, sampled to 1e-4 of anim_mix (phase 19.1) 2026-09-28 12:44:49 +03:00
asset-map.md @Asset(kind, map): a path under each map's directory - ludicc --check looks for it in every map (a @PerMap row's in its own, a game-wide row's in all), unless @Asset(kind, map, optional); the schema marks it scope map; reseeded 2026-09-29 22:13:14 +03:00
bake-expand.md ludic.base: bake_expand(inputs, map) and bake_maps(first_input) beside bake_inputs_hash - {map} put, globs expanded in whole-path byte order with dot-names out - so a bake's runner and the check expand a row one way; tests/baked_expand_test 2026-09-29 22:22:05 +03:00
bake-images.md render3d: an impostor's bake is BC7 with its mips - both atlases uploaded compressed, as baked 2026-09-29 19:18:48 +03:00
baked-textures.md render3d: baked textures before the PNG - a png_decode takes assets/baked/png, a cut-out load assets/baked/cutouts 2026-09-29 17:30:57 +03:00
bind-variable.md feat(lang): bind a port member that takes nothing to a variable 2026-09-25 13:02:01 +03:00
build-ir-in-temp.md fix(cli): build's IR goes to the run's temp directory, not the project 2026-09-23 17:29:55 +03:00
check-build.md feat(cli): ludic build --check / ludicc --check - check without building 2026-09-25 13:13:42 +03:00
chunked-keys.md chunked tables: a key is unique across its map and the slot's own, not interned - a slot keeps row i's key in its own buffer i, rewritten when the slot is refilled (interning ~92k map-unique keys as a player walks would fill the bounded intern table and keep them all); ludicc --check refuses a key written in two of a map's chunk files, naming both; reseeded 2026-09-29 21:43:20 +03:00
contact-darkening.md feat(render3d): the lake answers to a body standing in it 2026-09-19 20:23:27 +03:00
declared-namespaces.md feat(lang): L6 namespaces declared in Ludic - alias 2026-09-24 02:40:57 +03:00
def-from-resource.md feat(lang): def REGISTRY from "file.lres" - a game fills an open registry from its own file 2026-09-25 07:22:05 +03:00
defaults-views-templates.md feat(ui): ui_scale and ui_box, on-submit, zoom (and a length over a length in calc), on-hold 2026-09-25 03:31:38 +03:00
deps-alias-writes.md feat(cli): ludic deps --writes warns about writes through a local alias 2026-09-25 13:22:37 +03:00
deps-reach.md feat(cli): 0.R4 - ludic deps sees through fn values (widest_reach) and lists the widest functions (--widest N, --reach N) 2026-09-26 06:24:46 +03:00
deps-write-reach.md feat(cli): 0.R5 - ludic deps says what a function can come to change (widest_write_reach, --wreach N); a port member and a registry field reach only themselves 2026-09-26 23:19:54 +03:00
devlink-ui.md ludic.devlink: the interface's verbs (ui_screen, ui_model, ui_tree, ui_override) through a DevlinkUi port 2026-09-29 22:48:34 +03:00
devlink.md ludic.devlink: an editor's live link into a running dev build (protocol v1, stage 1) 2026-09-29 19:09:59 +03:00
dilate-parallel.md render3d: cut-out padding on every core - tex_dilate's rows per pass through Job.parallel_for, bytes unchanged 2026-09-29 17:11:21 +03:00
dispatch-kept-records.md changes: a dispatched action's record kept by the queue 2026-09-28 02:09:35 +03:00
duplicate-definitions.md feat(lang): a name is defined once, a local once per block, and a result is always returned 2026-09-23 23:55:20 +03:00
ecs-growable-stores.md feat(compiler): the built-in ECS's stores grow - 100 000 entities, where 1024 was the wall 2026-09-27 22:53:24 +03:00
editor-schema.md ludic schema: a components section and a natives list (R4) 2026-09-29 19:36:04 +03:00
engine-alias-shadow.md fix(lang): a function named like an engine namespace method's target is refused 2026-09-25 05:14:45 +03:00
expect-eq-strings.md fix(test): expect_eq on strings compares their text and prints both on a failure 2026-09-25 19:23:38 +03:00
expect-floats.md fix(test): expect_eq and expect_near compare floats as floats 2026-09-25 05:06:15 +03:00
friend-of.md feat(lang): friend module lab of fishing, data - a friend of the modules it names 2026-09-25 04:12:47 +03:00
function-values.md feat(lang): functions are values (L2), and render3d takes its scene as callbacks 2026-09-24 00:27:26 +03:00
generics.md feat(lang): L5 generic records and functions 2026-09-24 02:21:15 +03:00
http-text-copy.md Http.text and Http.header return copies - they handed back the handle's own buffer (and on macOS the response's string), which Http.free released: a text read before the free and used after it was garbage or empty (maroon-lake's maps.json was written with 0 bytes) 2026-09-29 20:57:44 +03:00
i18n-keys.md ludic.i18n: keys (phase 26.1) - en.po as the base, tr/trf/trn marked strings made into text by L, keyed plurals, the fallback to English then the key ([[key]] loud in a dev build), the English form beside it 2026-09-29 23:16:12 +03:00
inflate-context.md inflate: its context in a caller-owned record, allocation-free per block; the state-backed calls kept 2026-09-29 17:25:16 +03:00
int-min-literal.md compiler: the most negative int is emitted as itself - itoa took its digits off -v, which overflows back to itself, so 0x80000000 in an int was written as a bare "-" (invalid IR); digits now come off v as it is, and the buffer holds a long's 20 2026-09-29 22:00:49 +03:00
json-escape-linear.md Json.parse decodes an escaped string in one pass into one buffer - joined a character at a time, every shorter copy was kept, and a long escaped text took gigabytes (ui-preview: 6.5 GB in 3 s); protocol-v1.md states the @import path rule and the key a file override answers to 2026-09-29 21:53:57 +03:00
json-unicode.md Json.parse decodes \uXXXX to UTF-8 - a surrogate pair joined, a lone surrogate or bad hex as U+FFFD - and \t \r \b \f, where it dropped the backslash and kept the hex as text; examples/lang/json_unicode checks it byte by byte 2026-09-29 21:07:10 +03:00
json-write-file.md runtime: Json.write_file, and Json.encode through a kept buffer 2026-09-28 15:32:36 +03:00
lab-png-16-bit.md ludic.lab: 16-bit PNGs (lab_png_write16_from), so a test can write a height map render3d reads back as R16 2026-09-29 22:08:22 +03:00
leaves-light-and-the-bole.md fix(runtime): the wheel on macOS did nothing, or everything 2026-09-20 01:24:37 +03:00
lexer-literals.md fix(lang): a template inside another's hole, and integer literals past 2^31 - 1 2026-09-25 08:10:46 +03:00
light-air-and-the-hour.md feat(render3d): the air and the light are the hour's, not one constant apiece 2026-09-19 14:42:30 +03:00
light-you-can-see.md feat(render3d): light you can see - sun shafts and valley mist 2026-09-19 16:06:16 +03:00
lint.md feat(tools): L10 ludic-fmt enforces a project's style 2026-09-24 14:38:35 +03:00
listener-return.md fix(events): a listener's return ends that listener, not the dispatch 2026-09-23 17:42:18 +03:00
ludic-deps.md feat(cli): ludic deps - the module graph as the compiler resolved it 2026-09-25 11:16:34 +03:00
ludic-lab.md feat(ludic.lab): the visual lab as a package - a scene on a plate, never a world 2026-09-25 07:48:43 +03:00
map-scoped-tables.md docs: map-scoped tables in LANGUAGE.md, the maps manifest key, a changeset 2026-09-29 21:03:56 +03:00
maps-entry-only.md 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 and cannot judge them; --maps reads them anyway, --no-maps never; ludicc unchanged 2026-09-29 22:14:53 +03:00
math-result-types.md fix(check): a Math.* call has the type the emitter gives it 2026-09-28 14:12:28 +03:00
model-release.md feat(render3d): 23.4 - model_release, shared textures counted, and scatter layers sized to what they hold 2026-09-27 23:34:14 +03:00
module-layers.md feat(lang): layers - module flow in layer app uses base, items 2026-09-25 12:54:15 +03:00
module-scope.md feat(lang): L3 module scope - module, export, friend module 2026-09-24 00:52:12 +03:00
module-uses.md feat(lang): module NAME uses A, B - a module reaches only the modules it names 2026-09-25 04:06:45 +03:00
moltenvk-no-command-pooling.md Os.platform/arch uname once; render3d primes a new buffer's Metal buffer 2026-09-28 13:34:45 +03:00
native-libraries.md feat(pkg): phase 15 - a package can carry a native library 2026-09-26 23:35:45 +03:00
native-notices.md feat(bundle): a native library's licence ships with it - each linked package's native/LICENSE* beside the .exe, in Contents/Resources on macOS 2026-09-27 14:43:16 +03:00
near-fade-spirv.md feat(render3d): Vulkan in a macOS window through MoltenVK; window built-ins take a slice's elements; the NEAR_FADE SPIR-V 2026-09-27 00:52:45 +03:00
numbers-float-strict.md feat(lang): strict numbers in float files; render3d on float 2026-09-23 17:13:25 +03:00
numbers-float.md feat(lang): numbers float - a module whose decimal literals are float 2026-09-23 16:02:11 +03:00
one-wind.md feat(render3d): one wind, and everything in the valley is moved by it 2026-09-19 18:24:43 +03:00
open-registry.md feat(lang): open registry - other modules' defs, in an order imports cannot change 2026-09-25 04:28:34 +03:00
os-platform-once.md Os.platform/arch uname once; render3d primes a new buffer's Metal buffer 2026-09-28 13:34:45 +03:00
package-export-clash.md fix(lang): 0.R4 - ludic.ui's UiAct is its own, and a name that meets a package's export says whose it is 2026-09-26 06:57:35 +03:00
physics-jolt.md feat(physics): phase 16 - ludic.physics over Jolt Physics v5.6.0 2026-09-26 23:41:37 +03:00
ports.md feat(lang): ports - port Clock { ... } in a module, bind Clock { ... } in the app 2026-09-25 04:20:12 +03:00
private-kinds.md feat(lang): a module's private records and events are its own 2026-09-25 11:02:48 +03:00
private-per-module.md feat(lang): a module's private names are its own 2026-09-25 07:08:05 +03:00
prune-late-states.md fix(migrate): 0.R4 - --prune keeps a state declared after a plain parameter; a parameter named twice is refused 2026-09-26 05:32:08 +03:00
queue-owns-count.md feat(base): 0.R4 - a queue keeps its own count, so every package verb takes only its own state; ludic migrate state --prune 2026-09-26 04:19:56 +03:00
r3d-fault-loud.md render3d: a renderer that cannot draw says so and stops - LUDIC_HOME's leading ~ expanded (an unexpanded one left a run without shaders, logging 27 missing variants and drawing on), the SPIR-V manifest ends with E <count> and a cut or miscounted one is refused, and every such failure is a fault: r3d_open / r3d_load_step fail on it, r3d_fault() / r3d_fault_exit(code) for the game; tests/manifest_test 2026-09-29 22:12:39 +03:00
r3d-readback-flush.md render3d: gpu_tex_read_all and gpu_tex_write_all flush the frame first - the read-back is a submit of its own and took the image before the draws that fill it (the sky bake's BRDF table was all zeros at one width in three) 2026-09-29 23:18:05 +03:00
r3d-variants-all.md render3d: heightgen's plain and SMOOTH variants compiled (61 programs) - a smoothed or noise-only terrain had no height field on Vulkan; run-time defines as pure functions (program_defs.ludic) and manifest_test holds every program the source can ask for, literal or computed, to the manifest 2026-09-29 22:30:35 +03:00
rain-that-leaves-something.md feat(render3d): rain that leaves something behind 2026-09-19 19:43:29 +03:00
README.md refactor(cli)!: split the contributor tool out of the ludic CLI 2026-09-05 23:15:12 +03:00
redeclared-local-missing-return.md feat(lang): a name is defined once, a local once per block, and a result is always returned 2026-09-23 23:55:20 +03:00
reducer-reads.md feat(lang): 0.R5 - a reducer writes one state and may read others, declared between its state and the action 2026-09-26 18:23:48 +03:00
registries.md feat(lang): L8 registries by declaration - registry and def 2026-09-24 13:34:51 +03:00
render3d-bc-not-a-target.md fix(render3d): nothing draws into a BC7 texture - sampler binding binds, and compressed images get no mips 2026-09-28 10:34:23 +03:00
render3d-bc-textures.md feat(render3d): 23.3 - a .dds beside a .png is uploaded BC-compressed with its whole mip chain 2026-09-28 01:23:45 +03:00
render3d-blade-density.md render3d: the GPU blades grow by a painted density (a layer of the ground densities, grass_density_layer) where a map has one - a 5x5-tile window round the camera in a storage buffer the cull samples - and by today's rules where not; grass_rule_at is the rules on the CPU for the migration 2026-09-29 21:14:22 +03:00
render3d-chunk-heights.md feat(render3d): terrain_chunk_heights - the ground's B-spline heights a chunk at a time (23.5) 2026-09-28 11:25:21 +03:00
render3d-gltf-cached.md changes: gltf_cached 2026-09-28 11:39:49 +03:00
render3d-grass-kind.md render3d: a grass kind - the GPU blades' per-kind numbers are a GrassKind record the game hands over (grass_kind_set), not constants 2026-09-29 19:15:39 +03:00
render3d-height-format.md perf(render3d): the terrain's height and normal in R32F + RG16F, 128 MB where one RGBA32F was 256 2026-09-28 02:31:18 +03:00
render3d-hooks.md feat(lang): functions are values (L2), and render3d takes its scene as callbacks 2026-09-24 00:27:26 +03:00
render3d-moltenvk.md feat(render3d): 22.10 - ludic.render3d carries MoltenVK, so a Mac program has its Vulkan driver 2026-09-28 01:16:26 +03:00
render3d-plan25-vulkan.md render3d: VkAllocationCallbacks counted (R3D_ALLOC_VK), pipeline create infos freed, actor pool at init 2026-09-28 15:46:19 +03:00
render3d-recycle.md render3d: recycle texture and buffer ids, free a released model whole, actor_release 2026-09-28 12:00:53 +03:00
render3d-small-window.md render3d: r3d_small, a renderer sized for a small UI window 2026-09-29 14:23:00 +03:00
render3d-texture-scratch.md render3d: free a texture's create infos and the .dds path it looked for; steady's model at 0 2026-09-28 12:25:02 +03:00
render3d-vulkan-only.md wip(render3d): plan 22.14 - the OpenGL backend removed (suite 306/306, not yet handed over) 2026-09-28 00:30:11 +03:00
render3d-zero-per-frame.md fix(render3d): nothing allocated in the steady state - the Vulkan allocator reuses its records 2026-09-28 11:14:56 +03:00
resources.md feat(lang): L9 resource files - registry ... from 2026-09-24 14:00:20 +03:00
runtime-on-call.md fix(runtime): Random.* in any program; value_* called directly brings the value tree 2026-09-25 04:59:48 +03:00
runtime-temporaries.md runtime: Log, DateTime.format, Input.text, Path, Mime, Fs, Os and Text keep nothing per call 2026-09-28 13:55:49 +03:00
schema-code.md compiler: ludic schema gains a "code" section (R7) - the code map from the compiler, so the studio's scan is only a fallback: modules (package, layer, uses and where, friend, files), states, actions, reducers, every dispatch, events (cancellable, net) with every emit and their @On listeners, ports (members with fn types, defaults, required) and binds (port, member, fn, value, at), handlers (phase, hook and target, @Public, @Server / @Predicted, @Queries with filters as written, scene and layer), models (@Owned, @Sync), prefabs, scenes (start, public, shows, lasts / then, loads, enter / exit, layers) and fn_refs - every fn name in code or a resource file with the slot it fills (registry field and row, port member or default, record field, emit field, call argument, assignment, let). Places are "file:line:col"; sorted by name, sites by place; schema_version stays 1 (additive). The parser records emits, fn refs, handlers and scenes' layers / lasts / loads for it (emit_schema_code.ludic, emit_schema_code_game.ludic); LANGUAGE.md and the changeset say so; schema_case lines added (not run); reseeded, bootstrap-cfree fixpoint holds 2026-09-30 00:19:24 +03:00
session-rest-least.md ludic.session: ply_rest_least, the party's rest for the clock (the valley's selftest57 down into the packages): the roster's freed slot reused and ply_leave_all, the nearest player alone, and ludic.shop's week of demand leaving the shared Random stream untouched 2026-09-29 22:51:01 +03:00
state.md feat(lang): 0.S - components take states in their header; migrate writes them, never inside a name, never outside the programs given, never into a package's state 2026-09-25 17:11:15 +03:00
still-water.md fix(render3d): the meadow is vegetation, and still water reads as water 2026-09-19 18:07:01 +03:00
stream-arena.md render3d: a stream's cache is made with the stream - records and an arena - and evicts in place 2026-09-28 13:26:59 +03:00
string-temporaries.md Os.heap_bytes: the heap without the renderer; string_temps reads it 2026-09-28 12:31:42 +03:00
template-copy.md templates: {s} with one string hole and nothing else is a new string, not s itself - it was the one template that passed its string through (string() does), and code that wrote it as a copy kept what it then freed; the mark rides the callee, since escape analysis writes the call's uns; reseeded 2026-09-29 20:14:58 +03:00
test-generics.md fix(test): a generic function called from a test block works 2026-09-25 04:46:08 +03:00
test-parallel.md feat(test): ludic test -j N runs tests side by side 2026-09-25 11:38:09 +03:00
test-per-process.md feat(test): ludic test <dir>, each test block in a process of its own 2026-09-25 04:39:55 +03:00
text-keys.md compiler: text keys' follow-up (Master's rulings) - a Key in a template literal's hole is an error (the parser marks each hole's string(), TPL_HOLE; write trf(k"...", ...)), trn takes only a plural key kn"..." and tr / trf refuse one (with or without en.po), and a program with no lang line (a package test) uses key literals unchecked and silently - the warning stays for a lang line whose en.po is missing; rejected/i18n_keys gains key_hole (2) and key_rules (3), key_missing's plural moves to trn; LANGUAGE.md and the changeset say so; reseeded, bootstrap-cfree fixpoint holds 2026-09-29 23:46:48 +03:00
text-to-float.md feat(text): Text.to_float; a void function's return <value> is an error 2026-09-23 19:19:28 +03:00
the-ground-up-close.md fix(render3d): the ground keeps more of its own colour, and the rock keeps all of it 2026-09-19 16:29:17 +03:00
the-world-knows-you-are-there.md feat(render3d): the lake answers to a body standing in it 2026-09-19 20:23:27 +03:00
toolchain-r3-fixes.md fix(lang): the action drain is the program's; --check runs every check a build makes; a registry key named count is refused; name lookups are tables 2026-09-25 23:34:43 +03:00
type-checker.md feat(lang): L4 type checker between parse and emit 2026-09-24 01:34:49 +03:00
udp-open-local.md Udp.open_local(port): a socket bound to 127.0.0.1 alone (udp.ll, udp_win.ll), for a port only this machine's tools reach - a dev link - where Udp.open binds every interface 2026-09-29 19:15:30 +03:00
ui-mounted.md ludic.ui: ui_mounted(out) - the classes of the components the last frame showed, in tree order and each once, into the caller's list - and ui_model_of(cls), the first mounted instance's model read in place; examples/library/ui_mounted 2026-09-29 22:51:33 +03:00
ui-override.md ludic.ui: an override's path is compared normalised, and an absolute path matches the relative one it ends with - a lab build registers its components as lab/../src/ui/..., so the studio's src/ui/... (or absolute) path matched nothing and the override changed nothing; nothing is normalised while no override stands; examples/library/ui_override_up registers under a .. path as the lab does 2026-09-29 23:00:12 +03:00
ui-press-drain.md fix(ui): 0.R4 - an action a UI button dispatches is reduced before the frame is presented 2026-09-26 07:08:26 +03:00
ui-preview-hold.md ui-preview: hold <id or key> <pseudo> - hover, active, focus, focus-visible, checked or disabled held on from the next frame on top of the real input, by the element's key (so through model and load), let go with "" or reset; ludic.ui's held.ludic keeps them and the matcher asks it first; protocol-v1.md and smoke.txt 2026-09-29 22:04:20 +03:00
ui-preview.md ludic ui-preview: ludic.ui components previewed for a studio over stdio (R5) 2026-09-29 19:53:26 +03:00
ui-t-key.md ludic.ui: t(key, holes...) in a template - the key marked (byte 1, a marked key kept as it is) and each hole's value after a byte 31, the text the translator decodes at draw time; joined through the text memo, so nothing is made while it reads the same (phase 26) 2026-09-29 23:16:05 +03:00
unknown-type-names.md fix(check): 0.R4 - a misspelled type in a parameter, a result or a field is refused where it is written 2026-09-26 05:58:35 +03:00
unsafe.md feat(lang): L7 memory is safe unless it says unsafe 2026-09-24 12:53:27 +03:00
uses-packages.md feat(lang): packages count under uses; a port of defaults may go unbound 2026-09-25 05:36:11 +03:00
vehicles-spec.md vehicles: a kind's numbers are a VehicleSpec the game hands over (vehicles_spec), not constants 2026-09-29 20:22:02 +03:00
void-return-error.md feat(text): Text.to_float; a void function's return <value> is an error 2026-09-23 19:19:28 +03:00
vulkan-leaks.md fix(render3d): the Vulkan renderer stops leaking as it loads and draws 2026-09-27 23:03:38 +03:00
vulkan-on-macos.md feat(render3d): Vulkan in a macOS window through MoltenVK; window built-ins take a slice's elements; the NEAR_FADE SPIR-V 2026-09-27 00:52:45 +03:00
wildlife-painted.md ludic.wildlife: painted habitats and ranges through the port (habitat_in, range_at), the rule where none is painted 2026-09-29 20:35:13 +03:00
window-buffers.md feat(render3d): Vulkan in a macOS window through MoltenVK; window built-ins take a slice's elements; the NEAR_FADE SPIR-V 2026-09-27 00:52:45 +03:00
worker-reads-states.md feat(compiler): a Job.parallel_for worker may read a state but not change one 2026-09-27 22:04:59 +03:00
zones.md ludic.zones: a disc per player from their fog, merged into clusters; the simulating packages ask whether a place is simulated 2026-09-29 15:53:23 +03:00

Changesets

A changeset is one small Markdown file describing a single user-facing change, dropped in this directory. ludic-dev release consumes every changeset here into a new CHANGELOG.md section, bumps VERSION, and deletes the consumed files.

Format

bump: minor
type: feat
One or more lines describing the change, in the past-agnostic imperative used in
the changelog. Markdown is fine.
  • bump: — major, minor, or patch (SemVer). The release version is bumped by the highest level among the pending changesets (unless ludic-dev release <level> overrides it).
  • type: — the Conventional Commit type (feat, fix, perf, docs, …). It decides which group the change lands in: feat → Features, fix → Fixes, perf → Performance, and so on, in that order. A type with no known heading gets one named after itself.

Writing the body

The body is markdown and reaches the changelog as markdown: it becomes one list item, with continuation lines indented to stay inside it. Nested bullets, blank lines between paragraphs and inline code all survive.

bump: minor
type: feat
**Tiled map support** — load and draw Tiled maps.

- **TMX/TSX** — the XML formats, decoded to the same intermediate as JSON.
- **Collision** — the `collision` layer projects onto the engine tilemap.

Lead with the thing that changed, not with the mechanism. A reader scanning the release should be able to stop after your first clause.

Adding one

Create a file with a short, unique name, e.g. changes/regex-namespace.md. Any filename works except this README.md, which the release step always skips.

Preview how the next release will read before cutting it — this writes nothing:

ludic-dev release --dry-run