docs: migrate README to API-Doc-Convention (S2-cascade.libs)

Restructured per ADR-0038: Abstract + bold-list Badges + Topology H2
+ H3-API-Subsections (one per public function) + Conventions/Consumer
pattern/CHANGELOG/References.

Topology auto-populated via Sporel.exe --lint --fix. Pre-commit hook
installed via --install-hooks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Axel Meyer
2026-05-16 16:36:43 +02:00
parent fd90275e7e
commit 0858fdf401
3 changed files with 257 additions and 16 deletions

13
.githooks/pre-commit Normal file
View File

@@ -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

257
README.md
View File

@@ -1,33 +1,258 @@
# lib-core.camera # lib-core.camera
P.0 2D camera state + begin/finish wrapper. Holds `target = {x, y}` + `zoom`, 2D camera state + begin/finish render wrapper, with free-pan (keys/edge/drag), bounds-clamping, and screen-to-world unproject. Wraps `engine.render.camera_2d_begin/end` with current target + zoom.
wraps `engine.render.camera_2d_begin/end` with current state.
- Lib-ID: `lib-core.camera` **Version:** 0.3.0
- Version: `0.1.0` **Lib-ID:** lib-core.camera
- Spec: `meta/docs/superpowers/specs/2026-05-09-p0-lib-camera-design.md` **Requires:** lib-core.input v>=0.4.0
**Tags:** camera, viewport, transform, pan, bounds
Forward-compat stubs (DEPRECATED-MVP) for follow, set_bounds, lerp_to, shake, ## Topology
set_rotation. See `meta/docs/architecture/render-pipeline.md` and
`meta/docs/architecture/map-topology.md §7.3` for full target context. <!-- topology:start (auto-generated; do not edit) -->
```mermaid
graph LR
this["lib-core.camera"]
lib_core_input["lib-core.input"]
this --> lib_core_input
engine["engine.*"]
this --> engine
```
<!-- topology:end -->
## API ## API
- `camera.set_target(x, y)` — set world-coords for screen-center
- `camera.set_zoom(z)` — set zoom (positive number) ### `camera.set_target(x, y)`
- `camera.target() → {x, y}` — current target **Syntax:** `camera.set_target(x: number, y: number) -> void`
- `camera.zoom() → number` — current zoom
- `camera.begin()` — push camera transform (must be inside render hook) **Example:**
- `camera.finish()` — pop camera transform (`end` is Lua keyword) ```lua
camera.set_target(256, 256) -- world-coords for screen-center
```
**Description:** Sets the world-coords the camera centers on. Clamps to current bounds (if set).
### `camera.set_zoom(z)`
**Syntax:** `camera.set_zoom(z: number) -> void`
**Example:**
```lua
camera.set_zoom(1.0)
```
**Description:** Sets zoom factor. Must be a positive number.
### `camera.target()`
**Syntax:** `camera.target() -> number, number`
**Example:**
```lua
local x, y = camera.target()
```
**Description:** Returns current target world-coords.
### `camera.zoom()`
**Syntax:** `camera.zoom() -> number`
**Example:**
```lua
local z = camera.zoom()
```
**Description:** Returns current zoom factor.
### `camera.begin()`
**Syntax:** `camera.begin() -> void`
**Example:**
```lua
camera.begin()
-- draw world here
camera.finish()
```
**Description:** Pushes the camera transform onto the render stack. Must be called inside a render hook.
### `camera.finish()`
**Syntax:** `camera.finish() -> void`
**Description:** Pops the camera transform. Named `finish` because `end` is a Lua keyword.
### `camera.bind_pan_keys(left, right, up, down)`
**Syntax:** `camera.bind_pan_keys(left: string, right: string, up: string, down: string) -> void`
**Example:**
```lua
input.bind("cam_left", { "a" })
input.bind("cam_right", { "d" })
input.bind("cam_up", { "w" })
input.bind("cam_down", { "s" })
camera.bind_pan_keys("cam_left", "cam_right", "cam_up", "cam_down")
```
**Description:** Binds 4 input-actions for keyboard pan. Per-frame summation happens in `update(dt)`.
### `camera.bind_pan_drag(action)`
**Syntax:** `camera.bind_pan_drag(action: string) -> void`
**Example:**
```lua
input.bind("cam_drag", { "mouse_middle" })
camera.bind_pan_drag("cam_drag")
```
**Description:** Binds an input-action that, while held, pans by mouse-delta (zoom-scaled).
### `camera.enable_pan_edge()`
**Syntax:** `camera.enable_pan_edge() -> void`
**Description:** Enables edge-scroll panning: cursor near a window-edge pans the camera at `pan_speed`. Threshold via `set_edge_threshold`.
### `camera.set_pan_speed(n)`
**Syntax:** `camera.set_pan_speed(n: number) -> void`
**Description:** Sets pan-speed in world-units per second for keys + edge-scroll.
### `camera.pan_speed()`
**Syntax:** `camera.pan_speed() -> number`
**Description:** Returns current pan-speed.
### `camera.set_edge_threshold(n)`
**Syntax:** `camera.set_edge_threshold(n: number) -> void`
**Description:** Sets edge-scroll threshold in screen-pixels.
### `camera.edge_threshold()`
**Syntax:** `camera.edge_threshold() -> number`
**Description:** Returns current edge-scroll threshold.
### `camera.set_pan_invert(b)`
**Syntax:** `camera.set_pan_invert(b: bool) -> void`
**Description:** Inverts drag-pan direction. False = "drag world" feel, true = "drag camera" feel.
### `camera.pan_invert()`
**Syntax:** `camera.pan_invert() -> bool`
**Description:** Returns current drag-invert setting.
### `camera.set_pan_enabled(source, b)`
**Syntax:** `camera.set_pan_enabled(source: string, b: bool) -> void`
**Example:**
```lua
camera.set_pan_enabled("edge", false) -- disable edge-scroll only
```
**Description:** Per-source toggle. `source` is one of `"keys"`, `"edge"`, `"drag"`. All sources default to enabled.
### `camera.pan_enabled(source)`
**Syntax:** `camera.pan_enabled(source: string) -> bool`
**Description:** Returns current toggle-state for the given source.
### `camera.set_bounds(xmin, ymin, xmax, ymax)`
**Syntax:** `camera.set_bounds(xmin: number, ymin: number, xmax: number, ymax: number) -> void`
**Example:**
```lua
camera.set_bounds(0, 0, 4096, 4096)
```
**Description:** Sets world-space bounds. Once set, `set_target` and `update` clamp to keep the target inside. `xmin < xmax` and `ymin < ymax` required.
### `camera.clear_bounds()`
**Syntax:** `camera.clear_bounds() -> void`
**Description:** Removes any active bounds; camera becomes free-roaming.
### `camera.bounds()`
**Syntax:** `camera.bounds() -> number, number, number, number | nil`
**Description:** Returns `xmin, ymin, xmax, ymax` if bounds active, else `nil`.
### `camera.update(dt)`
**Syntax:** `camera.update(dt: number) -> void`
**Example:**
```lua
function update(ctx, dt)
camera.update(dt)
end
```
**Description:** Per-frame pan summation: applies keys + edge + drag contributions (each gated by `pan_enabled[source]`), then clamps to bounds.
### `camera.screen_to_world(sx, sy)`
**Syntax:** `camera.screen_to_world(sx: number, sy: number) -> number, number`
**Example:**
```lua
local mx, my = engine.input.get_mouse_pos()
local wx, wy = camera.screen_to_world(mx, my)
```
**Description:** Inverse of the Raylib 2D camera transform (origin = window-center, no rotation). Useful for picking, world-space cursors. v0.3.0+.
## Conventions
- Y-down-positive per ADR-0031.
- Pan-sources are additive: keys + edge + drag contributions sum each frame.
- Bounds-clamping is always-on once set; use `clear_bounds` to disable.
- Drag-pan is zoom-scaled so on-screen mouse-distance maps to consistent world-distance.
## Consumer pattern ## Consumer pattern
```lua ```lua
local input = require("lib-core.input")
local camera = require("lib-core.camera") local camera = require("lib-core.camera")
camera.set_target(256, 256) -- world-coords
camera.set_target(256, 256)
camera.set_zoom(1.0) camera.set_zoom(1.0)
camera.set_pan_speed(400)
camera.set_bounds(0, 0, 4096, 4096)
input.bind("cam_left", { "a" })
input.bind("cam_right", { "d" })
input.bind("cam_up", { "w" })
input.bind("cam_down", { "s" })
input.bind("cam_drag", { "mouse_middle" })
camera.bind_pan_keys("cam_left", "cam_right", "cam_up", "cam_down")
camera.bind_pan_drag("cam_drag")
camera.enable_pan_edge()
function update(ctx, dt) camera.update(dt) end
function render(ctx) function render(ctx)
engine.render.clear_color(...) engine.render.clear_color(0, 0, 0)
camera.begin() camera.begin()
-- draw world here -- draw world here
camera.finish() camera.finish()
end end
``` ```
## CHANGELOG
### v0.3.0 (P.3.5)
- Added `screen_to_world(sx, sy)` — unproject screen-coord to world-coord.
### v0.2.0
- Pan-API (keys / edge / drag, additive, per-source toggle).
- Bounds-clamping (always-on once set; `clear_bounds` to disable).
- Drag-invert + zoom-scaling.
- `update(dt)` for per-frame pan-summation.
### v0.1.0 (P.0)
- Initial release: target/zoom + begin/finish.
## References
- Spec v0.3.0 (P.3.5): `meta/docs/superpowers/specs/2026-05-13-p3-5-camera-free-pan-design.md`
- Spec v0.1.0 (P.0): `meta/docs/superpowers/specs/2026-05-09-p0-lib-camera-design.md`
- Architecture: `meta/docs/architecture/render-pipeline.md`, `meta/docs/architecture/map-topology.md` §7.3
- ADR-0001 (engine knows verbs, libs bring nouns)
- ADR-0031 (pixel-convention: Y-down-positive)
- ADR-0038 (API-Doc-Convention)

3
scripts/install-hooks.sh Normal file
View File

@@ -0,0 +1,3 @@
#!/bin/sh
git config core.hooksPath .githooks
echo "Hooks activated."