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:42:22 +02:00
parent 15edf657ee
commit abe8dfa1bf
3 changed files with 231 additions and 16 deletions

231
README.md
View File

@@ -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
<!-- topology:start (auto-generated; do not edit) -->
```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
```
<!-- topology:end -->
## 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)