diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100644 index 0000000..1ebdad0 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,13 @@ +#!/bin/sh +# Sporel API-Doc-Convention pre-commit hook. +SPOREL_EXE="${SPOREL_EXE:-$(command -v Sporel.exe 2>/dev/null || command -v sporel 2>/dev/null)}" +if [ -z "$SPOREL_EXE" ]; then + echo "INFO: Sporel.exe not on PATH. Skipping lint." >&2 + exit 0 +fi +"$SPOREL_EXE" --lint="$(pwd)" --fix +RC=$? +if git diff --cached --name-only | grep -qx 'README.md'; then + git add README.md +fi +exit $RC diff --git a/README.md b/README.md index 384e3f6..7822434 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,74 @@ -# sporel-lib-core.camera-test +# lib-core.camera-test -P.3.5 test-lib — validates `lib-core.camera` v0.3.0 API contracts (state setters, -bounds-clamping, pan_enabled toggle, pan_invert, engine-surface presence) -via the `engine.test.*` assertion surface. 16 assertions total. +Test-lib for `lib-core.camera` (Tests-as-Libs per ADR-0037). -- Lib-ID: `lib-core.camera-test` -- Version: `0.1.0` -- Kind: `test-lib` (headless; M.run_tests(ctx) invoked by engine test-runner) -- Spec: `meta/docs/superpowers/specs/2026-05-13-p3-5-camera-free-pan-design.md` +**Version:** 0.1.0 +**Lib-ID:** lib-core.camera-test +**Requires:** lib-core.camera v>=0.3.0, lib-core.input v>=0.4.0 +**Tags:** test, camera -## Run +## Topology + + +```mermaid +graph LR + this["lib-core.camera-test"] + lib_core_camera["lib-core.camera"] + this --> lib_core_camera + lib_core_input["lib-core.input"] + this --> lib_core_input + engine["engine.*"] + this --> engine +``` + + +## Coverage Statement + +Per Fault-Injection-Audit-Convention (2026-05-16): + +| Section | Assertion-Count | Coverage | +|---|---|---| +| State setters/getters | 3 | `set_target`/`target`, `set_zoom`/`zoom`, `set_pan_speed`/`pan_speed` | +| Bounds-Clamping | 3 | `set_bounds` clamping, `clear_bounds` -> unclamped, immediate-clamp on bounds-shrink | +| pan_enabled state | 6 | Per-source disable (keys/edge/drag), `bind_pan_keys` auto-enable, toggle false/true | +| pan_invert state | 2 | `set_pan_invert(false)`, `set_pan_invert(true)` | +| Engine-Surface existence | 2 | `engine.input.get_mouse_delta`, `engine.window.size` function presence | +| **TOTAL** | **16** | | + +### Tested-Surface +- `camera.set_target` / `camera.target` (value + bounds-clamping) +- `camera.set_zoom` / `camera.zoom` +- `camera.set_pan_speed` / `camera.pan_speed` +- `camera.set_bounds`, `camera.clear_bounds` +- `camera.bind_pan_keys` (auto-enable side-effect) +- `camera.set_pan_enabled` / `camera.pan_enabled` (toggle false/true) +- `camera.set_pan_invert` / `camera.pan_invert` +- Engine surface: `engine.input.get_mouse_delta`, `engine.window.size` + +### Fault-Injection Audit (2026-05-16) + +Verified via deliberate-mutation in fixture-mirror; mutations reverted after observation. + +| # | Mutation | Caught by | +|---|---|---| +| M1 | `set_target`: x/y assignments swapped | Assertions 1 (`set_target -> target`) + 4 (`set_target clamped to bounds`) fail | +| M2 | `clear_bounds` made no-op | Assertion 5 (`clear_bounds -> unclamped`) fails | +| M3 | `bind_pan_keys` auto-enable removed | Assertion 10 (`bind_pan_keys auto-enables`) fails | + +All 3 mutations caught — no gaps detected. + +### Structurally Untestable +- `camera.begin` / `camera.finish` — render-stack side-effects only observable inside a render hook; headless test-runner has no render context. +- `camera.update(dt)` interaction with per-frame mouse-input — requires live `engine.input` polling state. +- `camera.screen_to_world` — requires live window-size from `engine.window.size`, headless returns 0 (deferred). + +### Deferred +- `enable_pan_edge`, `bind_pan_drag` runtime behavior (covered indirectly via `pan_enabled` toggles). +- `set_edge_threshold` / `edge_threshold` getter/setter pair. + +## Running + +Via `--test-libs=lib-core.camera-test` (Smoke P0-S-batch Stage): ```bash Sporel.exe --libs-dir= --test-libs=lib-core.camera-test @@ -17,22 +76,9 @@ Sporel.exe --libs-dir= --test-libs=lib-core.camera-test Output is TAP version 13 on stdout; exit-code = failure-count (0 = all pass). -## Coverage Statements (2026-05-16 Fault-Injection Audit) +## References -Verified via deliberate-mutation in fixture-mirror; mutations reverted after observation. - -### Caught Mutations - -| # | Mutation | Caught by | -|---|---|---| -| M1 | `set_target`: x/y assignments swapped (`target_x = y; target_y = x`) | Assertions 1 ("set_target -> target", `t.x==100 and t.y==200`) and 4 ("set_target clamped to bounds") both fail | -| M2 | `clear_bounds` made no-op (`do return end` as first statement) | Assertion 5 ("clear_bounds -> unclamped") fails — target remains clamped after clear | -| M3 | `bind_pan_keys` auto-enable removed (`pan_enabled_v.keys = true` commented out) | Assertion 10 ("bind_pan_keys auto-enables") fails — `pan_enabled("keys")` returns false | - -### Uncaught Mutations (Gaps) - -None — all 3 mutations caught. - -### Tested Production-Lib Surface - -`lib-core.camera` v0.3.0 — Tests cover behavior of: `camera.set_target`/`target` (value + bounds-clamping), `camera.set_zoom`/`zoom`, `camera.set_pan_speed`/`pan_speed`, `camera.set_bounds`, `camera.clear_bounds`, `camera.bind_pan_keys` (auto-enable side-effect), `camera.set_pan_enabled`/`pan_enabled` (toggle false/true), `camera.set_pan_invert`/`pan_invert`. Engine surface: `engine.input.get_mouse_delta`, `engine.window.size` function presence. +- ADR-0037 (Tests-as-Libs Convention) +- ADR-0038 (API-Doc-Convention) +- Production-Lib: `~/Projects/Sporel/sporel-libs/lib-core/camera/README.md` +- Spec: `meta/docs/superpowers/specs/2026-05-13-p3-5-camera-free-pan-design.md` diff --git a/scripts/install-hooks.sh b/scripts/install-hooks.sh new file mode 100644 index 0000000..2ff6e41 --- /dev/null +++ b/scripts/install-hooks.sh @@ -0,0 +1,3 @@ +#!/bin/sh +git config core.hooksPath .githooks +echo "Hooks activated."