Skip to content

[M2-SPRITE-03] Atlas UV frame animation hook - #74

Merged
offdev merged 1 commit into
masterfrom
feat/m2-sprite-03-sprite-frames
Oct 4, 2026
Merged

offdev merged 1 commit into
masterfrom
feat/m2-sprite-03-sprite-frames

Conversation

@offdev

@offdev offdev commented Oct 4, 2026

Copy link
Copy Markdown
Owner

Scope (M2-SPRITE-03, nothing else)

The atlas UV frame animation hook — the data-driven half of FR-2.1's "atlas UV animation (sheet frames)":

  • New public header src/laige-render/include/laige/render/sprite_frames.h (header-only, pure math):
    • SpriteFrameLayout — the atlas sheet frame layout in texels: frameWidth/frameHeight, columns/rows, frameSpacing (gap between adjacent frames), sheetBorder (sheet-edge margin). Plain value, no ownership; the documented sheet model: row-major, frame 0 at the top-left, tight sheet 2·border + cols·fw + (cols−1)·spacing.
    • kSpriteFrameMaxAtlasTexels (2^24) — the float-exact atlas domain.
    • spriteFrameUv(frameIndex, layout, atlasWidth, atlasHeight) → Result<SpriteUvRect> — O(1), zero allocation, no logging, no GL; the UV corners are the exactly-rounded k/W values; the invariant u1 > u0, v1 > v0 holds exactly in the domain.
  • Failure contract (CORE-008, API-008, first failure wins): zero extents / atlas outside [1, 2^24] / frameIndex >= columns·rows (never wraps silently — the documented out-of-range error) / frame rect beyond the atlas → InvalidArgument. The layout is caller-owned, untrusted asset metadata (SCALE-004); an adversarial stride is rejected before the col·stride multiplication can wrap u64 (CPP-004).
  • The M3 hook: SpriteItem gains frameIndex (the declared animation frame — the caller sets it and sets uv to the frame's rect via spriteFrameUv; the batcher carries the index through untouched — uv is what the M2-SPRITE-02 renderer draws; M3 animation drives the frame advance on top of this same layout).
  • Batcher delta: SpriteItem +1 u32 (76 B/slot, ~136 B/capacity slot, 6.8 MB at the 50k stress budget) — pure-integer bookkeeping unchanged.

Additive only: no existing symbol's signature or meaning changed; new public API is SpriteFrameLayout / spriteFrameUv / kSpriteFrameMaxAtlasTexels + SpriteItem.frameIndex.

Docs (same change, CORE-006)

  • NEW docs/api/sprite_frames.md — full contract: sheet model + margins, v-axis convention (v = 0 is the sheet's top row — the M2-SPRITE-02 GL_NEAREST row-0 contract), failure table, the float-exact domain, the M3 hook, Performance (DOC-004), misuse warnings.
  • docs/api/sprite_batcher.md (SpriteItem table row + 76 B/slot + Related), docs/concepts/coordinates.md (§4.8 UV bullet + §5 conversion-table row + Related), docs/README.md (API index), module README status paragraph.
  • laige-api.json regenerated: 1221 symbols / 38 headers (+10 / +1).

Verification

  • Tests: tests/laige-render/sprite_frames_tests.cpp — new CTest entry sprite_frames (the step's Verify command), 11 tests / 4 suites, no GL required:
    • SpriteFrameUvGolden — hand-computed UVs for documented layouts (packed 4×4, tight 82×82 margin sheet, non-square 12×8 frames with spacing, single column/row, idempotent call).
    • SpriteFrameErrors — out-of-range at count / beyond / u32 top with the no-wrap pin, zero-extent layouts, the atlas domain (incl. the exact 2^24 top), the fit failures (one-texel-short spacing, the exact boundary, the adversarial stride).
    • SpriteFrameProperty — 2 000 seeded random tight sheets vs the documented formula + the float-exact invariants + the row/col adjacency rule + the 1 000-conversion zero-allocation proof (non-sanitizer trees, the iso_picking/depth_sort precedent).
    • SpriteFrameItemPassThrough — the frameIndex + uv pair survives the batcher's add/build/get (the M3 entry point).
  • All six local trees warning-clean + full ctest green: build 110/110, build-clang 110/110, build-release 99/99, build-shared 110/110, build-asan 107/107, build-tsan 107/107 (ctest -R sprite_frames green in each).
  • Lints: api-real-tree / api-check-fresh, include-lint (38 public headers), determinism-lint — all green.
  • Budget: no standalone budgets.json entry (the per-frame conversion cost is part of the composite 50k render-CPU budget, sprites_50k_cpu, measured with M2-PERF-01).

Roadmap

M2-SPRITE-03 box checked; Progress Board M2 15/33, total 61/194; Change Log row.

Scope note: the implementation exceeds the roadmap's "~100 lines" sanity note for the same documented-contract reason as M2-SORT-01/M2-SPRITE-01 (the header preamble + the API doc are part of the implementation per CORE-006/DOC-004).

Atlas sheet frame layout (SpriteFrameLayout: frame size, row/col,
inter-frame spacing + sheet-edge margin, all texels) + the pure
frame-index -> UV sub-rect computation (spriteFrameUv): O(1), zero
allocation, float-exact at atlas dimensions <= 2^24 (the documented
u1 > u0, v1 > v0 invariant); out-of-range frame -> InvalidArgument,
never wrap (the layout is untrusted asset metadata — validated
first-failure-wins, with the u64 overflow guard before the col·stride
multiplication). SpriteItem gains the animation frame index
(frameIndex — caller-set, carried through the batcher untouched; uv
is what the renderer draws). This is the data-driven hook M3
animation drives.

Docs: docs/api/sprite_frames.md (new), sprite_batcher.md (SpriteItem
row + 76 B/slot), coordinates.md §4.8 + §5 table row, docs/README
index, module README. laige-api.json regenerated (1221 symbols / 38
headers). ctest entry sprite_frames (11 tests / 4 suites, no GL);
all six local trees warning-clean + full ctest green; api /
include / determinism lints green. No standalone budgets.json entry
(composite 50k render-CPU budget measured at M2-PERF-01).
@offdev
offdev merged commit 2591432 into master Oct 4, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant