# 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 :: ` 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`.