ludic/docs/language/camera/camera-follow.md
Orkuncakilkaya 31cfbc2465
All checks were successful
bootstrap / cfree-fixpoint (push) Successful in 17s
ci / build-and-test (push) Successful in 1m12s
commit-lint / conventional-commits (push) Successful in 4s
docs / build-and-deploy (push) Successful in 18s
feat(rendering): add Screen.camera/clip/blend_mode/oval + Camera.* + Screen.pixel (#23)
Completes the transform/state-based rendering #23 tracked as blocked on new
renderer state. All of it threads through the two framebuffer chokepoints every
draw primitive already funnels through (rt_put_px / rt_fill_rect), so one place
gives the whole draw API a camera, a clip rect, and a blend mode. Defaults are
neutral — camera (0,0), clip = full screen, blend = replace — so every existing
golden render is byte-identical (the 60+ render tests still pass unchanged).

New renderer state (runtime/native/core.ludic):
  - Screen.camera(x, y) / Camera.set(x, y)   world-space draw offset; a world
                                             point draws at (wx-x, wy-y). Moves
                                             everything — reset to (0,0) for a HUD.
  - Camera.follow(x, y, lerp)                ease the offset toward centring a
                                             target (fixed lerp 0..1)
  - Camera.shake(amount)                     +/- amount jitter from the seeded RNG
                                             (replay shakes identically); 0 clears
  - Screen.clip(x,y,w,h) / clip_reset()      screen-space clip rectangle
  - Screen.blend_mode(m)                     0 = replace, 1 = additive (clamped)

New primitives:
  - Screen.oval(x, y, rx, ry, color)         axis-aligned ellipse outline (midpoint)
  - Screen.measure_text(text) -> int         advance width in the 5x7 font
  - Screen.pixel(x, y) -> int                read a framebuffer pixel (0x00RRGGBB)

Everything stays integer and deterministic (the camera, shake, and blend all
reproduce exactly under identical inputs), so headless renders remain diffable.
Camera.follow interpolates in the fixed domain (fixed*fixed then floor) to avoid
the int*fixed coercion trap.

Screen.pixel makes the whole surface testable by reading rendered pixels back:
examples/library/render.ludic asserts 18 cases — pixel round-trip, camera and
Camera.set/follow offsets, clip in/out + reset, additive blend with 255 clamp,
oval extremes vs hollow centre, and text measurement — all verified against the
actual framebuffer, not just that the call compiled. Wired into x test (now 65
passed). Docs: 7 new Screen pages + a Camera section with 3 pages,
inventory/coverage green. Seed reseeded; the C-free bootstrap fixpoint holds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-31 13:43:01 +03:00

29 lines
1,022 B
Markdown

---
id: camera-follow
name: Camera.follow
category: camera
kind: namespace-method
tokens: Camera.follow
sig: Camera.follow(x, y, lerp)
tip: Ease the camera toward centring a target point.
order: 2
ns: Camera
member: follow
---
Moves the camera a fraction <code>lerp</code> of the way toward centring the world point <code>(x, y)</code> on screen. <code>lerp</code> is a <code>fixed</code> in <code>0.0</code>..<code>1.0</code>: <code>0</code> holds still, a small value trails smoothly behind a moving target, <code>1.0</code> snaps it centred. Call it each frame with the target's position for a classic smooth-follow camera. Deterministic.
Parameters:
- `x`, `y` — the world point to centre on (usually the player)
- `lerp` — how far to move this frame (a `fixed`, 0..1)
```ludic
program Demo {
property Position { x: int = 0, y: int = 0 }
model Player { Position }
handler DrawWorld phase Render {
Camera.follow(Position.x, Position.y, fixed(1) / fixed(8)) # smooth trail
Screen.show()
}
}
```