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>
lib-core.selection
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.
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
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:
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:
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:
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:
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:
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_addadds to set,mod_toggletoggles; no modifier = replace. - v0.1.0 (P.3.10 patch): uses
lib-core.input.was_action_released(no moreprev_click_downworkaround). - Y-down-positive per ADR-0031.
Consumer pattern
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" })
input.bind("mod_toggle", { "ctrl" })
selection.bind_action("lmb")
selection.bind_modifier_add("mod_add")
selection.bind_modifier_toggle("mod_toggle")
local handle = selection.register(function()
return { x = entity.x, y = entity.y, w = entity.w, h = entity.h }
end)
function update(ctx, dt) selection.update(dt) end
function render(ctx)
camera.begin()
-- draw entities
selection.render_highlights() -- world-space
camera.finish()
selection.render_drag_box() -- screen-space
end
CHANGELOG
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_downworkaround removed.
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)