Commit graph

98 commits

Author SHA1 Message Date
2e9081ca56 chore(release): v0.1.0
All checks were successful
bootstrap / cfree-fixpoint (push) Successful in 12s
ci / build-and-test (push) Successful in 50s
commit-lint / conventional-commits (push) Successful in 3s
2026-08-30 23:48:02 +03:00
03836799fe chore(repo): ignore Python __pycache__ from the tooling
Some checks failed
commit-lint / conventional-commits (push) Waiting to run
bootstrap / cfree-fixpoint (push) Successful in 12s
ci / build-and-test (push) Has been cancelled
2026-08-30 23:47:27 +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
709465cdd8 build(git-hooks): enforce Conventional Commits via a hook + CI, record history decision
All checks were successful
bootstrap / cfree-fixpoint (push) Successful in 12s
ci / build-and-test (push) Successful in 50s
commit-lint / conventional-commits (push) Successful in 3s
The convention was documented in CONTRIBUTING.md but nothing enforced it, and no
decision was on record for the pre-self-hosting `Phase` history.

- tools/git-hooks/commit-msg — rejects a summary that is not a Conventional
  Commit. tools/git-hooks/lib.sh holds the single rule (types, scope, `!`, and
  the merge/revert/autosquash exemptions) so the hook and CI cannot drift.
- tools/git-hooks/lint-range.sh — lints a commit range with that same rule.
- .forgejo/workflows/commit-lint.yml — runs it over the new commits on every
  push and PR, as the backstop for contributors who have not enabled the hook.
- Fix the pre-commit hook, which pointed at the old build/ludic-fmt path (the
  toolchain moved to bin/) and so silently no-op'd; it now finds bin/ludic-fmt.
- CONTRIBUTING.md — full type/scope table, the one-line enable
  (`git config core.hooksPath tools/git-hooks`), and a **Git history** section
  recording the decision: leave the pushed `Phase`-era history as-is (a rewrite
  is destructive and non-reversible for anyone who cloned); enforce the
  convention going forward; let #33's first release tag double as the clean `v0`
  baseline that brackets the old prefix without touching a commit.

Verified: the hook accepts feat/fix/ci/refactor(!)/merge/revert and rejects
"added regex" / "Fix bug" / "WIP"; lint-range passes recent history and flags
the old `Phase 8b:` commit.

Closes #34

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-30 23:30:45 +03:00
1c1192e7c0 ci: add build + test + bootstrap-cfree workflows for the Forgejo runner
All checks were successful
bootstrap / cfree-fixpoint (push) Successful in 21s
ci / build-and-test (push) Successful in 49s
Until now the only workflow was docs.yml — nothing gated a change on the
compiler even building, on `x test` / `x test-tools`, or on the headline C-free
self-rebuild reproducing the seed. Add two Forgejo Actions workflows on the same
`docker` runner the docs job uses.

The toolchain is macOS-first: the self-hosted compiler emits the Darwin libc
standard-stream globals (`__stdoutp`/`__stderrp`), the one thing that stops its
IR from linking on Linux. Everything else is portable — clang-16 assembles the
seed cleanly and the C-free bootstrap reproduces it byte-for-byte on Linux too.
So rather than require a macOS runner (none is registered), bridge that single
gap with a tiny **C-free LLVM-IR shim** (tools/ci/linux_stdio_shim.ll) that
defines the Darwin-named globals over glibc's stdout/stderr, injected into every
clang link via LUDIC_CC. The language keeps its no-C-compiler guarantee.

Workflows:
- ci.yml — bootstrap the toolchain from the seed, then `x test` + `x test-tools`
  + the docs-cover-the-implementation checks, on push to main and PRs.
- bootstrap.yml — `x bootstrap-cfree`: assert the seed rebuilds itself
  byte-for-byte (returns non-zero on drift).

Make the suites host-aware so a Linux run is green without hiding anything: a
new is_darwin()/skip() pair (tools/x/prelude.ludic) makes the cases that are
genuinely macOS-ABI bound — the Cocoa-windowed `ludicc -o` link, the golden
render hashes (blessed on macOS; text raster differs by a hair elsewhere), the
Os known-folder/uname surface, Fs.list and the LSP workspace walk (both read the
BSD dirent layout) — print a visible `skip` off Darwin instead of failing. On
macOS every one of them still runs: suites stay 56 / 29 / 29 green there, and
run 51 / 28 (+skips) on Linux, bootstrap-cfree byte-identical on both.

The formatting gate is ludic-fmt *idempotence* (already in `x test-tools`), not
`fmt(x) == x`: this codebase deliberately preserves hand alignment, so a strict
"already formatted" check would fight that contract.

A prebuilt CI image with clang-16 + python3 baked in is the obvious follow-up
speed-up (ties into the packaging work in #33).

Closes #32

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-30 23:23:23 +03:00
ac1e8d157d refactor(stdlib): consolidate System.* onto Os.*, retire duplicated members
All checks were successful
docs / build-and-deploy (push) Successful in 2s
`System.*` and the newer `Os.*` (added in #21) both covered the environment
around the game, with four members (`arg`, `arg_count`, `env`, `exit`) lowering
byte-for-byte identically and the standard streams overlapping in concern. That
is a user-facing ambiguity (`System.env` vs `Os.env` are indistinguishable) and
a drift hazard (two copy-pasted codegen paths).

Make `Os.*` the single canonical environment/process namespace and retire the
overlapping `System.*` members:

- Remove `System.{arg, arg_count, env, exit, stdout, stderr}` from the namespace
  dispatch (selfhost/emit_expr.ludic). Use `Os.arg`/`Os.arg_count`/`Os.env`/
  `Os.exit` and `Os.stdout_write`/`Os.stderr_write` instead.
- `System.*` now covers only its unique low-level surface: the raw file handles
  (`file_open/read/write/seek/tell/close`), `read_char`, and `run`.
- The bare `arg`/`exit`/`getenv`/`file_stdout`/`file_stderr` intrinsics stay —
  they are the primitive layer the self-hosted compiler itself uses; only the
  redundant *namespaced* sugar is gone.
- Docs: drop the six retired `docs/language/system/*` pages, retune the section
  blurb, update inventory.json and the LSP signature table.
- Secondary finding from the issue: cross-link `Text.upper`/`Text.lower`
  (ASCII-only) to `Unicode.upper`/`Unicode.lower` (full Unicode case mapping).

Reseeded; C-free bootstrap fixpoint holds. All suites green (56 test / 29
selfhost / 29 test-tools); docs cover every implemented feature (291 ns-methods).

Closes #40

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-30 22:54:28 +03:00
dd4f5a26ad feat(stdlib): add Fs.* / Path.* / Mime.* — filesystem, paths, content types (#10)
All checks were successful
docs / build-and-deploy (push) Successful in 2s
A cohesive filesystem & IO library — the foundation for saves, config, mods, and
asset loading — wrapping the bare file_* builtins into one safe, ergonomic API a
non-expert can use without touching a file descriptor or a byte buffer.

  Path.*  join / dir / base / ext / stem / normalize   (pure lexical string ops)
  Fs.*    exists / is_dir / read_text / write_text / append_text / remove /
          size / mkdir / copy / list
  Mime.*  of (extension table) / sniff (magic bytes: PNG/JPEG/GIF/PDF)

Pure string IR for Path.*; libc (fopen/access/mkdir/rename/opendir…) for Fs.*;
C-free, emitted on demand (g_uses_fsrt). Safety and determinism baked in:
- write_text and copy are atomic (write a temp file, then rename over the target)
  so a crash mid-write never corrupts the previous file;
- mkdir creates parents (mkdir -p);
- list is sorted for a stable, reproducible directory walk;
- fallible calls return values (null / false / -1), never crashes — ready for a
  first-class try/else when the error-handling work lands.

Complements Os.* (#21): Os supplies per-user locations, Fs the operations. v1
targets the native macOS/BSD filesystem with "/" separators; Windows separators,
a sandboxed wasm virtual FS, recursive directory copy, and richer magic-byte
sniffing are documented follow-ups.

- examples/library/fs.ludic: 32 assertions across pure Path ops (incl. normalize
  resolving ./ .. and duplicate slashes), a real create/read/append/copy/list/
  remove cycle under build/, and Mime by-extension + by-magic (GIF signature vs a
  .bin extension). Wired into `x test` (now 56 passed).
- docs: new Path, Fs, and Mime sections + 18 per-symbol pages; inventory updated;
  every fence passes check-docs; site builds via docgen.
- seed regenerated; `x bootstrap-cfree` fixpoint holds.

Closes #10

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-30 22:43:50 +03:00
bad6cd1ac9 feat(stdlib): add Unicode.* — UTF-8 code points, graphemes, case mapping (#13)
All checks were successful
docs / build-and-deploy (push) Successful in 2s
Make Ludic text correct-by-default over UTF-8, so player names, translated UI,
and chat behave for every language instead of counting bytes and splitting
characters in half. The byte-oriented Text.* stays for speed; Unicode.* is the
layer that understands code points and (approximately) grapheme clusters.

  - len / byte_len          code points vs bytes — the two lengths, kept distinct
  - is_valid_utf8           strict validation of untrusted input
  - char_at / chars         code-point access by index; chars() -> []int
  - upper / lower           case mapping (ASCII + Latin-1)
  - truncate                first n code points, never a half-character
  - grapheme_len            user-perceived characters (approx UAX#29)

Pure integer/byte IR over NUL-terminated buffers; C-free, no data-table blob.
Decoding and validation cover the full UTF-8 range (overlong/surrogate/>10FFFF
rejected). grapheme_len collapses combining marks, variation selectors, ZWJ
sequences (family emoji), and regional-indicator flag pairs. Documented v1
scope: wider-script/locale case rules (Latin-Extended, Greek, Cyrillic, Turkish
i, German ß) and NFC normalization are follow-ups.

- examples/library/unicode.ludic: asserts the invariants across ASCII, Latin-1
  (é round-trips through upper/lower), a decomposed "café" (5 code points, 4
  graphemes), a ZWJ family emoji (5 code points, 1 grapheme), and a flag (2
  regional indicators, 1 grapheme). Wired into `x test` (now 55 passed).
- docs: a new Unicode section + 9 per-symbol pages clarifying byte vs code point
  vs grapheme; inventory updated; every fence passes check-docs; site builds.
- seed regenerated; `x bootstrap-cfree` fixpoint holds.

Closes #13

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-30 22:29:00 +03:00
b205dd8dfd feat(stdlib): add Os.* — the OS/environment interface (#21)
All checks were successful
docs / build-and-deploy (push) Successful in 2s
An Os.* namespace, Go-flavored and game-scoped, for the environment *around*
the game: the command line, environment variables, standard streams, process
exit, the host platform, and the per-user known folders a game writes into.
Rounds the bare System.* builtins (arg/getenv/exit) into one coherent surface.

  - args / arg_count / arg     the argument vector (args() -> []string)
  - env / env_or / has_env     read env vars (null-safe via env_or)
  - set_env / unset_env        mutate this process's environment
  - exit(code)                 terminate with a status code
  - platform() / arch()        host facts (uname sysname/machine)
  - stdout_write / stderr_write  raw writes to the standard streams
  - save_dir / config_dir / cache_dir / temp_dir   per-user known folders

Pure libc over NUL-terminated strings; C-free, no new runtime. arg_count/arg/
exit stay light (no prelude) as thin aliases of the existing intrinsics; the
rest share one Os runtime prelude emitted on demand (g_uses_osrt). platform()
is portable (uname system name is field 0 on every Unix); arch() and the
known-folder layout follow the macOS/BSD conventions — the fully supported
native target today. Linux/Windows/wasm folder resolution and a target-aware
arch() are documented follow-ups.

- examples/library/os.ludic: asserts the invariants that hold regardless of
  host — env round-trip, env_or fallback, unset, args()==arg_count(), non-empty
  platform/arch and known dirs. Wired into `x test` (now 54 passed).
- docs: a new Os section + 17 per-symbol pages; inventory updated; every fence
  passes check-docs (--fmt) and the site builds via docgen.
- seed regenerated; `x bootstrap-cfree` fixpoint holds.

Closes #21

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-30 22:19:11 +03:00
031b138f84 feat(stdlib): add Log.* — levelled, structured logging (#15)
All checks were successful
docs / build-and-deploy (push) Successful in 2s
A Log.* namespace: five levels (trace/debug/info/warn/error), a runtime
threshold, and structured key=value fields, so games get something better than
scattered print calls and release builds can go quiet without touching call
sites.

  - Log.trace/debug/info/warn/error(msg, [k, v]...)  -> stderr, "[LEVEL] msg k=v"
  - Log.set_level(n)   show only level >= n (0 = all default, 5 silences all)
  - Log.level()        read the current threshold

Fields accept strings, ints, and longs (numbers formatted automatically); the
level tag is chosen at compile time so a filtered-out level costs only a
comparison. Writes to stderr, never touching the simulation — no effect on
determinism/replays. v1 is the console sink; rotating-file and in-engine overlay
sinks are noted as follow-ups.

- examples/library/logging.ludic: asserts the set_level/level threshold
  round-trip and that every level (with mixed-type fields) runs without faulting;
  the stderr gating itself was verified by hand (warn/error emit, lower levels
  suppressed). Wired into `x test` (now 53 passed).
- docs: a new Log section + per-symbol pages; inventory and coverage pass.
- seed regenerated; `x bootstrap-cfree` fixpoint holds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-30 21:57:22 +03:00
a4f1494a04 feat(stdlib): add Noise.* — deterministic fixed-point procedural noise (#3)
All checks were successful
docs / build-and-deploy (push) Successful in 2s
A Noise.* namespace for procedural generation, implemented entirely in Q16.16
fixed point over an integer permutation hash so a seed reproduces the exact same
field on every platform and run (native/headless/wasm) — the determinism edge
over float noise that drifts across CPUs.

  - value2 / perlin2 / simplex2  — value, gradient, and simplex noise -> [-1,1]
  - fbm2(x,y,seed,octaves)       — fractal Brownian motion (octaves of simplex)
  - cellular2 / cellular2_id     — Worley F1 distance + nearest-cell id
  - unit(n)                      — remap [-1,1] -> [0,1]

Covers issue phases 1–2 fully plus cellular from phase 3; domain warp, ridged/
billow, and sample1/sample3 remain as follow-ups. Pure integer IR, C-free;
cellular/fbm reuse the math prelude's fx_sqrt.

- examples/library/noise.ludic: asserts the invariants a fixed-point generator
  must hold (Perlin == 0 at lattice points, every sampler within [-1,1],
  reproducibility, seed sensitivity, non-negative cellular distance). Wired into
  `x test` (now 52 passed).
- docs: a new Noise section + per-symbol pages; inventory and coverage pass.
- seed regenerated; `x bootstrap-cfree` fixpoint holds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-30 21:50:50 +03:00
2ddf830f0b feat(stdlib): finish Crypto (CSPRNG + base64) and add Uuid.* library (#19 #16)
All checks were successful
docs / build-and-deploy (push) Successful in 2s
Crypto (#19): add the OS cryptographically-secure random surface
(random_bytes/random_hex/random_u32, reading /dev/urandom) and a standard
base64 encoder, completing the library alongside the existing SHA-256/
HMAC-SHA256/verify_hmac/hex/ct_equal. All pure integer IR, C-free.

Uuid (#16): a new namespace for stable, collision-free IDs — v4 (random) and
v7 (time-ordered) generation, plus parse/is_valid/to_text/equals/nil. UUIDs are
canonical lowercase 36-char strings; v4 and v7's random tail draw from the
crypto CSPRNG, so both carry the documented determinism caveat (mint at the
edges, never inside lockstep simulation). Reuses the crypto prelude's
fn_secure_bytes / fn_hex_encode.

- examples/library/{crypto,uuid}.ludic: known-answer vectors (SHA-256, HMAC,
  base64 per RFC 4231/4648) and structural invariants (uuid version/variant
  bits, parse/equals), wired into `x test` (now 51 passed).
- docs: per-symbol pages for every new method + a new Uuid section; inventory
  and impl-vs-docs coverage check pass.
- seed regenerated; `x bootstrap-cfree` fixpoint holds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-30 21:40:51 +03:00
a422ef4375 docs: rewrite README as an accurate entry point (#35)
All checks were successful
docs / build-and-deploy (push) Successful in 2s
The README was macOS-centric and internally contradictory about backends
(claimed `--target wasm32` and ELF/COFF cross-compile as implemented while the
prose said they were retired with the C compiler). Rewritten to match the repo:

- Tight pitch: compiled, self-hosted, C-free, ECS, deterministic 2D.
- Honest **Backends** table: native 2D ships today; the web/wasm platform layer
  (runtime/web/) and native-vs-wasm diff harness are in-tree and documented, but
  emitting wasm is not yet re-wired on the self-hosted toolchain — same for
  `--target` cross-compile and `--shared` (per COMPILING.md). Removed the false
  "implemented" claims.
- **Quick start** verified end-to-end: the clang seed one-liner, `bin/x build`,
  `bin/x app examples/games/snake.ludic`, and the headless flow (now writing
  build/out.ppm, not the repo root).
- Layout table matches the reorganised tree: examples/ subdirs (with a link to
  examples/README.md), the golden hash manifest, runtime/native + runtime/web,
  tooling and docs.
- Replaced the giant inline roadmap with a concise status plus links out to the
  issue tracker/proposals, the docs site, and the wiki.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-30 18:57:39 +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
0bc5d76952 docs: add Apache-2.0 LICENSE for the compiler/runtime source
All checks were successful
docs / build-and-deploy (push) Successful in 2s
The tree had only the bundled Kenney asset licenses (CC0), leaving the Ludic
compiler and runtime source implicitly all-rights-reserved (issue #36).

- Add a root `LICENSE`: Apache-2.0 — permissive, with the patent grant that
  suits a language/compiler project.
- README: add a License section stating `SPDX-License-Identifier: Apache-2.0`
  and clarifying that code (Apache-2.0) and the third-party CC0 Kenney art are
  licensed separately. Also add a Contributing section linking CONTRIBUTING.md
  and the Forgejo templates.

Closes #36

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-30 16:46:39 +03:00
7d64a617d4 docs: add CONTRIBUTING, code of conduct, and Forgejo templates
Contributor onboarding for the self-hosted toolchain (issue #37):

- `CONTRIBUTING.md`: prerequisites, the bootstrap one-liner, the dev loop
  (`bin/x reseed` -> `bin/x bootstrap-cfree` -> `bin/x test`), stdlib-addition
  guidance, and the code/commit conventions (Conventional Commits, ludic-fmt,
  one-job-per-file, Ludic-not-C/Python/JS for new tooling).
- `.forgejo/issue_template/`: bug, proposal, and cleanup/DX templates.
- `.forgejo/pull_request_template.md`: a checklist covering tests, the
  bootstrap fixpoint, formatting, docs/inventory, and commit style.
- `.forgejo/CODEOWNERS` and a short `CODE_OF_CONDUCT.md`.

Closes #37

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-30 16:46:30 +03:00
3bab2d2d4c docs: merge duplicate networking namespace dirs; guard against recurrence
Documentation namespace cleanup (issue #38).

Audit outcome:

- `date` vs `datetime` are NOT duplicates — `Date` is calendar days since the
  epoch, `DateTime` is instants (seconds); distinct runtime namespaces. Kept
  both.
- `network` vs `networking` WAS a real duplicate. Every other stdlib area
  documents only its namespace (`World.*`, `Screen.*`, …), never the bare
  builtins it lowers to. Networking alone also documented the low-level
  `net_*`/builtin forms under `networking/`, duplicating the `Network.*`
  pages under `network/`. Removed `networking/`; `network/` (the `Network`
  namespace, which the compiler and LSP both expose) is canonical. Folded the
  `@Sync`/`@Owned` framing into `network/_section.md` so no context is lost.
- Dropped the `networking` key from docgen inventory.json.

Guard (AC3): `tools/docgen/check.py` now fails if any `ns:` is documented from
more than one directory, or if two sections share an id or (case-folded)
title — so a duplicate-namespace split cannot silently reappear.

`gen.py` + `check.py` pass (34 sections, 365 symbols).

Closes #38

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-30 16:46: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
9ae69e64b4 feat(stdlib): Crypto.* — SHA-256 + HMAC-SHA256, constant-time verify (#19)
All checks were successful
docs / build-and-deploy (push) Successful in 3s
The security-sensitive counterpart to the fast, non-cryptographic Hash.*
library: standard, test-vector-backed hashing for signed saves and message
integrity, kept in its own namespace so nobody reaches for the wrong tool.

  Crypto.sha256(s)                SHA-256 -> 64-char lowercase hex
  Crypto.hmac_sha256(key, msg)    HMAC-SHA256 -> 64-char hex
  Crypto.verify_hmac(key, msg, mac)  recompute + constant-time compare -> bool
  Crypto.hex(s)                   lowercase hex of a string's bytes
  Crypto.ct_equal(a, b)           constant-time string equality

The primitives are implemented from scratch in plain integer LLVM IR
(FIPS 180-4 / RFC 2104): no libc crypto, no data-dependent branches in the
compression rounds, so a given input hashes to the same 32 bytes on every
platform and run. Digests are returned as hex strings, not raw bytes, because
a `str` is null-terminated and a raw digest can contain a NUL. MAC checks use
a non-short-circuiting compare so timing does not leak how much of a forged tag
was correct.

Emitted on demand via g_uses_cryptort, mirroring the emit_hash prelude gate.
Scoped to the deterministic, known-answer-testable core; OS-backed
random_bytes (the one piece that can't be validated by test vectors) is left
for a follow-up.

Tested against published SHA-256 vectors (empty/"abc"/fox + 55/56/64-byte
multi-block padding) and HMAC-SHA256 vectors; wired into the self-host suite as
`crypto`. Docs: a new Crypto section with honest "what this protects / does
not" guidance, one page per method, all fences checked and in the inventory.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-30 13:37:02 +03:00
1858ab65ad ci(docs): run on the docker runner label, not ubuntu-latest
All checks were successful
docs / build-and-deploy (push) Successful in 12s
The docs workflow sat in "Waiting" indefinitely — "no online runner found
matching this label: ubuntu-latest". Our Forgejo runner advertises the
`docker` label (and is Docker-capable, so the container: python:3.12 step
still works); `ubuntu-latest` is a GitHub-ism it never registered. Point
runs-on at the label the runner actually has.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-30 11:44:55 +03:00
4aa2012231 feat(stdlib): sorting toolkit — sort_by/sort_desc_by/sort_with + stable merge sort (#11)
Some checks are pending
docs / build-and-deploy (push) Waiting to run
Grow List sorting from a numeric-only insertion sort into a small,
game-friendly toolkit that sorts records and query results by a key or a
full comparator, stably and in O(n log n).

- List.sort_by(s, keyfn)      ascending by a key (draw order, price)
- List.sort_desc_by(s, keyfn) descending (leaderboards)
- List.sort_with(s, cmpfn)    full cmp(a,b)->int comparator (multi-field)

Comparators/keys are passed as named top-level functions rather than
lambdas, so the toolkit ships without waiting on closures (#1).

Engine: a stable bottom-up merge sort. emit_takeright is the single
place stability is decided ("take the right run's head only on a strict
win" -> equal keys keep prior order). List.sort becomes a hybrid:
insertion sort for n<32, merge sort above; both stable, so output is
unchanged. Key functions must return an integer-ish type; record slices
hold pointer elements, so the key/comparator receives the record pointer.

Tests: selfhost/tests/sort.ludic (scalar large-n, sort_by, sort_desc_by,
stability, sort_with). Docs: list-sort_by/desc_by/with + updated
list-sort. All suites green (28 self-host / 46 test / 29 test-tools);
reseeded, C-free bootstrap fixpoint holds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-30 11:39:26 +03:00
b5455cd550 feat(stdlib): DateTime format/parse + simulated Clock — completes #9
Some checks are pending
docs / build-and-deploy (push) Waiting to run
Finish issue #9 by adding the two remaining acceptance items on top of the
calendar/clock core, still pure-integer and deterministic:

  DateTime.format(dt, pattern) -> string   render an instant via a token
                                           pattern (YYYY/YY/MM/DD/HH/mm/ss;
                                           other chars pass through)
  DateTime.parse(text, pattern) -> int     read an instant back; -1 on a
                                           non-digit where one is expected
  Clock.now/set/advance/reset              a game-controlled simulated clock
                                           (the @L_clock global) that never
                                           touches the wall clock, so gameplay
                                           reading Clock.now() is replay-safe

format/parse take a string-LITERAL pattern and are expanded at compile time
(field offsets are then constant), folding @fn_str_concat over literal runs and
two small runtime helpers: @fn_dt_pad0 (zero-padded field) and @fn_dt_rd
(fixed-width digit reader that stops at the terminator and flags malformed
input). Clock is a universal i32 global declared in emit_head, so it works in
entry and game programs alike.

Adds examples/offline_rewards.ludic — the issue's worked "you were away N hours"
example, driven from its own entry and asserted in the regression suite — plus
selfhost/tests/datetime2.ludic (format/parse round-trip, parse failure, clock),
docs (Clock section + 4 pages, DateTime.format/parse pages), inventory and LSP
hover. Reseeded; C-free fixpoint holds; all suites green (27 self-host / 46
regression / 29 tools); check.py (366 symbols), check-impl.py (218 ns-methods)
and validate.py OK.

With this, #9's scope is fully delivered: DateTime/Date/Duration + core ops,
format/parse, a deterministic simulated clock, docs + offline-rewards example,
and tests. (v1 stays UTC-only, no leap seconds, i32 epoch valid through 2038.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-30 03:51:48 +03:00
1a2c6ec2c7 feat(stdlib): Time/Date/Duration calendar-clock core (issue #9)
Some checks are pending
docs / build-and-deploy (push) Waiting to run
Implement the calendar/clock half of #9 as plain-i32 integer epochs — no
new type, no floating point (the issue's "integer epochs to avoid drift") —
so every operation is deterministic and bit-identical on every platform:

  Duration — a span in whole seconds; seconds/minutes/hours/days build one,
             as_seconds/as_minutes/as_hours/as_days read it back. Because a
             duration is just an int, `+` and `>` work with no extra machinery
             (Duration.minutes(5) + Duration.seconds(30), away > Duration.hours(3)).
  Date     — a civil day as days-since-1970 (UTC): new/year/month/day/weekday/
             is_leap/days_in_month/to_epoch/add_days/diff_days.
  DateTime — an instant as seconds-since-1970 (UTC, matching Time.now):
             from/date/add/year/month/day/weekday/hour/minute/second.
  Time.since(past) = now - past, for offline-progress / "time away" checks.

New selfhost/emit_datetime.ludic (is_/emit_ for the three namespaces, wired
into emit_ns_call + the frag list). The two civil<->epoch conversions are
Howard Hinnant's public-domain proleptic-Gregorian algorithms, emitted once
per program as the @fn_days_from_civil / @fn_civil_from_days prelude and gated
by g_uses_datert; days_in_month is next-month-day-0 (no lookup table). Time
gains `since`. Docs (Duration/Date/DateTime sections, 28 method pages +
time-since), inventory, and LSP hover kept in sync; a registered test checks
component math against hand-computed values. Reseeded; C-free fixpoint holds;
all suites green (26 self-host / 45 regression / 29 tools); check.py,
check-impl.py and validate.py OK.

format/parse, a game-controlled simulated clock, and timezones are tracked
follow-ups; v1 is UTC-only and, on the i32 epoch, valid through 2038.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-30 03:37:38 +03:00
121053e179 feat(stdlib): 2D Vector type + Vector.* namespace (issue #25)
Some checks failed
docs / build-and-deploy (push) Has been cancelled
Implement the Vec.* half of #25 under the proper (de-abbreviated) name
Vector, unblocking it with a self-contained value type instead of waiting
on the full #1 type system.

A Vector is two Q16.16 fixed components (x, y) packed into one i64 — a true
by-value type that lives in a register and never allocates (reuses the new
`long`/i64 support; llty maps `Vector` to i64). Fifteen operations, all
deterministic fixed-point reusing fx_mul/fx_div/fx_lerp and the @fn_fx_*
prelude: make/zero/x/y, add/sub/scale/dot, length/distance/normalize/lerp,
rotate/angle/from_angle.

New selfhost/emit_vector.ludic (wired into emit_ns_call + the frag list),
the `Vector` primitive type in llty and the grammars/LSP/JetBrains tokens,
docs (type-vector + 15 Vector.* pages + section), and a registered test.
Reseeded; C-free fixpoint holds; all suites green (45/25/29); site + check.py OK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-30 02:09:38 +03:00
2e0047514b refactor(lang): rename builtins flr->floor and fx->fixed
De-abbreviate the two bare fixed-point conversion builtins:
  flr(f) -> int     ->  floor(f) -> int    (fixed -> int, flooring)
  fx(i)  -> fixed   ->  fixed(i) -> fixed  (int -> fixed; mirrors how the
                                            stringify builtin is `string`)

Updates the compiler dispatch, all call sites, the grammars/LSP/JetBrains
tokens, and the docs (fn-flr -> fn-floor, fn-fx -> fn-fixed). 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 02:01:56 +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
b7745a4600 refactor(lang): rename the str type (and stringify builtin) to string
Expand the abbreviated string type and its conversion builtin to the full
word everywhere:
  str            ->  string        (the immutable-string type)
  str(x) -> str  ->  string(x) -> string   (the stringify builtin;
                                            what `{…}` interpolation calls)

Types are recognized by identifier, and llty maps both spellings to LLVM
`ptr`, so this is an atomic source rewrite: type annotations, the Ludic
type tags, the builtin name/dispatch, and the interpolation desugar, plus
the grammars, LSP, docs (type-str -> type-string, fn-str -> fn-string), and
inventory. int/bool stay (universally accepted, 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:51:11 +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
2f19c8d8e2 refactor(stdlib): de-abbreviate namespaces (Mem/Sys/Net/Collide)
Expand the abbreviated public namespaces to full words, part of the
language-wide de-abbreviation pass:
  Mem -> Memory, Sys -> System, Net -> Network, Collide -> Collision

Math stays (universally accepted, like int/bool). Renames the dispatch
strings, LSP signatures, docs (dirs, files, frontmatter), and the
inventory manifest; behavior is byte-identical (the bare rt_ targets are
unchanged). Reseeded; C-free bootstrap fixpoint holds; all suites green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-30 01:30:47 +03:00
6e6467b515 feat(stdlib): Math.exp/log/pow + Ease.elastic transcendentals (issue #25)
Some checks are pending
docs / build-and-deploy (push) Waiting to run
Finish the unblocked "Math / Ease" half of #25: the fixed-point
transcendentals deferred from #2. Vec.* stays blocked on the vec2 type
in #1.

- Math.exp, Math.log (natural), Math.pow — deterministic Q16.16 via two
  new prelude fns in emit_math_prelude: @fn_fx_exp2 (range-reduced 5th-order
  Taylor 2^f, then a clamped shift by the integer part) and @fn_fx_log2
  (llvm.ctlz for the exponent + an atanh series on (m-1)/(m+1) for the
  mantissa). exp=2^(x·log2 e), log=log2(x)·ln2, pow=2^(b·log2 a).
- Ease.elastic — ease-out elastic 2^(-10t)·sin((10t-0.75)·2pi/3)+1.
- Pure integer IR, so bit-identical on every platform. Results must fit the
  Q16.16 range (|x| < 32768); larger magnitudes saturate (documented).

Test selfhost/tests/transcend.ludic (registered in the self-host suite) +
docs for all four. Reseeded; the C-free bootstrap fixpoint holds. All suites
green (24 self-host, 45 regression, 29 tool).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-30 01:19:44 +03:00
2002e977d9 feat(lang,stdlib): 64-bit long type + 64-bit Hash variants (issue #17)
Some checks are pending
docs / build-and-deploy (push) Waiting to run
Add `long`, a 64-bit signed integer primitive (i64), threaded through
codegen: llty; int<->long coercion (coerce_code/to_long) at let/assign/
return/call-args; i64 arithmetic + comparison promotion in emit_bin;
unary negate/~; print via %lld and str()/interpolation via @fn_long_str.
Editor vocabulary (syntax header, TextMate grammar, formatter, LSP)
synced; check-vocabulary green. Numeric literals stay i32 — build large
values by widening (documented on the type page).

Complete the Hash.* namespace (issue #17) with both 32- and 64-bit
algorithms: Hash.of/fnv1a/crc32/mix/combine (32-bit) and
Hash.of64/fnv1a_64/mix64 (64-bit, returning long). Deterministic and
C-free; CRC-32 (poly 0xEDB88320) and FNV vectors verified against
reference implementations.

Tests: selfhost/tests/{hash,long}.ludic. Docs: docs/language/hash/*,
type-long.md. Seed reseeded; C-free bootstrap fixpoint holds; 23
selfhost + 45 regression + 29 tooling checks green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-30 01:01:06 +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
ff15c4e01d docs(types): document the fixeds and ptrs typed buffers
All checks were successful
docs / build-and-deploy (push) Successful in 2s
The type table lists fixeds (buffer of fixed values) and ptrs (buffer of
pointers) alongside words, but they had no reference pages. Add both, with
compilable examples; coverage inventory updated (types: 10 -> 12).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-29 18:50:15 +03:00
31f1019a69 docs(site): vertical pipeline on mobile, click tooltips, consistent chrome
All checks were successful
docs / build-and-deploy (push) Successful in 2s
- Pipeline diagram stacks vertically with downward arrows on mobile (row on
  >=720px) instead of the awkward wrap.
- Tooltip cards now open on CLICK (works on touch too), the card itself is
  clickable and opens the symbol's page in a NEW TAB, and an outside click or
  Escape closes it. Replaces the hover-only behavior.
- Reference pages get their own nav — Home / API Reference / Source ↗ — instead
  of the landing-only Features/Examples/Get started anchors, and now include the
  Source link.
- Unify the container width (1120px) across the landing and all reference pages
  so the header and content align between them.
- Reword extern/@export FFI docs from "C-ABI" to "native" for consistency with
  the site's no-C wording.

Verified in-browser at 375px and 1280px: vertical pipeline, click→card→new-tab,
outside-click close, correct reference nav, aligned container; 158 examples still
compile.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-29 18:42:40 +03:00
c93eca80af docs(site): mobile-first revamp of all pages
All checks were successful
docs / build-and-deploy (push) Successful in 2s
Rewrite both stylesheets mobile-first (base = phone; @media min-width
progressively enhances) and add a responsive nav.

- Hamburger menu on small screens: links collapse into a clean, uniform
  dropdown list (plain rows + separators, no out-of-place boxed buttons);
  full inline nav with boxed CTA returns at >=720px. Auto-wired for every page.
- Fix horizontal-overflow root causes: min-width:0 on grid/flex code containers,
  pre/sig scroll internally, long names wrap (overflow-wrap). 0 page overflow on
  every page type at 375px.
- Stack layouts on mobile: single-column hero/features/steps/index grid,
  column parameter cards, 2-col color swatches; multi-column returns on wider
  screens. Reduced hero/section padding; fluid clamp() headings.
- Hover cards gated to hover-capable devices so a tap just navigates.
- Desktop layout preserved (verified at 1280px: inline nav, 2-col hero, 3-col
  features, no regression).

Verified in-browser at 375px and 1280px across landing, API index, item
(method/keyword/annotation/phase), namespace overview, and color pages.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-29 18:22:55 +03:00
3c7ec9b016 docs(api): per-symbol pages, fuzzy search, deep token linking, hover cards
All checks were successful
docs / build-and-deploy (push) Successful in 2s
Rebuild the API Reference around one page per symbol and richer, verified content.

Pages & navigation
- One HTML page per symbol (kw-*, type-*, phase-*, screen-*, fn-*, annot-*, op-*)
  instead of a single scrolling page; namespace overview pages (ns-screen …
  ns-color) and a searchable index (api.html) with client-side fuzzy search.
- Sticky-header scroll offset (scroll-margin) so a jumped-to entry/param/color is
  never hidden, plus a flash highlight on the scrolled-to target.

Deep linking in every snippet & example
- Namespace members split: `Screen`→namespace page, `fill_rectangle`→method page;
  `Color`→palette page, `Charcoal`→its swatch — separately.
- Named arguments (`width:`) link to that parameter's anchor on the method page.
- Hover any token for a summary card built from the real API data (symbols.json).

Content & coverage
- Full authoritative surface documented from the compiler: every keyword, type,
  the 6 phases (Start/Input/FixedUpdate/Update/LateUpdate/Render, each its own
  page), all 22 annotations, namespace methods with parameter docs, builtins,
  the world_* reflection ABI, networking, operators — 155 symbols.
- Longer, clearer explanations; "model"/"model instance" terminology, not "entity";
  descriptive identifiers in every example (Position{column,row}, Velocity{delta_x,
  delta_y}, Health{current,maximum}, Player/Enemy) — no Pos/Seg/x/dx.
- Accuracy fixes from compiler ground-truth: world_count() takes no arg,
  world_query_next(property, cursor) arg order, event fields bind by name; dropped
  `when` and `module` (not in the self-hosted parser).

Tooling
- inventory.json + check.py: coverage guard (every symbol has a page), duplicate-
  token guard, and broken-link guard — fail CI so docs can't drift.
- validate.py: compiles every ```ludic example against bin/ludicc (158 compile).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-29 17:53:22 +03:00
25f987e30d docs: add Networking section + process/OS builtins (net_send, owner, is_server, serialize/apply, arg, exit, run, getenv, read_char, file_*)
All checks were successful
docs / build-and-deploy (push) Successful in 1s
Sourced from the compiler's confirmed builtin set and NETWORKING-DESIGN.md, so
the reference reflects the current language surface. 93 -> 104 symbols.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-29 16:39:29 +03:00
ccef707e4c ci(docs): build in a python:3.12 container, clone source directly
All checks were successful
docs / build-and-deploy (push) Successful in 11s
node:20-bookworm (the runner image) has no python3 and setup-python could not
resolve; run the pure-Python generator in a python image and git-clone the
public repo instead of relying on node-based actions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-29 16:33:25 +03:00
bc32f1a9af ci(docs): use actions/setup-python so the generator runs on any runner image
Some checks failed
docs / build-and-deploy (push) Failing after 54s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-29 16:30:21 +03:00
51ddfa3ce9 docs: automated documentation pipeline (per-symbol source → pages)
Some checks failed
docs / build-and-deploy (push) Failing after 38s
Replace the hardcoded landing page and minimal reference with a generated
documentation site driven by a single source of truth.

- docs/language/**: one file per symbol (93 keywords/types/builtins/namespace
  methods/operators/annotations), each with front-matter (id, kind, tokens,
  sig, tip) + description + a ```ludic example. Seeded by exploding the former
  inline SECTIONS list; these files are now the source of truth.
- docs/site/: site.json (editable hero/features/showcase/messaging, not
  hardcoded) + snippets/*.ludic (real programs shown on the landing page).
- tools/docgen/gen.py: generates index.html, api.html, ludic-highlight.js and
  symbols.json. The highlighter's symbol tables, hover tips and jump anchors
  are GENERATED from the per-symbol files — add a symbol and it is recognized,
  tipped and linked in every snippet automatically. Python stdlib only.
- tools/docgen/check.py: verifies the pages contract + that no snippet token
  links to a missing reference anchor.
- .forgejo/workflows/docs.yml: rebuilds and publishes to the pages branch on
  every push to main touching the docs sources.

Consumes the new Screen.*/Color.*/named-arg API and the 221-color palette.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-29 16:25:54 +03:00
a3a1e4d160 Language: named colors, namespaced Screen/Input/Random/Map API, named args
An opinionated, game-facing surface for the language:
- Color.<Name>: 221 named colors resolved to 0xRRGGBB at compile time
  (selfhost/emit_color.ludic).
- Namespaced builtins Screen.* / Input.key / Random.* / Map.*, so calls read as
  subject.action; present() -> Screen.show(), clear -> Screen.clear,
  fill_rect -> Screen.fill_rectangle, etc.
- Named arguments, e.g. Screen.fill_rectangle(x:, y:, width:, height:, color:),
  reordered to the callee's parameters at emit time.
- machine/become can run over a named program-scope var, not just a register.
- Migrate examples off numeric registers to named vars; snake/menu/chronorift
  render byte-identical to the goldens and the bootstrap fixpoint is preserved.
- Regenerate the checked-in seed (selfhost/ludicc.seed.ll).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-29 16:05:55 +03:00
9e4837428c Ignore .DS_Store
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-29 15:12:49 +03:00
bca8f126fc Networking N2–N6, and a fully C-free toolchain
Implement the rest of NETWORKING-DESIGN.md (N2–N6) and eliminate every
`.c` file from the repo. clang remains only the LLVM-IR assembler; no C
is compiled anywhere.

Networking (selfhost/emit_net.ludic + parser/emit changes):
- N2 @Sync: per-model serialize/apply + by-kind dispatchers; POD-scalar
  compile error and empty-participation warning; selective replication.
- N3 @Owned: @L_owner array + owner/set_owner/is_owner; owners snapshot.
- N4 @ToServer/@ToClients remote events: framed net_send + net_pump re-emit.
- N5 @Server/@Predicted role guards + drivable sim (tick_fixed/tick_render,
  entry-owns-the-loop).
- Built-in loopback transport so multiplayer runs with zero foreign code;
  extern fn net_send/net_poll still overrides it for a real socket.
- N6 blessed runtime (examples/net_rt.ludic) + end-to-end demo (net_demo).
- Fix: llty("entity") is now i32 (entities are i32 handles), so let e = self().

C elimination:
- Networking + foreign-mod-ABI tests rewritten as self-contained pure-Ludic
  programs (examples/net_*, world_*, mod_events, scoped); tests/ removed.
- Reflection ABI exposed to Ludic as world_* builtins (Ludic-to-Ludic modding).
- Formatter rewritten C→Ludic: tools/ludic-tools/fmt.ludic.
- Language server rewritten C→Ludic: tools/ludic-tools/lsp.ludic (lexer, index
  parser, cross-file workspace resolver, JSON, all LSP handlers).
- Obsolete migrate_*.c codemods deleted; ludic_syntax.h kept as vocabulary data.

Suites: ./test.sh 44/44, ./tools/test-tools.sh 28/28 (LSP 42/42), fixpoint holds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-29 15:08:23 +03:00
96d01e45ab Merge Phase 8b: modern primitive names 2026-08-28 02:55:25 +03:00
d3301684f1 Phase 8b: modern names for the raw-memory and OS/IO primitives
Groups B and C of the leftover-primitive cleanup — renames, not new machinery,
and deliberately NO unsafe_ prefix (a __-prefix is itself a C convention, and an
`unsafe` marker carries no signal in a fully-manual-memory language with no safe
subset to contrast against).

  memory:  mem_free -> free   mem_realloc -> resize   mem_set -> fill
           ptr_add -> offset
  process: os_argc -> arg_count   os_arg -> arg   os_exit -> exit
           os_system -> run   os_getenv -> getenv   read_byte -> read_char
  dead:    mem_copy, os_time, write_byte (0 uses) — deleted

Two reseeds: accept both old and new names in the intrinsic dispatch, then
migrate every call site and drop the old names. file_open/read/write/seek/tell/
close are left as-is — they're the domain-prefixed syscall layer wrapped by
read_file, not the argc/argv-style C-ness the audit targeted; a `File` type is a
separate, larger design if wanted.

test.sh's CLI smoke updated (os_exit -> exit); check-vocabulary's grammar marker
moved off the deleted names. Reseeded (22243 lines); C-free fixpoint holds;
goldens identical; 18/18; vocab + doc-fences clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-28 02:55:24 +03:00
6f1ba2c214 Merge Phase 8a: fixeds/ptrs indexing 2026-08-28 02:49:40 +03:00
5a9e8cb2da Phase 8a: finish typed indexing — fixeds/ptrs buffers (retire peekf/pokef/peekp/pokep/str_len)
The last peek/poke pairs were the same "typed buffer access wearing a C name" as
peek8/peek32, so they become indexing too:

  peekf(sc_x, i)     -> sc_x[i]        (a `fixeds` buffer -> a fixed)
  pokep(gc_bmp, s,b) -> gc_bmp[s] = b  (a `ptrs` buffer -> a pointer)
  str_len(s)         -> len(s)         (len is polymorphic since 7f; str_len was dead)

Two new element-typed buffers join words: `fixeds` (32-bit fixed) and `ptrs`
(pointer, 8-byte stride). emit_index_addr dispatches on the base type; IR is
byte-identical to the old intrinsics.

The peekf/peekp buffers (ed_x0/ed_x1/ol_x/sc_x fixed coords; gc_bmp/img_px/
tt_data/ui_text pointer arrays) were retyped scope-aware, then the 33 sites
migrated to indexing. Two bugs found via a menu golden diff / a link-time type
error and fixed: the buffer-name regex truncated digit suffixes (ed_x0 -> ed_x),
and the char-literal brace-miscount skipped a few module declarations (same class
as 7j).

Reseeded (22371 lines); C-free fixpoint holds; goldens byte-identical; 18/18;
vocab (fixeds/ptrs types in, 5 intrinsics out) + doc-fences clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-28 02:49:40 +03:00
ae65acde33 Merge Phase 7k: bytes/words allocators 2026-08-28 02:26:15 +03:00
86948d2b19 Phase 7k: bytes(n) / words(n) allocators (retire mem_alloc)
Allocation reads as intent, not malloc:

  mem_alloc(64)          -> bytes(64)              (64 bytes -> a byte buffer)
  mem_alloc(w * h * 4)   -> words(w * h)           (w*h 32-bit words)

bytes(n) mallocs n bytes and returns a plain pointer (byte-indexed); words(n)
mallocs n*4 bytes and returns a `words` pointer (int-indexed). Since words(X) and
mem_alloc(X*4) allocate the identical number of bytes, the migration cannot change
any allocation size — the `* 4` factor just moves from the argument into the
allocator name, pairing naturally with the Phase-7j `words` retyping
(`var fb: words = words(w * h)`).

Migrated 102 sites (mem_alloc(E*4) -> words(E), else bytes(E)); deleted the
mem_alloc intrinsic. mem_realloc/free/copy/set stay as the low-level
reallocation/free family. Reseeded (22565 lines); C-free fixpoint holds; goldens
byte-identical; 18/18; vocab + doc-fences clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-28 02:26:15 +03:00
5cab68ea2e Merge Phase 7j: words indexing 2026-08-28 02:20:50 +03:00