Commit graph

13 commits

Author SHA1 Message Date
5c4c10a1d7 fix(release): keep a changeset's markdown in the changelog
build_section piped every changeset body through `tr '\n' ' '`. A multi-line
changeset came out as one paragraph, so nested bullets rendered as inline
" - " runs and a whole release read as a single unbroken block — v0.3.0 was one
~4 KB bullet.

The renderer is now Ludic rather than a shell one-liner. A section is grouped
by conventional-commit type (Features, Fixes, Performance, ...), each changeset
is one bullet, and continuation lines are indented two spaces so nested lists
and paragraphs stay inside their item. Bullets are sorted within a group, so
cutting the same release twice produces the same text.

Also:

- `x release --dry-run` renders the pending section to stdout and touches
  nothing, so a release can be read before it is cut.
- `x changelog-render` re-renders a section from a directory of changesets, and
  `x changelog-section` prints one release's section back out of CHANGELOG.md.
- The v0.1.0 and v0.3.0 sections are re-rendered with the former, from the
  changesets recovered at each tag's parent commit; the bullet counts (6 and
  25) and word multisets are unchanged. v0.2.0 is left alone: it carries a
  hand-written summary and topical subheadings, and regenerating it would have
  replaced curation with raw changeset dumps. The file header now says that
  a section may carry such a summary, since it previously claimed released
  sections are never hand-edited.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 01:47:49 +03:00
8fed9add66 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>
2026-09-05 01:12:26 +03:00
075a7b1430 feat(pkg): x build-lib / prebuilt consumption — binary packages end-to-end (#64)
All checks were successful
bootstrap / cfree-fixpoint (push) Successful in 19s
ci / build-and-test (push) Successful in 1m37s
commit-lint / conventional-commits (push) Successful in 1s
docs / build-and-deploy (push) Successful in 26s
The package-manager half of prebuilt binary packages, on top of the compiler
foundation (dynamic system registration + --emit-module).

- x build-lib [module.ludic]: compile a package's module to a per-target native
  dylib under lib/<target>/, with an @rpath install name so a consumer resolves
  it from the content-addressed store.
- x link-flags: print the clang flags (the dylib, an rpath to its store dir,
  -export_dynamic) so any build system links a project's prebuilt module dylibs;
  x app splices them automatically for in-repo builds.
- kind prebuilt is resolved + linked like any dependency; a missing build target
  stays a hard error.

Proven hermetically (macOS-gated, since dylibs are native): a module exporting a
component + an @System(Update) + a function is built with x build-lib, fetched
as a prebuilt dep, and linked into a consumer game that never saw its source —
the module's system mutates the shared world and its function is callable
("3 42"). Package suite 17/0; full suite 87/0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-01 07:52:06 +03:00
2c44bae496 feat(pkg): package manager — fetch + MVS resolve + namespace registration (#63)
All checks were successful
bootstrap / cfree-fixpoint (push) Successful in 17s
ci / build-and-test (push) Successful in 1m33s
commit-lint / conventional-commits (push) Successful in 2s
docs / build-and-deploy (push) Successful in 25s
Implements the v1 direction decided in the RFC as a set of `x` subcommands
plus a small, contained compiler change.

  * URL-as-identity, no registry — a dependency is named by its git import
    path and a `git tag vX.Y.Z` publishes a version.
  * Minimum Version Selection — a `require` is a minimum; the resolver picks
    the greatest required minimum per module, then the reachable closure at
    those versions. Deterministic, no SAT solver (tools/x/pkg.ludic).
  * Content-addressed global store + per-project links — packages live once in
    ~/.ludic/store keyed by a content hash; each project links them under
    ludic_modules/. package.ludic (manifest) + package.lock.ludic (lock).
  * Namespace registration for source packages via a module-root import
    fallback in the compiler: do_import resolves a non-local, non-absolute
    import under $LUDIC_MODULES (default ludic_modules/), so a fetched
    package's Ludic compiles into the consumer the way the built-in stdlib
    does. Collisions and missing prebuilt targets are hard errors.

Commands: x add / x get / x update / x verify / x vendor. New hermetic suite
`x test-pkg` (stands up throwaway git repos, offline) is gated inside `x test`.

Existing programs compile byte-for-byte identically (the import fallback only
fires when the local path is absent); the C-free bootstrap fixpoint holds and
the seed is regenerated. Full suite: 87 passed, package suite: 12 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-01 07:08:12 +03:00
498593311f feat(testing): line coverage via --coverage + bin/x test --coverage (#45)
All checks were successful
bootstrap / cfree-fixpoint (push) Successful in 17s
ci / build-and-test (push) Successful in 1m13s
commit-lint / conventional-commits (push) Successful in 3s
docs / build-and-deploy (push) Successful in 19s
Instrument each emitted statement with a per-source-line hit counter, gated
behind a new --coverage flag (default off) so ordinary builds — and the
compiler's own self-compile — stay byte-identical and the C-free bootstrap
fixpoint is untouched. A static line table plus a parallel hit-counter array
are dumped at exit through an atexit hook to $LUDIC_COVERAGE (default
ludic.cov) as a `FILE <name>` header and `<line> <hits>` rows.

bin/x test --coverage compiles the test specs with instrumentation, runs them
into per-file dumps, and aggregates a clean per-file line-coverage report that
names the unreached lines. Adds examples/library/coverage.ludic (a spec whose
tests deliberately miss one branch) and docs. Closes the last open acceptance
item of the testing framework (#12).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-31 14:51:14 +03:00
9ed0070039 feat(tooling): port the docgen site generator to Ludic (no Python) (#41)
All checks were successful
bootstrap / cfree-fixpoint (push) Successful in 16s
ci / build-and-test (push) Successful in 1m4s
commit-lint / conventional-commits (push) Successful in 3s
docs / build-and-deploy (push) Successful in 17s
Follow-up to #31: the doc/lint/grammar checks moved to Ludic there; this ports
the remaining docgen piece (gen.py / check.py / palette.py) so nothing in the
documentation pipeline is Python any more.

Three new `x` subcommands, all in Ludic and compiled by Ludic:

  - x docs-gen [--out DIR]  the static-site generator: parses docs/language/**
    front-matter + bodies (fences, Parameters:), builds the section/symbol
    model, reads the asset templates, and emits every page + ns/color/api pages
    + the landing page + ludic-highlight.js + symbols.json + .nojekyll.
  - x docs-check [DIR]      the coverage / integrity guard (required files, a
    page per inventory.json symbol, duplicate-token and one-dir-per-namespace
    guards, highlighter link targets).
  - x docs-palette          the named-colour source of truth: the palette table
    moved into tools/x/docgen.ludic, emitting emit_color.ludic (pointer, not
    ptr) + palette.json.

Verified against the Python oracle: `x docs-gen` reproduces all 466 output files
BYTE-FOR-BYTE (a Ludic json.dumps/html.escape/front-matter port — ordered dicts,
indent=2 vs compact, ensure_ascii \uXXXX, codepoint-aware truncation), and
`x docs-check` matches check.py's pass/fail output. Wired into `x test` as a
gate (docs-gen -> docs-check on a fresh site; docs-palette stays byte-identical).

CI swap: ci.yml and docs.yml call the Ludic generator; docs.yml drops the
python:3.12 container and bootstraps the toolchain from the IR seed instead.
tools/docgen/{gen,check,palette}.py deleted; only assets/ + inventory.json
remain. Completes #31's criterion 3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-31 02:12:32 +03:00
e65e862244 feat(tooling): port the doc/lint/grammar checks to Ludic (no Python)
All checks were successful
bootstrap / cfree-fixpoint (push) Successful in 13s
ci / build-and-test (push) Successful in 52s
commit-lint / conventional-commits (push) Successful in 3s
docs / build-and-deploy (push) Successful in 2s
Replace the Python doc/lint/vocabulary guards with Ludic equivalents that
run through the `x` task runner, so the checks need no Python interpreter:

  x check-docs         every ```ludic doc fence parses (or is marked)
  x check-impl         every implemented feature has a docs/language page
  x check-vocabulary   vocabulary in sync across grammar / lexer / header / parser
  x lint-asset <file>  validate one editor .json / .xml asset

New fragments: tools/x/json.ludic (a small JSON reader — objects/arrays/
strings with \uXXXX + surrogates/numbers/literals, used by the vocabulary
check's grammar navigation and the asset validator) and tools/x/checks.ludic
(the checks + string helpers + a minimal XML well-formedness validator).

`x test-tools` now runs the vocabulary + docs-coverage checks and the
JSON/XML asset validation through Ludic instead of python3; ci.yml's
docs-coverage step calls `x check-impl` / `x check-vocabulary`. Each port was
verified against its former Python script for exact verdict parity on the
clean tree and on injected drift (a removed keyword, a broken grammar
alternation, an undocumented method).

Deletes the superseded scripts: tools/check-vocabulary.py, tools/check-docs.py,
tools/docgen/check-impl.py, tools/docgen/validate.py. The docgen site
generator (gen.py/check.py/palette.py) and the LSP protocol driver
(test-lsp.py) remain and are tracked separately.

Toolchain unchanged (seed byte-identical); `x test` (56) and `x test-tools`
(29) stay green.

Part of #31

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-31 01:06:38 +03:00
fed80f2152 feat(release): SemVer + ludicc --version, changesets, and x release
Some checks failed
commit-lint / conventional-commits (push) Waiting to run
bootstrap / cfree-fixpoint (push) Successful in 13s
ci / build-and-test (push) Has been cancelled
The project had no versioning discipline: 0 tags, no CHANGELOG, no way for the
compiler to report a version. Add a lightweight, native release flow.

- Versioning: SemVer, with VERSION as the single source of truth. `ludicc
  --version` (and `ludic --version`) read it at runtime — so a bump touches one
  file and never reseeds the compiler. `x version` reports it too.
- Changesets: one small Markdown file per user-facing change under changes/
  (bump level + type + summary; see changes/README.md). This replaces "remember
  to edit the changelog" with a mergeable artifact, no Node changeset tool.
- `x release [major|minor|patch] [--publish]`: fold the pending changesets into a
  new CHANGELOG.md section (grouped by type), bump VERSION, commit, and tag
  vX.Y.Z. The level defaults to the highest changeset bump. `--publish` also
  pushes and creates the Forgejo release with source + toolchain tarballs;
  tools/ci/forgejo_release.py is the small stdlib-Python HTTP glue for the
  release API (a native Http client is issue #6).

Seed the initial changesets describing the shipped surface; the first `x release`
turns them into the v0.1.0 CHANGELOG. Reseeded for the --version flag; C-free
bootstrap fixpoint holds; suites 56 / 29 / 29 on macOS, 51 / 28 (+skips) on Linux
CI, bootstrap-cfree byte-identical on both.

Part of the repository-cleanup / DX pass (with #32, #34).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-30 23:46:59 +03:00
fb728bbefe chore(repo): DX cleanup — categorise examples, text-diffable golden, build/ output (#27 #28 #30)
Repository-cleanup / DX pass folding three tracker items into one coherent
change, verified green end to end (`bin/x test` 49/0, `bin/x selfhost-test`
29/0, `bin/x test-tools` 29/0).

#28 — curate & categorise examples/
- 42 flat entries regrouped into intent-revealing subdirs: games/, rendering/,
  ecs/, events/, networking/, lang/, library/ (was lib/).
- chronorift dir-vs-file duplication resolved: the entry file and its import
  modules now live together under games/chronorift(.ludic).
- Every path reference updated repo-wide (test runner, editor-tool drivers,
  docs/site, design docs).
- New examples/README.md indexes the whole set with run commands.
- Showcase examples without a self-asserting entry (hello, events, net_rt) now
  get a compile-only rot guard in `bin/x test`, so nothing here rots silently.

#30 — text-diffable golden baseline
- The 4 binary selfhost/golden/*.ppm blobs are replaced by a single
  selfhost/golden/renders.sha256 manifest (SHA-256 per render). Hashes are
  byte-identical to the old PPMs, so the baseline is unchanged — only its form.
- game_case now compares framebuffer hashes; a regression shows as a changed
  hex line in review, not "binary files differ".
- New `bin/x golden` regenerates the manifest deliberately (review with
  `git diff selfhost/golden/renders.sha256`).

#27 — PPM & asset handling
- Headless renders now write build/out.ppm, never the repo root; `x app`,
  `x clean`, messaging and .gitignore updated to match. Nothing is written to
  the working root any more.
- Redundant local Kenney .zip archives removed (the art ships extracted;
  .gitignore already excludes *.zip). CC0 License.txt files retained.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-30 18:54:21 +03:00
1c9235a948 chore(repo): add .editorconfig, reconcile .gitignore, add x clean
Repository hygiene pass (issue #39):

- Add `.editorconfig` mirroring ludic-fmt: 2-space indent, LF, UTF-8, trim
  trailing whitespace and final newline by default; 4-space for Python
  tooling; keep trailing whitespace in Markdown (hard line breaks).
- Reconcile `.gitignore`: root-anchor `/build/` and `/out.ppm`, normalise the
  misleading `**.zip` glob to `*.zip`, and document that the Kenney art is
  tracked *extracted* while the download/plugin zips are local-only. `.idea`
  -> `.idea/`.
- Add a `bin/x clean` command that removes `build/`, the root `out.ppm`, and
  stray `bin/*.tmp`, keeping the toolchain binaries so the running `x`
  survives.

No generated artifacts land outside build/ or bin/, both of which are ignored.

Closes #39

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-30 16:46:10 +03:00
effb3f637f refactor(lang): rename the ptr/ptrs types to pointer/pointers
Expand the abbreviated pointer types to full words on the language surface:
  ptr   ->  pointer     (a raw address / FFI handle)
  ptrs  ->  pointers    (a buffer of pointers)

The Ludic type name is distinct from LLVM's own `ptr` spelling: llty() maps
`pointer`/`pointers` to LLVM `ptr`, and the emitted IR keeps `ptr`, so only
the Ludic-level surface changes. Rewrites type annotations across all
sources, the 8 hardcoded pointer type-tags, the `pointers`-buffer indexing
in emit_addr, the grammars/LSP/JetBrains tokens, and the docs
(type-ptr -> type-pointer, type-ptrs -> type-pointers). int/bool keep their
conventional short spelling (like Math).

Reseeded; C-free fixpoint holds; all suites green (45/24/29); site + check.py OK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-30 01:58:54 +03:00
4c48077d68 refactor(lang): rename the fn keyword to function
Expand the function-declaration keyword to the full word across the whole
language and toolchain:
  fn name(...) -> T { ... }   ->   function name(...) -> T { ... }

Done as a self-hosting migration: teach the parser both spellings, reseed,
rewrite every .ludic definition to `function`, then drop `fn`. The compiler
now rejects `fn`. Touches the parser, all selfhost/tools/runtime/example/test
sources, the grammars (TextMate shared+vscode, ludic_syntax.h, JetBrains
LudicTokens.kt), the LSP and formatter, the Python doc/vocab tools
(check-impl, check-docs, validate, palette, test-lsp), and the docs
(fences, prose, kw-fn -> kw-function).

Reseeded; C-free bootstrap fixpoint holds. All suites green (45 regression,
24 self-host, 29 tool); the docs site generates and check.py passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-30 01:43:22 +03:00
a38195128f feat(stdlib): namespaced standard library (issue #2)
Implement the bulk of the namespaced-stdlib proposal (workshopsoft/ludic#2):
156 namespace methods across Math, Text, List, Ease, Collide, World, Net,
Sys, Save, Mem, extended Screen, Color functions, extended Random, and Time.
All deterministic fixed-point; self-hosting (C-free bootstrap fixpoint holds).

Compiler (selfhost/):
- Math.*: sqrt/sin/cos/tan/atan2/asin/acos (fixed-point runtime prelude —
  bit-by-bit isqrt, 256-entry interpolated sine table, Ross atan2), plus
  hypot/dist/dist2/deg_to_rad/rad_to_deg/posmod/wrap/ping_pong/snapped/
  move_toward/smoothstep/lerp/remap/sign/floor/ceil/round.
- Text.* (complete): upper/lower/trim/repeat/pad, split/join/replace,
  and the libc-backed queries.
- List.* (complete): insert/remove_at/remove/sort plus the earlier ops.
- Ease.* (in/out/in_out/back/bounce) and Collide.* (rects/point_rect/
  circles/rect_circle).
- Phase 3: World/Net/Sys/Save namespaced over the bare builtins (byte-
  identical IR) and Mem.* (bytes/words/copy/fill/peek/poke).
- Screen.* extended (line/circle/fill_circle/triangle/fill_triangle via new
  runtime primitives; sprite/sprite_scaled aliases), Color.* functions,
  Random.* (value/int/sign), Time.* (frame/delta/elapsed/now — new
  game-loop frame counter).
- Fix a lexer bug: fixed-point literals with >4 fractional digits overflowed.

Docs & tooling:
- 129 new per-symbol doc pages; gen.py made data-driven (namespaces
  discovered from the docs, no hardcoded list); new check-impl.py enforces
  that every implemented namespace method / keyword / type / phase has a
  doc page, wired into `x test-tools`. Document the previously-undocumented
  keywords (break/continue/where/entry/new/public + and/or/not tokens).
- LSP: namespaced signature help (ns_method_sig) covering every namespace.

Tests: 12 new self-host/regression tests + a golden render for the drawing
primitives. All suites green (selfhost 21, regression 45, tools 29).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-30 00:26:19 +03:00