Install
Download cranberry-forge-latch-0.1.0.tgz from the showcase and install the local archive:
npm install ./cranberry-forge-latch-0.1.0.tgz three@0.180.0import { Latch } from "@cranberry-forge/latch";This is an ESM package with complete TypeScript declarations. Its peer dependency is Three.js >=0.180.0 <0.181.0; validation uses 0.180.0. The archive is independently usable. No other Cranberry Forge package is required. An npm registry publication is not required for archive installation.
In a browser without a bundler, serve the extracted package alongside Three and provide an import map before your module:
<script type="importmap">
{
"imports": {
"three": "/vendor/three/build/three.module.js",
"@cranberry-forge/latch": "/packages/latch/index.js"
}
}
</script>Selection and timing contract
- Update registered roots, their ancestors, and occluder world matrices. Recursively raycast registered roots with Three's default layer 0.
- Resolve each descendant hit to its nearest registered ancestor, so nested interaction roots remain distinct. Ignore objects hidden by themselves or an ancestor.
- Keep surface hits within actor reach. Test explicit occluders along both the aim ray and the segment from actor origin to the target surface. The selected target's own descendants do not occlude it. Other scene objects are not implicit occluders; register actual collision scenery explicitly.
- Choose the closest eligible aim hit. Preserve the previous target within
focusTolerance; initial equal-distance ties use registration order. A blocked condition keeps focus, so the host can explain the reason instead of selecting through it. - Apply input transitions and evaluate availability. Return events; host mutations happen afterward.
The first observed pressed: true counts as a fresh press if a target is acquired immediately. Once input is held, acquiring or switching to another target cannot activate it. Switching directly from an existing focus on the same frame as a new press also requires a release. This prevents a replacement target from consuming the old gesture.
A hold starts at zero: its first pressed update receives no dt credit, because that elapsed time preceded the observed press. Later held updates accumulate the supplied time. Completion occurs once at or beyond the duration. Continuing to hold never repeats an activation. Release, aim loss, lost reach, occlusion, suspension, removal, and unavailable conditions cancel progress. Conditions are checked during focus updates and again immediately before completion. Input held through a condition change cannot automatically start again.
dt is not clamped internally. Supply your simulation's time policy. A long active update can complete a hold; suspend or clamp elapsed time in the host when opening menus, hiding the page, or recovering from a long stall. No wall-clock timestamps or timers are consulted, so a recorded sequence of inputs and geometry is reproducible.
Ownership and limits
- Latch owns: registration records, previous focus, current held gesture, elapsed hold time, and release gating.
- The host owns: Three scene objects, input adapters, actor movement and collision, aim construction, rendering, UI, inventory, animation, saves, networking, and gameplay effects. Availability checks must be pure and synchronous.
- Scope: local contextual interactions, not a physics engine, pointer event propagation system, menu framework, or network authority. No automatic DOM binding, line-of-sight cache, aim assistance, or spatial index is included.
- Raycasting: normal Three mesh/material/layer rules apply, including face sidedness and custom
raycast()behavior. Target roots can contain lines, sprites, or instanced meshes, but selection is by registered root; individual instances are not independent targets. Set a camera for Sprites. Alpha texture holes and visual transparency do not automatically become collision holes. - Geometry: actor reach is measured to a hit surface, not a root pivot or a bounding sphere. Occlusion is a ray/segment test, not a capsule sweep or pathfinding test. The host chooses a sensible actor interaction origin and occluder geometry.
- Performance: each update raycasts registered geometry and explicit occluders; per-candidate actor visibility tests can be expensive in large scenes. Use simple interaction/collision meshes, register a local set, and profile your content. No allocation-free or large-scene performance claim is made.
- Lifetime: call
removefor collected/destroyed targets. Preserve real physical button state across target changes. Applyactivateevents afterupdatereturns; Latch never calls an activation callback during scene traversal.