Merge branch 'flow/m3te/P14.1' into m3te-plan

This commit is contained in:
swipelab
2026-06-06 09:39:15 +03:00

184
docs/std-gaps.md Normal file
View File

@@ -0,0 +1,184 @@
# sx std-library gaps surfaced by m3te
m3te is authored entirely in sx. While building it, a handful of *general-purpose*
utilities had to be hand-rolled because the sx standard library / library modules
don't provide them. This report enumerates those candidates — the ones that
arguably belong in sx `std` (or a library module) — and, for each, records an
explicit cross-check against the real sx library so the list contains **true gaps
only**.
- **sx library checked:** `/Users/agra/projects/sx/library/modules/`
`std.sx`, `math/` (`math.sx`, `vector2.sx`, `matrix44.sx`), `std/`
(`objc.sx`, `cli.sx`, `hash.sx`, `json.sx`, `uikit.sx`, `objc_block.sx`),
`process.sx`, `fs.sx`, `test.sx`, `ui/` (`types.sx`, `animation.sx`,
`image.sx`, …), `gpu/`, `platform/`.
- **m3te source read:** `*.sx` (`main.sx`, `board.sx`, `board_view.sx`,
`board_anim.sx`, `board_fx.sx`, `board_layout.sx`, `gem_anim.sx`, `audio.sx`,
`swipe.sx`, `build.sx`), `tests/test.sx`, `tools/`.
Highest-value candidates first. Borderline items are flagged. A separate
"Already provided" section lists helpers m3te *also* hand-rolled but which the
library **does** ship (so they are honestly **not** gaps) — included because
the step asks for an honest accounting.
---
## Category: Numerics / parsing
### 1. String → number parsing (`parse_s64`, `parse_f32`)
- **m3te location:** `main.sx:156` (`parse_s64`), `main.sx:122` (`parse_f32`).
- **What it does:** decimal ASCII `string``s64` / `f32` (sign + integer +
optional fractional part).
- **Why general-purpose:** the exact inverse of the formatters everyone already
uses; needed by any program that reads numbers from argv, env, config, or a
text file. Nothing game-specific about it.
- **Suggested home:** `modules/std` (alongside `int_to_string` /
`float_to_string`), or a dedicated `modules/strings`.
- **Not already in sx library (checked):** `std.sx` ships only the *forward*
direction — `int_to_string` (`std.sx:68`), `uint_to_string`,
`float_to_string` (`std.sx:121`), `int_to_hex_string` — and **no** parse.
No `atoi`/`strtol`/`strtod`/`string_to_int` anywhere under
`library/modules` (grepped). `std/json.sx` has a `Parser.parse_number`
(`json.sx:547`) but it is a private method consuming a JSON parser's buffer,
not a reusable `string → number`.
### 2. Seedable deterministic PRNG (`Rng` / LCG)
- **m3te location:** `board.sx:55` (`Rng` struct: `next_u32` `board.sx:59`,
`next_range` `board.sx:66`), `board.sx:71` (`rng_seeded`), constants
`board.sx:51-53`.
- **What it does:** a 32-bit linear-congruential generator carried in `s64`
and masked to 32 bits — `next_u32()`, `next_range(n)` (uniform-ish `[0,n)`),
`rng_seeded(seed)`.
- **Why general-purpose:** a seedable, reproducible RNG is a textbook stdlib
primitive (simulations, procedural generation, shuffles, tests). m3te's copy
is written to be host-width-independent; only its *consumer* (`pick_gem`) is
game-specific — the generator itself is not.
- **Suggested home:** `modules/math` (or a new `modules/random`).
- **Not already in sx library (checked):** no `rand`/`rng`/`lcg`/`xorshift`/
`random`/`seed` generator anywhere under `library/modules` (grepped names and
the classic LCG constants `1664525` / `1013904223`). `std/hash.sx` is SHA-256
only (content addressing, not a PRNG). `math/math.sx` has `sin`/`cos`/`sqrt`/
`clamp`/… but no randomness.
---
## Category: FFI / system ergonomics
### 3. NUL-terminated C string (`*u8`) → sx `string` bridge (`from_cstr`)
- **m3te location:** `main.sx:108-117` (`read_env`, copying form: `strlen` +
`cstring` + `memcpy`); `audio.sx:144-148` (viewing form over a `getcwd`
buffer using `c_strlen`). libc `strlen` is re-bound in both `main.sx:28` and
`audio.sx:30`.
- **What it does:** turn a foreign NUL-terminated byte pointer into an sx
`string` — either by copying (`strlen` + alloc + `memcpy`) or by building a
length-bounded view.
- **Why general-purpose:** *every* FFI surface that returns `char*` needs this
(env vars, `getcwd`, `UTF8String`, libc results, …). It is the single most
repeated FFI chore in m3te and has no game content.
- **Suggested home:** `modules/std` (e.g. `from_cstr(*u8) -> string` plus a
zero-copy `cstr_view`), and a public `strlen` binding so callers stop
re-declaring it.
- **Not already in sx library (checked):** `process.env` (`process.sx:86`)
performs exactly this copy **inline** but does not expose it as a reusable
helper, and re-binds `strlen` privately (`process.sx:28`). `std/objc.sx`
exposes `NSString.UTF8String` returning `[*]u8` (`objc.sx:113`) with **no**
helper to convert it back to a `string`. No public `from_cstr`/`cstr_to_string`
exists.
### 4. `getcwd` binding / `cwd()` helper
- **m3te location:** `audio.sx:29` (`getcwd` `#foreign` binding), used in
`load_system_sound` (`audio.sx:141`) to absolutize a bundle-relative path.
- **What it does:** read the process's current working directory into a string.
- **Why general-purpose:** resolving relative paths to absolute is a routine
filesystem need, unrelated to match-3.
- **Suggested home:** `modules/fs` (next to `basename`/`dirname`) or
`modules/process`.
- **Not already in sx library (checked):** `fs.sx` binds `open`/`read`/`mkdir`/
`rename`/… and ships `basename`/`dirname`/`path_join` but **no** `getcwd`.
`process.sx` binds `getenv`/`popen`/`system` but not `getcwd`. The platform
modules bind only `chdir` (`platform/uikit.sx:20`, `platform/sdl3.sx:14`),
never `getcwd`; `platform/bundle.sx` obtains a cwd by shelling out, not via a
binding. (grepped `getcwd`/`chdir`/`cwd`.)
---
## Category: Graphics (borderline — partly engine-specific)
### 5. Image file / RGBA buffer → GPU texture (`load_texture`, `upload_rgba`)
- **m3te location:** `board_view.sx:132` (`load_texture`: PNG path → texture
handle), `board_fx.sx:104` (`upload_rgba`: in-memory RGBA buffer → texture
handle). Both branch `gpu.create_texture` vs a raw-GL fallback.
- **What it does:** decode an image (via `stbi_load`) or take an RGBA buffer and
upload it as a `.rgba8` texture, returning the handle.
- **Why general-purpose:** "load a PNG into a texture" is needed by any app that
draws sprites; the decode + `create_texture` core is reusable.
- **Suggested home:** `modules/ui/image.sx` (a loader to complement the existing
`ImageView`) or `modules/gpu`.
- **Not already in sx library (checked):** `ui/image.sx` defines `ImageView`
which takes an **already-uploaded** `texture_id` — it does **not** load from
disk. `stbi_load` appears only in m3te, never in `library/modules/ui`
(grepped). The GPU layer exposes `gpu.create_texture` but no file/decode
front-end.
- **Borderline:** the raw-GL fallback branch is engine-specific; only the
"decode → `create_texture`" half is cleanly general. Worth shipping as the
GPU-only path; the GL fallback can stay app-side.
---
## Category: Testing (borderline — a stub exists)
### 6. Failing assertion with source location (`expect`)
- **m3te location:** `tests/test.sx:10` (`expect(cond, msg)` → prints
`FAIL <file>:<line>: <msg>` and exits non-zero via `process.exit`).
- **What it does:** a test assertion that, on failure, reports the caller's
`file:line` and **fails the process** so a harness/CI catches it.
- **Why general-purpose:** every sx test suite needs a failing assertion;
m3te's whole `tests/` gate is built on this one helper.
- **Suggested home:** `modules/test`.
- **Partially exists (checked):** `modules/test.sx` ships only
`assert(condition)` which prints `"assertion failed\n"` and **does not exit**
(`test.sx:3-7`) — useless as a gate. `process.assert(cond, msg)`
(`process.sx:148`) *does* report `file:line` and exit 1, so the capability is
half-present but split awkwardly: the dedicated test module can't fail a run,
and the failing variant lives in `process`. A real `modules/test` `expect`/
`assert` that reports location and fails is the gap.
---
## Already provided by the sx library — NOT gaps (honest accounting)
m3te also re-implemented or could have reused these; the library **does** ship
them, so they are explicitly **not** counted above:
- **`clamp` / `lerp` / `min` / `max` / `abs` / `sign`** — m3te uses these
throughout (e.g. `gem_anim.sx:45`, `board_layout.sx:20`, `swipe.sx:31`) and
they all come from `math/math.sx:11-37`. Not hand-rolled; not a gap.
- **`read_env`** (`main.sx:108`) — duplicates `process.env` (`process.sx:86`)
almost line-for-line. m3te re-declares its own `getenv` (`main.sx:27`) instead
of importing `process`, but the capability exists. Not a gap (it motivates #3,
the *reusable* `from_cstr` underneath it).
- **`ease_out_cubic` / `ease_in_quad`** (`board_anim.sx:28-29`) — identical to
`ui/animation.sx:19` and `ui/animation.sx:13`. Re-implemented only because
`board_anim` imports `ui/types` but not all of `ui/animation`. Not a gap.
*Placement note:* these are pure curves and arguably belong in `modules/math`
too, so non-UI code can reach them without pulling in the UI module.
- **`fx_lerp_u8` / per-channel colour lerp** (`board_fx.sx:97`) — `Color.lerp`
(`ui/types.sx:124`) already lerps all four channels. Not a gap.
- **NSString / `NSLog` bridging** (used in `audio.sx`) — `std/objc.sx` ships
`NSLog` (`objc.sx:75`) and an `Into(*NSString) for string` (`objc.sx:119`),
which m3te uses via `xx`. Not a gap.
---
## Summary (top gaps, ranked)
1. **String → number parsing** (`parse_s64` / `parse_f32`) — clean, universally
needed, the missing inverse of the existing formatters.
2. **Seedable PRNG** (`Rng` LCG) — textbook stdlib primitive, entirely absent.
3. **`from_cstr` C-string bridge** (+ public `strlen`) — the most-repeated FFI
chore; `process.env` inlines it but never exposes it.
4. **`getcwd` / `cwd()`** — only `chdir` is bound; the read side is missing.
5. **Image-file → GPU texture loader** — borderline; the decode-and-upload core
is general, the GL fallback is app-side.
6. **`expect` test assertion** — borderline; capability is split between a
non-failing `test.assert` stub and `process.assert`.