refactor(x): per-process scratch dirs, Ludic ports of the LSP test and Forgejo release

- every scratch file lives in `$TMPDIR/x_<pid>/` (tmp_dir/tmp_path in
  prelude.ludic), removed by main's new dispatch() → tmp_cleanup();
  X_KEEP_TMP=1 keeps it. `x test` and `x check-*` may now run together.
- `x test-lsp` (tools/x/lsp_test.ludic) replaces tools/test-lsp.py: the
  whole request stream is framed into one stdin file, the server runs to
  `exit`, and the response stream is parsed back by request id; adds a
  check that ludicc's own error is published on save
- tools/x/forgejo.ludic replaces tools/ci/forgejo_release.py (curl with a
  0600 header file; the token is no longer on the command line;
  LUDIC_FORGEJO_API for forks)
- `x docs-palette --check` regenerates into scratch and compares, so the
  drift guard judges the working tree rather than git HEAD; the generator
  no longer emits a trailing blank line the formatter rejects
- `x test-tools`: exit 2 from test-lsp / test-grammar.js is a visible
  skip, never a pass; the widget-prop test uses the `id: Root` syntax
- tools/test-grammar.js: current vocabulary (property/model/handler/
  prefab/scene/event/become/@Queries), LUDIC_NODE_MODULES, exit 2 on skip
- tools/atlas.ludic rewritten in the current language (it did not compile)
- operators.ludic / os.ludic registered in the suite
- json.ludic: j_quote() writer helper

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-05 01:12:26 +03:00
parent 647dfec334
commit 8fed9add66
19 changed files with 1154 additions and 614 deletions

View file

@ -1,56 +1,41 @@
# ============================================================================
# atlas.ludic — render a Kenney tile pack as one index-labeled sheet, so you can
# see which sprite id is which before referring to it in a game.
#
# ludicc tools/atlas.ludic --headless -o build/atlas
# ./build/atlas && sips -s format png out.ppm --out atlas.png
# bin/ludicc --headless tools/atlas.ludic -o build/atlas
# ./build/atlas < /dev/null && sips -s format png build/out.ppm --out atlas.png
#
# Replaces the old tools/atlas.c, which used the C runtime that no longer
# exists. It is the same program, in the language it is a tool for.
# ============================================================================
game Atlas {
const DIR: str = "assets/kenney/tiny-dungeon/Tiles"
const N: int = 132
const COLS: int = 12
# A tool written in the language it is a tool for: one Start handler draws every
# tile with its number underneath and quits, and the headless build leaves the
# frame in build/out.ppm.
program Atlas {
const DIR: string = "assets/kenney/tiny-dungeon/Tiles"
const TILE_COUNT: int = 132 # tile_0000.png … tile_0131.png
const COLUMNS: int = 12
const CELL: int = 24
const SPRITE_INSET_X: int = 4 # centre a 16px sprite in a 24px cell
const SPRITE_INSET_Y: int = 2
const LABEL_INSET_X: int = 1
const LABEL_INSET_Y: int = 18
const BACKDROP: int = 0x101018
const LABEL_COLOR: int = 0x8090b0
system Sheet phase Start {
clear(0x101018)
for i in 0 .. N {
let col = i % COLS
let row = i / COLS
let x = col * CELL
let y = row * CELL
# tile_0000.png … tile_0131.png — the id is the file's number
let id = load_png(tile_path(i))
if id >= 0 {
draw_sprite(id, x + 4, y + 2)
}
text_int(x + 1, y + 18, i, 0x8090b0, 1)
# "<DIR>/tile_0007.png" — the id is the file's zero-padded number
function tile_path(i: int) -> string {
var digits = Text.from_int(i)
while Text.length(digits) < 4 { digits = "0" + digits }
return `{DIR}/tile_{digits}.png`
}
handler Sheet phase Start {
Screen.clear(BACKDROP)
for i in 0 .. TILE_COUNT {
let x = (i % COLUMNS) * CELL
let y = (i / COLUMNS) * CELL
let id = Assets.image(tile_path(i))
if id >= 0 { Screen.sprite(id, x + SPRITE_INSET_X, y + SPRITE_INSET_Y) }
Screen.draw_number(x + LABEL_INSET_X, y + LABEL_INSET_Y, i, LABEL_COLOR, 1)
}
present()
Screen.show()
quit()
}
# build "<DIR>/tile_%04d.png" without a formatter: the runtime's string
# helpers write into a scratch buffer we own
function tile_path(i: int) -> string {
let buf = atlas_buf()
let n = rt_put_str(buf, 0, DIR)
n = rt_put_str(buf, n, "/tile_")
if i < 1000 { poke8(buf, n, 48) n = n + 1 }
if i < 100 { poke8(buf, n, 48) n = n + 1 }
if i < 10 { poke8(buf, n, 48) n = n + 1 }
n = rt_put_int(buf, n, i)
n = rt_put_str(buf, n, ".png")
poke8(buf, n, 0)
return buf
}
var atlas_scratch: pointer = ptr_null()
function atlas_buf() -> pointer {
if ptr_is_null(atlas_scratch) { atlas_scratch = mem_alloc(512) }
return atlas_scratch
}
}