From abe8dfa1bf72ce423591a2fcd17addb91c83434a Mon Sep 17 00:00:00 2001 From: Axel Meyer Date: Sat, 16 May 2026 16:42:22 +0200 Subject: [PATCH] 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) --- .githooks/pre-commit | 13 +++ README.md | 231 ++++++++++++++++++++++++++++++++++++--- scripts/install-hooks.sh | 3 + 3 files changed, 231 insertions(+), 16 deletions(-) create mode 100644 .githooks/pre-commit create mode 100644 scripts/install-hooks.sh 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 aef5a76..725620a 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,211 @@ -# lib-core.selection — v0.1.0 +# lib-core.selection -Click-Select + Drag-Box-Select + Selection-Set-State. +Click-select + drag-box-select + selection-set-state. Handle-registry pattern: modules register AABB-callbacks, the lib polls them during hit-test. State-machine (`idle`/`ambiguous`/`dragging`) disambiguates click-vs-drag via pixel-threshold. Modifier-bindings via action-names (`mod_add`, `mod_toggle`). Render via alpha-tinted draw_rect helpers. -## Quick Setup +**Version:** 0.1.0 +**Lib-ID:** lib-core.selection +**Requires:** lib-core.input v>=0.4.0, lib-core.camera v>=0.3.0 +**Tags:** selection, rts, click, drag-box, hit-test + +## Topology + + +```mermaid +graph LR + this["lib-core.selection"] + lib_core_input["lib-core.input"] + this --> lib_core_input + lib_core_camera["lib-core.camera"] + this --> lib_core_camera + engine["engine.*"] + this --> engine +``` + + +## API + +### `selection.register(aabb_fn)` +**Syntax:** `selection.register(aabb_fn: fun() -> {x: number, y: number, w: number, h: number}) -> handle` + +**Example:** +```lua +local h = selection.register(function() + return { x = entity.x, y = entity.y, w = entity.w, h = entity.h } +end) +``` + +**Description:** Registers a selectable entity. Caller supplies an AABB-callback returning current world-space `{x, y, w, h}`. Returns an opaque handle. + +### `selection.unregister(handle)` +**Syntax:** `selection.unregister(handle: handle) -> void` + +**Description:** Removes a selectable. Also removes from the active selection-set if present. No-op for unknown handles. + +### `selection.count_registered()` +**Syntax:** `selection.count_registered() -> integer` + +**Description:** Returns the number of registered selectables. + +### `selection.list()` +**Syntax:** `selection.list() -> handle[]` + +**Description:** Returns an array of currently-selected handles. + +### `selection.count()` +**Syntax:** `selection.count() -> integer` + +**Description:** Returns the size of the selection-set. + +### `selection.contains(handle)` +**Syntax:** `selection.contains(handle: handle) -> bool` + +**Description:** Returns true iff `handle` is in the selection-set. + +### `selection.is_empty()` +**Syntax:** `selection.is_empty() -> bool` + +**Description:** Returns true iff the selection-set is empty. + +### `selection.add(handle)` +**Syntax:** `selection.add(handle: handle) -> void` + +**Description:** Adds a handle to the selection-set. No-op if already selected or handle unknown. + +### `selection.remove(handle)` +**Syntax:** `selection.remove(handle: handle) -> void` + +**Description:** Removes a handle from the selection-set. No-op if not selected. + +### `selection.toggle(handle)` +**Syntax:** `selection.toggle(handle: handle) -> void` + +**Description:** Adds the handle if absent, removes if present. + +### `selection.clear()` +**Syntax:** `selection.clear() -> void` + +**Description:** Empties the selection-set. + +### `selection.bind_action(action_name)` +**Syntax:** `selection.bind_action(action_name: string) -> void` + +**Example:** +```lua +input.bind("lmb", { "mouse_left" }) +selection.bind_action("lmb") +``` + +**Description:** Binds the primary select-action (typically LMB). Press starts a potential click/drag; release resolves it (click selects the topmost AABB under the cursor; drag selects all AABBs intersecting the box). + +### `selection.bind_modifier_add(action_name)` +**Syntax:** `selection.bind_modifier_add(action_name: string) -> void` + +**Description:** Binds a modifier-action (typically Shift). While held, new selections ADD to the existing set instead of replacing. + +### `selection.bind_modifier_toggle(action_name)` +**Syntax:** `selection.bind_modifier_toggle(action_name: string) -> void` + +**Description:** Binds a modifier-action (typically Ctrl). While held, selections TOGGLE instead of replacing. + +### `selection.set_drag_threshold(px)` +**Syntax:** `selection.set_drag_threshold(px: number) -> void` + +**Description:** Sets the pixel-distance threshold that disambiguates click vs drag. Default ~4 px. + +### `selection.drag_threshold()` +**Syntax:** `selection.drag_threshold() -> number` + +**Description:** Returns the current drag-threshold. + +### `selection.set_enabled(b)` +**Syntax:** `selection.set_enabled(b: bool) -> void` + +**Description:** Master enable/disable. + +### `selection.enabled()` +**Syntax:** `selection.enabled() -> bool` + +**Description:** Returns master-enabled state. + +### `selection.drag_state()` +**Syntax:** `selection.drag_state() -> string` + +**Description:** Returns `"idle"`, `"ambiguous"` (button down but haven't crossed drag-threshold yet), or `"dragging"`. + +### `selection.box_rect()` +**Syntax:** `selection.box_rect() -> {x: number, y: number, w: number, h: number} | nil` + +**Description:** Returns the current drag-box in screen-space, or `nil` if not currently dragging. + +### `selection.set_drag_box_color(r, g, b, a)` +**Syntax:** `selection.set_drag_box_color(r: integer, g: integer, b: integer, a: integer) -> void` + +**Description:** Sets the alpha-tinted fill color of the drag-box overlay. + +### `selection.drag_box_color()` +**Syntax:** `selection.drag_box_color() -> integer, integer, integer, integer` + +**Description:** Returns current drag-box color components. + +### `selection.set_highlight_color(r, g, b, a)` +**Syntax:** `selection.set_highlight_color(r: integer, g: integer, b: integer, a: integer) -> void` + +**Description:** Sets the alpha-tinted color used by `render_highlights` for selected entities. + +### `selection.highlight_color()` +**Syntax:** `selection.highlight_color() -> integer, integer, integer, integer` + +**Description:** Returns current highlight color components. + +### `selection.update(dt)` +**Syntax:** `selection.update(dt: number) -> void` + +**Example:** +```lua +function update(ctx, dt) selection.update(dt) end +``` + +**Description:** Per-frame state-machine: tracks press/release, threshold-crossing, drag-box assembly, hit-test on release, applies add/toggle/replace based on modifiers. + +### `selection.render_drag_box()` +**Syntax:** `selection.render_drag_box() -> void` + +**Example:** +```lua +selection.render_drag_box() -- screen-space, draw after camera.finish() +``` + +**Description:** Draws the alpha-tinted drag-box (screen-space). No-op when not dragging. + +### `selection.render_highlights()` +**Syntax:** `selection.render_highlights() -> void` + +**Example:** +```lua +camera.begin() +-- world entities +selection.render_highlights() -- world-space +camera.finish() +``` + +**Description:** Draws alpha-tinted highlights over all selected AABBs (world-space). Call inside `camera.begin/finish`. + +## Conventions + +- Handle-registry pattern (parallel to `lib-core.interaction`). +- Last-registered = end-of-array; click hit-test picks the last-matching AABB (top-most by registration order). +- Drag-box is screen-space; highlights are world-space. +- Click vs drag disambiguated by pixel-threshold from press-position. +- Modifier semantics: `mod_add` adds to set, `mod_toggle` toggles; no modifier = replace. +- v0.1.0 (P.3.10 patch): uses `lib-core.input.was_action_released` (no more `prev_click_down` workaround). +- Y-down-positive per ADR-0031. + +## Consumer pattern ```lua local selection = require("lib-core.selection") local input = require("lib-core.input") +local camera = require("lib-core.camera") input.bind("lmb", { "mouse_left" }) input.bind("mod_add", { "shift" }) @@ -20,26 +219,26 @@ local handle = selection.register(function() return { x = entity.x, y = entity.y, w = entity.w, h = entity.h } end) --- per-frame: -function update(ctx, dt) selection.update(dt) end +function update(ctx, dt) selection.update(dt) end + function render(ctx) camera.begin() - -- entities… + -- draw entities selection.render_highlights() -- world-space camera.finish() selection.render_drag_box() -- screen-space end ``` -## API +## CHANGELOG -- Registry: `register(aabb_fn) → handle`, `unregister(h)`, `count_registered()` -- Set-Query: `list()`, `count()`, `contains(h)`, `is_empty()` -- Set-Mutation: `add(h)`, `remove(h)`, `toggle(h)`, `clear()` -- Bindings: `bind_action(name)`, `bind_modifier_add(name)`, `bind_modifier_toggle(name)` -- State-Config: `set_drag_threshold(px)` / `drag_threshold()`, `set_enabled(bool)` / `enabled()` -- State-Query: `drag_state() → "idle"|"ambiguous"|"dragging"`, `box_rect() → {x,y,w,h}|nil` -- Visual: `set_drag_box_color(r,g,b,a)` / `drag_box_color()`, `set_highlight_color(r,g,b,a)` / `highlight_color()` -- Per-Frame: `update(dt)`, `render_drag_box()`, `render_highlights()` +### v0.1.0 (P.3.6 + P.3.10 patch) +- Initial release: click-select + drag-box-select + selection-set + add/toggle modifiers. +- P.3.10 patch: uses `lib-core.input.was_action_released`; `prev_click_down` workaround removed. -See `meta/docs/superpowers/specs/2026-05-14-p3-6-lib-selection-design.md` for full design. +## References + +- Spec v0.1.0 (P.3.6): `meta/docs/superpowers/specs/2026-05-14-p3-6-lib-selection-design.md` +- ADR-0001 (engine knows verbs, libs bring nouns) +- ADR-0031 (pixel-convention: Y-down-positive) +- ADR-0038 (API-Doc-Convention) 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."