feat(tools): L10 ludic-fmt enforces a project's style

lint lines in package.ludic - one_statement, max_file_lines, max_function_lines,
max_comment_lines, max_header_lines, paths, baseline - checked by ludic-fmt --check
<files> and ludic-fmt --lint (the project), at the line; a baseline ratchet lets a
rule arrive in a codebase that breaks it (--init-baseline), lowered as it is fixed.
check-impl reads the alias declarations too (it had been blind to every namespace
L6 moved out of the compiler), and eight methods get their pages; a test holds the
formatter to keeping type arguments together (L5).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-24 14:38:35 +03:00
parent e543525eb7
commit 334469ef61
16 changed files with 635 additions and 2 deletions

View file

@ -0,0 +1,20 @@
---
id: app-monitor_count
name: App.monitor_count
category: app
kind: namespace-method
tokens: App.monitor_count
sig: App.monitor_count() -> int
tip: How many displays the machine has.
order: 49
ns: App
member: monitor_count
---
The number of displays attached, for a setting that chooses which one the game opens on; 1 on a platform that reports only the main one.
```ludic
program Screens {
entry { print(App.monitor_count()) }
}
```

View file

@ -0,0 +1,23 @@
---
id: app-window_fixed
name: App.window_fixed
category: app
kind: namespace-method
tokens: App.window_fixed
sig: App.window_fixed(fixed)
tip: A window that cannot be resized or maximised.
order: 51
ns: App
member: window_fixed
---
With `true`, the window has no resize grip and no maximise button - for a launcher or a dialog, which a player should not take for the game itself.
Parameters:
- `fixed` — `true` to fix the window's size, `false` to free it
```ludic
program Launcher {
entry { App.window_fixed(true) }
}
```

View file

@ -0,0 +1,23 @@
---
id: app-window_to_monitor
name: App.window_to_monitor
category: app
kind: namespace-method
tokens: App.window_to_monitor
sig: App.window_to_monitor(index)
tip: Moves the window to that display.
order: 50
ns: App
member: window_to_monitor
---
Moves the game's window onto display `index` (0 is the main one), keeping its size. A no-op where the platform leaves moving windows between screens to the player.
Parameters:
- `index` — which display, 0 to `App.monitor_count() - 1`
```ludic
program Move {
entry { App.window_to_monitor(0) }
}
```

View file

@ -0,0 +1,26 @@
---
id: fs-read_bytes
name: Fs.read_bytes
category: fs
kind: namespace-method
tokens: Fs.read_bytes
sig: Fs.read_bytes(path) -> []byte
tip: A whole file as bytes, or null.
order: 11
ns: Fs
member: read_bytes
---
Reads a whole file into a `[]byte` - bounds-checked, so no raw memory - or returns <code>null</code> when it cannot be read. It reads through the asset pack like every other load, so it finds a shipped game's files. Pair it with <code>text_of(b, n)</code> for text.
Parameters:
- `path` — the file to read
```ludic
program Header {
entry {
let b = Fs.read_bytes("sound.wav")
if b != null { print(b[24]) }
}
}
```

View file

@ -0,0 +1,30 @@
---
id: fs-write_bytes
name: Fs.write_bytes
category: fs
kind: namespace-method
tokens: Fs.write_bytes
sig: Fs.write_bytes(path, data, count) -> bool
tip: Writes the first count bytes of a []byte to a file.
order: 12
ns: Fs
member: write_bytes
---
Writes the first `count` bytes of `data` to `path`, replacing the file, and answers whether all of them were written. `count` is clamped to the slice's length.
Parameters:
- `path` — the file to write
- `data` — a `[]byte`, e.g. from <code>buffer(n)</code>
- `count` — how many bytes of it
```ludic
program Save {
entry {
let b = buffer(2)
b[0] = 72
b[1] = 105
print(Fs.write_bytes("hi.bin", b, 2))
}
}
```

View file

@ -0,0 +1,24 @@
---
id: input-text
name: Input.text
category: input
kind: namespace-method
tokens: Input.text
sig: Input.text() -> string
tip: The text typed this frame.
order: 61
ns: Input
member: text
---
What the keyboard layout, the modifiers and any dead key produced this frame, as UTF-8 - the only right way to fill a name field, because it is the characters the player meant rather than the keys they pressed (a binding reads keys by position; text reads what they type). Empty in a headless run.
```ludic
program Name {
entry {
var name = ""
name = name + Input.text()
print(name)
}
}
```

View file

@ -0,0 +1,23 @@
---
id: time-now_us
name: Time.now_us
category: time
kind: namespace-method
tokens: Time.now_us
sig: Time.now_us() -> long
tip: A microsecond clock, for measuring.
order: 7
ns: Time
member: now_us
---
Returns a steady microsecond count from the platform clock, for timing a piece of work - the difference between two readings is how long it took. It is not the game's time (that is <a href="time-now"><code>Time.now</code></a>) and it does not stop when the game is paused.
```ludic
program Timed {
entry {
let t0 = Time.now_us()
print(Time.now_us() - t0)
}
}
```

View file

@ -0,0 +1,26 @@
---
id: time-sleep_us
name: Time.sleep_us
category: time
kind: namespace-method
tokens: Time.sleep_us
sig: Time.sleep_us(us)
tip: Waits this many microseconds.
order: 8
ns: Time
member: sleep_us
---
Suspends the program for about `us` microseconds - a high-resolution wait, for pacing a loop to a frame rate. No operating system's sleep is exact, so a limiter sleeps most of the wait and spins the last of it.
Parameters:
- `us` — how long to wait, in microseconds (a `long`)
```ludic
program Paced {
entry {
Time.sleep_us(1000)
print(1)
}
}
```