# AGENTS.md Guidance for coding agents and contributors working on Crafter.Math itself. The [README](README.md) is the user-facing manual, and [AGENTS.consumer.md](AGENTS.consumer.md) is what agents working in a *consumer* of this library read: crafter-build copies it into the dependency-notes section of the consumer's `AGENTS.md` on every build. When any of the three disagrees with the code, the code wins. ## Project overview Crafter.Math is the Crafter engine's math library, shipped as one C++ module, `Crafter.Math`, with seven partitions. It has no implementation units and no dependencies: `project.cpp` declares a static library whose only sources are the interfaces, so every function is a template or an inline body that a consumer instantiates with its own target and `-march`. The library is the bottom of the Crafter stack: Crafter.Asset, Crafter.Graphics and crafter-build's own `project.cpp` all load it from its default branch. | Path | What lives there | |---|---| | `interfaces/Crafter.Math.cppm` | The umbrella: `export import`s every partition. | | `interfaces/Crafter.Math-.cppm` | One partition per file, see below. | | `project.cpp` | The build description: the library plus the test matrix (`AddMarchVariants`). | | `tests//main.cpp` | One test program per directory: `Vector`, `Matrix`, `Quaternion`, `Intersection`. | | `AGENTS.consumer.md` | Notes for consumers' agents; crafter-build injects them into consumers' `AGENTS.md`. This file never gets such a section itself, since the library has no dependencies. | | `build/`, `bin/` | Generated and gitignored. `build/test-logs/` keeps the output of failed tests. | Partitions, in import order: - `:Common` picks the SIMD register type per ISA and holds what the SIMD vectors share: `VectorBase` with `VectorType`, `Max`, `MaxElement`, `AlignmentElement`, `BatchSize` and `OptimalPacking`, the shuffle/blend/negate mask builders, and the sin/cos polynomial helpers for SSE, AVX, AVX-512 and NEON. The intrinsics headers (`immintrin.h`, `wasm_simd128.h`, `arm_neon.h`, `riscv_vector.h`) are included in the global module fragment of every unit that needs them. - `:VectorF32` is `VectorF32`: five complete copies of the struct in one `#if` chain, one per backend. x86 (`__x86_64`), WebAssembly (`__wasm_simd128__`), RISC-V (`__riscv_vector`), AArch64 (`__aarch64__ && __ARM_NEON`), and the scalar `#else` over `std::array`. - `:VectorF16` is the same surface for `_Float16` with three backends: AVX512-FP16, NEON with ARMv8.2-FP16 arithmetic, and a generic `_Float16` copy for other non-x86, non-NEON targets. Where none applies, `:Basic` declares `using VectorF16 = VectorF32`. - `:Basic` holds that alias and `ToRadian`. - `:Vector` is the plain scalar `Vector`, independent of the rest, with the quaternion functions as statics of `Vector`. - `:MatrixRowMajor` builds on `:VectorF32` and `:Vector`: each row is a `VectorF32<4, 1>`. - `:Intersection` builds on `:VectorF32` and `:MatrixRowMajor`: batched ray, sphere and OBB tests. Numbers that follow from `:Common`, which the tests and consumers depend on (checked against the built PCMs on 2026-10-31): | Backend | Register (`Max`) | `MaxElement` (floats) | `VectorF32<3,1>::OptimalPacking` | `VectorF32<3, Opt>`: size, `BatchSize` | |---|---|---|---|---| | x86 AVX-512 (`x86-64-v4`, `sapphirerapids`) | 64 B | 16 | 5 | 64 B, 3 (15 lanes) | | x86 AVX2 (`x86-64-v3`), and the scalar fallback | 32 B | 8 | 2 | 32 B, 4 (8 lanes) | | AArch64 NEON, WebAssembly SIMD128 | 16 B | 4 | 1 | 16 B, 4 | | RISC-V RVV | 16, 32 or 64 B from the march's `Zvl*` | 4, 8 or 16 | | | `VectorF32` is one 16-byte register on every backend, with `AlignmentElement` 4 and `BatchSize` 4. `Vector` is 12 bytes aligned 4; `Vector` is 12 bytes aligned 1 (packed); `Vector` is 16 bytes. `MatrixRowMajor` is 48 bytes and the 4x4 is 64, both aligned 16. With AVX512-FP16, `VectorF16<3, 1>` is a real struct with `AlignmentElement` 8 and `MaxElement` 32; on the other x86 tiers it is the alias. ## Dev environment crafter-build is the only tool, from its Flatpak (`alias crafter-build='flatpak run net.catcrafts.CrafterBuild'`; a locally built one runs with `--branch=local`). The library has no dependencies, so nothing is fetched. The host is Linux x86_64; cross targets need their sysroot or toolchain on the host. Things about the Flatpak sandbox that cost time before: - It sees the host filesystem but not the host's `PATH`. Inside, `PATH` is `/app/bin:/usr/bin` and the host's `/usr/bin` is at `/run/host/usr/bin`. A runner probe for `qemu-aarch64` or `wine` fails there, which crafter-build reports as a *failed* test ("runner unavailable"), not a skip, and it builds nothing for that test. - A host binary from `/run/host/usr/bin/` runs inside the sandbox only when it needs no host shared libraries: `qemu-aarch64-static` and `wine` run, `qemu-aarch64` does not (`libcapstone.so.5` missing). `flatpak-spawn --host` is not permitted. - It has its own `/tmp`. Scratch files for the compiler go under `build/` (gitignored), not under `/tmp`. - Its caches are under `~/.var/app/net.catcrafts.CrafterBuild/cache/crafter.build/---/`, one `std.pcm` per target and march. A native crafter-build uses `~/.cache/crafter.build/` instead. Each test variant has its own directory, `build/-----/`, with the PCMs of all eight interfaces compiled for that tier, their objects and a `std.pcm`. A scratch program against a tier compiles with the Flatpak's clang against that directory; `touch tests//main.cpp && crafter-build -v test '-'` prints the exact command to copy. The shape, for `x86-64-v4`: ```bash flatpak run --command=clang++ net.catcrafts.CrafterBuild -stdlib=libc++ --target=x86_64-unknown-linux-gnu \ -march=x86-64-v4 -mtune=generic -std=c++26 \ -fprebuilt-module-path=$HOME/.var/app/net.catcrafts.CrafterBuild/cache/crafter.build/x86_64-unknown-linux-gnu-x86-64-v4-- \ -fprebuilt-module-path=build/Quaternion-x86-64-v4-x86_64-unknown-linux-gnu-x86-64-v4-generic- \ -O1 build/probe.cpp -c -o build/probe.o # link: the same flags, build//Crafter.Math*.o build/probe.o -fuse-ld=lld -L/app/lib -Wl,-rpath,/app/lib # run: flatpak run --command=$PWD/build/probe net.catcrafts.CrafterBuild ``` ## Build, test and style commands ```bash crafter-build # the static library; little to see, it has no implementation units crafter-build test # every tier for the host target: 12 tests on x86_64 crafter-build test 'Matrix*' # name glob; names are - crafter-build test --list crafter-build test --target=x86_64-w64-mingw32 --runner=cmd:/run/host/usr/bin/wine -- --target=x86_64-w64-mingw32 crafter-build test --target=aarch64-linux-gnu --runner=cmd: -- --target=aarch64-linux-gnu --sysroot=/opt/aarch64-rootfs crafter-build clean ``` `--target=` is given twice for a cross run: before `--` it filters the tests, after `--` it reaches `project.cpp`, which gates the tiers on `cfg.target`. `project.cpp` registers no lint rules, so `crafter-build lint` and `format` exit 1 with "no rules"; style is checked by eye (see Code style). There is no CI for this repository. Before a change is done, `crafter-build test` passes on the host, and on every cross target you can reach when you touched `:Common`, `:VectorF32` or `:VectorF16`. The `` for AArch64 is a script on the host, because an explicit `--runner=cmd:` gets none of the environment the default qemu runner sets, and that default runner (`qemu-aarch64` on `PATH`) cannot be found inside the sandbox: ```sh #!/bin/sh exec env QEMU_LD_PREFIX=/opt/aarch64-rootfs LD_LIBRARY_PATH=/opt/aarch64-rootfs/lib:/opt/aarch64-rootfs/usr/lib /run/host/usr/bin/qemu-aarch64-static "$@" ``` Without it the binaries die with `Could not open '/lib/ld-linux-aarch64.so.1'`, reported as 💥 signal 127. The sysroot is an Arch Linux ARM rootfs with `libc++` 22.1.3 and its `std.cppm`, which the Flatpak's clang 23 accepts. qemu's default CPU has FP16, so both AArch64 tiers run. ## Testing instructions Tests are declared in `project.cpp`, not discovered: the loop over `{ "Vector", "Intersection", "Matrix", "Quaternion" }` calls `cfg.AddMarchVariants(name, mathInterfaces, tiers)` with the tier array of the target's architecture, and each call makes one executable per tier, named `-`, that recompiles all eight interfaces with that tier's `-march` and `-mtune`. That is the point of the matrix: a PCM compiled once with the library's own march would exercise one backend. The tiers are three for x86 (`sapphirerapids` with AVX512-FP16, `x86-64-v4`, `x86-64-v3`), two for AArch64 (`armv8.2-a+fp16`, `armv8-a`) and three for RISC-V (`rv64gcv_zvl512b`, `rv64gcv_zvl256b`, `rv64gcv`). A new test is a new `tests//main.cpp` plus its name in that loop; a new tier is a line in the array. A test is an `int main()`: exit 0 passes, nonzero fails, 77 skips. The four follow one shape: a table of `std::string* TestX()` functions that return `nullptr` on success or a heap-allocated message, and a `main` that runs them in order, prints `[name] message` to stderr and returns 1 at the first failure. Tolerances are `1e-4` in Matrix and Quaternion, `1e-3` in Intersection and `0.01` in Vector, whose `TestAllCombinations` sweeps every `(Len, Packing)` up to `MaxElement` for both `VectorF16` and `VectorF32`. That sweep only exercises shapes whose `Len * Packing` is a power of two; `Len == 3` shapes are covered by the Matrix and Intersection tests and by the Quaternion test's `RotatePivot` cases, which run `VectorF32` and `VectorF16` at `Packing` 1 and at the widest packing a 3-vector and a quaternion share (8 halves on AVX512-FP16, where the `VectorF16` cross product used to break at the 128-bit lane boundary). Open a new test file with a comment on what it guards, as `tests/Quaternion/main.cpp` does, and when a fix changes behaviour, add the case that failed before it. What passes where, as of 2026-10-31 with crafter-build's clang 23.1.2: | Target | How it was run | Result | |---|---|---| | x86_64 Linux, three tiers | `crafter-build test` on a Xeon w7-3465X (has AVX512-FP16) | 12 passed | | x86_64 Windows (`x86_64-w64-mingw32`), three tiers | wine from `/run/host/usr/bin` | 12 passed | | AArch64, two tiers | qemu through the wrapper above | Vector, Intersection and Quaternion pass (6); the Matrix unit does not compile, see Gotchas | | RISC-V | never built here: no riscv64 sysroot (`qemu-riscv64-static` is installed) | unknown | | WebAssembly | no tests declared; the library builds for `wasm32-wasip1` as a dependency of Crafter.Graphics | n/a | The `sapphirerapids` tier only runs on a CPU with AVX512-FP16; elsewhere it dies with an illegal instruction (💥), which is not a logic failure. Failed, crashed and timed-out tests write `build/test-logs/-.log`; a later passing run does not delete them, so check the timestamp before trusting one. Real hardware is driven the same way, through `--runner=cmd: