ludic/packages/ludic.aim/README.md
Orkuncakilkaya 56e4b6d688 feat(senses): walkers slide round still things, and lines of sight are the world's to answer
- ludic.wildlife: WildlifeWorld.push / pushed_x / pushed_z (defaults change nothing); a ground
  walker's step is slid clear of a still thing with a body from its species' size and scale, and
  a step that gets nowhere turns away as the water does. Birds are not pushed; no dice added.
- ludic.npc: NpcWorld.push / pushed_x / pushed_z (a person 0.35 m round) in npc_walk, and
  NpcWorld.clear (default true), which npc_line_ok asks at 1 m so a detour does not cut a trunk.
- ludic.aim: AimView.clear replaces AimView.ground and the ground march; the pick asks the line
  a body's width short of where the ray enters the thing (aim_clear_at(t, back)), so a solid
  thing does not hide itself. probe.ludic (the ground-agreement probe) and AimView.dry are gone.
- ludic.photo: PhotoLens.clear replaces PhotoLens.ground, PhotoLens.trunk and the march.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 00:59:39 +03:00

61 lines
2.7 KiB
Markdown

# ludic.aim
What the crosshair is on, and which thing the use key means. The crosshair is the exact middle of
the screen and the projection is symmetric, so the ray through it is the camera's position along its
forward - no unprojection. Each candidate is an upright cylinder widened by a fixed angular
forgiveness, and the pick is the nearest one the ray enters that nothing hides: the line up to
where the ray enters it is asked of the game (`clear`), so a thing that is itself solid does not
hide itself. Uses
[`ludic.base`](../ludic.base/README.md) and nothing else.
```ludic
import "ludic.aim"
```
The package keeps no list of things: the game offers every candidate each frame (`aim_offer`), keeps
the object behind the id it gave, and draws the rim and the prompt.
## The rule it keeps
**The crosshair decides WHICH, the distance decides WHETHER** (`aim_use(kind)`):
| | |
| --- | --- |
| under the crosshair and in reach | `AIM_USE_THIS` - that, whatever else is nearer |
| under the crosshair and too far, within `AIM_USE_MAX` (10 m) | `AIM_USE_NOTHING` - and the prompt says why |
| nothing of that kind aimed at, or aimed past 10 m | `AIM_USE_NEAREST` - proximity is right again |
Every reach is floored at `AIM_REACH_MIN` (3 m) by `aim_reach_of(reach)`: a per-kind reach chosen
against its model was never chosen against standing between two props and looking at one. Anything
that asks "am I close enough to this" asks `aim_reach_of`.
## The port
```ludic
export port AimView {
x, y, z, fx, fy, fz, fov, # the camera (the ray)
clear: fn(ax, ay, az, bx, by, bz) -> bool, # nothing between: the ground and every still thing
body_x, body_z # distances and reach are the body's, not the camera's
}
```
Every member has a default: a camera 1.5 m up at the origin looking down -z at nothing in the way.
## API
| | |
| --- | --- |
| `aim_begin()`, `aim_offer(kind, id, x, y0, y1, z, r, reach, far) -> bool` | a frame's candidates: true when this one is now the pick (`reach` `AIM_NEVER` is never in reach) |
| `aim_kind()` (`AIM_NONE` for nothing), `aim_id()`, `aim_dist()`, `aim_in_reach()`, `aim_t()`, `aim_off_centre()` | the pick |
| `aim_use(kind)` -> `AIM_USE_THIS` / `_NOTHING` / `_NEAREST`, `aim_reach_of(reach)` | the use key |
| `aim_ray_cyl(cx, y0, y1, cz, r)`, `aim_slack_m(dist)`, `aim_clear_to(x, y, z)`, `aim_clear_at(t, back)`, `aim_range(x, z)`, `aim_miss(cx, cz, r)` | the geometry, for a game's own probes (`aim_clear_at`: the ray clear to `back` short of t along it) |
## Tests
```bash
ludic test packages/ludic.aim
```
A camera over flat ground with a ridge: the ray against a cylinder, the forgiveness, near before
far, the ridge hiding a thing and a solid thing not hiding itself, the reach floor and the three
answers for the use key.