Commit graph

13 commits

Author SHA1 Message Date
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
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
cc701013f5 Phase 6e: unify builtin naming (loaders domain-first, set_reg)
Fixed the naming inconsistencies the cohesion audit flagged, converging on the
domain-first style the bulk of the surface already uses (ui_*, text_*, rng_*,
font_load, image_load):

  - load_png    -> png_load       (asset loaders were split: font_load/image_load
  - load_sprites -> sprites_load   were domain-first, load_* were verb-first)
  - setreg      -> set_reg        (missing underscore vs ui_set_int/ui_set_text)

Game builtins resolve to their `rt_` runtime function, so the renames are in
runtime/native (rt_png_load, rt_sprites_load, rt_set_reg) plus the ~100 example
call sites; `become` lowering in emit_machine now emits @fn_rt_set_reg. Purely a
surface rename — every renamed call maps to the same runtime symbol, so behavior
and golden renders are byte-identical.

Vocabulary + docs updated (ludic_syntax.h, grammar, LudicTokens.kt, LANGUAGE.md,
README, SYNTAX-REDESIGN). Reseeded; C-free fixpoint holds; goldens identical;
17/17; vocab clean.

Left as-is: os_argc/os_arg (compiler-internal intrinsics, already namespaced and
consistent with each other; renaming would need a bootstrap dance for little
gain). reg/set_reg keep the getter-bare/setter-set_ shape ui_focused/ui_set_int
already use.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-27 23:38:10 +03:00
dfc17398cf Phase 6b: annotation DSL (@Queries, @Handles) + docs prose pass
@Queries(these: [Prop{constraint}, ...], on: Model) on a handler desugars to the
existing S_QUERY loop: each property binds by its own name, a Prop{...} block
qualifies its bare fields to that property, and on: adds a {Model} tag filter.
Implemented via parse_queries_anno + qualify_fields (parse_game.ludic), wired
into parse_one_decl; @Handles parses on the program (documentation).
examples/annotations.ludic demonstrates it (output 3 1 0 0); test.sh 15/15.

Docs: added the Annotations section to LANGUAGE.md and did the vocabulary prose
pass (component->property, archetype->model, system->handler, game->program)
across the docs. check-docs + test-tools green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-27 18:49:24 +03:00
42f955fd22 Phase 6a: rename core vocabulary (game/main/component/archetype/system)
game/module -> program, main -> entry, component -> property,
archetype -> model, system -> handler. Done via a transitional self-hosting
bootstrap: parser accepts both -> reseed -> compiler source moved to new
keywords + parser tightened to new-only -> reseed (fixpoint holds). Old keywords
now rejected.

Token-safe corpus migration (tools/ludic-tools/rename_kw.c) leaves the LLVM
@main/entry: labels in emit strings untouched; goldens byte-identical. Editor
vocab (header/JetBrains/TextMate/emacs), check-docs wrapper, and doc fences
updated; error string 'unknown component' -> 'unknown property'. test.sh 14/14,
test-tools 28/0, check-vocabulary green. (Doc prose pass to follow.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-27 18:22:13 +03:00
9585405dc0 Phase 5: anchor the editor vocabulary to the compiler
check-vocabulary.py now extracts the keywords selfhost/parse*.ludic actually
dispatches on and requires the header's declaration/clause keywords to be a
subset (LUDIC_KW_RESERVED exempts documented-but-unimplemented scene/layer/on/
start, itself checked so an implemented reserved word must be promoted). Closes
the highlighted-but-unparsed loop against the real compiler; verified it catches
an injected bogus keyword.

Reconciled the drift it exposed across ludic_syntax.h, JetBrains, TextMate, and
emacs: dropped scene/layer/on/start (->RESERVED), the never-implemented
needs/uses/requires/ensures/invariant/effects clauses, and the retired
edge/export/pure prefixes; added enum/main. @annotations highlight generically.
Tooling-only (no seed change): test.sh 14/14, check-vocabulary + test-tools 28/0.

Phases 1-5 complete.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-27 16:35:46 +03:00
f87964c5f7 Phase 4b: enum types name magic-int value spaces
enum Name { A, B, C } declares named int constants; Enum.Variant is a
compile-time int (index by declaration order), usable in match patterns,
comparisons, setreg. New: parse_enum + dispatch, enum_ordinal resolver
(emit_core), Enum.Variant handling (emit_expr). Scoped to a naming layer over
int (no enum-typed variables), so register/save semantics are untouched.

combat.ludic battle menus dispatch on KnightAct/MageAct instead of 0..3;
chronorift golden byte-identical. Editor vocab (ludic_syntax.h, JetBrains,
TextMate, emacs) gained enum, dropped retired edge/export/pure decl keywords;
check-vocabulary + test-tools 28/0. when/if kept by choice; bitwise stays
functions. Reseeded, fixpoint holds, test.sh 14/14.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-27 16:29:02 +03:00
9a558d6b58 Phase 3d: declaration modifiers become @annotations (Rule A)
edge/pure/export prefix keywords retired; modifiers are now leading @annotations
(@export, @edge, @pure, @deterministic). parse_one_decl collects a leading @anno
run and @export sets the fn export flag; dead edge-dispatch removed from
parse_system. Migrated examples/lib/combat.ludic; old prefixes now rejected.

Behavior-identical: the export flag is parse-only in the self-hosted emitter
(emits @fn_<name> for every fn, never reads the flag), so IR is byte-identical.
Reseeded, fixpoint holds, test.sh 14/14. Docs updated. Phase 3 complete.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-27 16:16:16 +03:00
b643037d8f Phase 3b+3c: ui props use ':'; drop dead needs/uses synonyms (Rule A)
3b: widget props go from key=value to key: value (parse_widget). emit_ui and
ludic-fmt unchanged (AST-identical; ':' formats correctly by default). Chose the
colonized form over parenthesized named-args — minimal churn, no new grammar.
Migration: tools/ludic-tools/migrate_ui.c. menu golden byte-identical.

3c: removed the undocumented, unused 'needs'/'uses' clause synonyms from
parse_system; reads/writes stay.

Reseeded, fixpoint holds, test.sh 14/14, check-docs + check-vocabulary green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-27 16:03:35 +03:00
39bd430ce8 Phase 3a: spawn/record initializers use ':' (Rule A)
Comp = { f = v }  ->  Comp { f: v }. record() requires ':' and parse_spawn()
drops the '=' before the record; '=' is now assignment/binding only. Records
appear only in games, so the compiler seed is unaffected.

Migration: tools/ludic-tools/migrate_records.c (spawn-context aware). Verified
every golden byte-identical, old '=' form rejected, reseeded, test.sh 14/14.
Docs updated (LANGUAGE.md, BOOTSTRAP.md R2).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-27 15:58:00 +03:00
eac8f8f335 Phase 2: enforce statement separators (Rule B)
The parser now requires a newline or ';' between statements (block() in
selfhost/parse.ludic); two statements may no longer sit adjacent with only
spaces. Also fixed if-without-else swallowing its trailing separator.

Migration: tools/ludic-tools/migrate_separators.c inserts ';' at statement
boundaries corpus-wide (examples, runtime, 25 self-host fragments, ~1100
boundaries). Verified semantically identical — the migrated compiler compiles
itself to IR byte-identical to the pre-migration seed, and every golden game
renders identically. Reseeded to the strict compiler; C-free fixpoint holds;
test.sh 14/14.

Docs: Rule B documented in LANGUAGE.md; BOOTSTRAP.md R1 + stale fences updated;
check-docs green across all docs. Fixed a multi-line string literal in
emit_expr.ludic (byte-identical \n escape) that the C toolchain lexer mis-lexes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-27 15:41:29 +03:00
985f9ad8f2 Baseline: Ludic compiler + toolchain, Phase 1 syntax fixes complete
Self-hosted compiler (selfhost/*.ludic), runtime, examples, editor tooling,
and docs. Phase 1 of the syntax-redesign cohesion pass has landed:
edge-system fix, signature-query, when-alias, and the documentation truth-pass.
Suite green (14/14), C-free bootstrap fixpoint holds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-27 15:15:35 +03:00