Skip to content

Clamp tile spanX to columns in TileGridEngine layout - #25

Merged
adborbas merged 3 commits into
mainfrom
fix/tilegridengine-spanx
Jul 16, 2026
Merged

Clamp tile spanX to columns in TileGridEngine layout#25
adborbas merged 3 commits into
mainfrom
fix/tilegridengine-spanx

Conversation

@adborbas

Copy link
Copy Markdown
Owner

Changes

  • Clamp spanX to columns inside TileGridEngine.place(tile:atRow:col:) and snapshot(), matching the placement loop / canPlace, so the cells reserved, the cells written, and the reported span all agree.
  • Add TileGridEngineClampTests covering an over-wide tile (.large in a 2-column grid), the single-column case, and the in-app case (.large in a 4-column grid, span unchanged).

Why

place() wrote grid cells using the unclamped tile.size.spanX while the placement loop and canPlace already used min(spanX, columns). A tile wider than the grid — e.g. a .large tile (spanX 4) in a 2-column grid — wrote past the row end and trapped with an index-out-of-range. snapshot() reported the same unclamped span, so the reported span exceeded the reserved cells.

This is latent in the current app (columns are always 4/8/12 and max tile width is 4, so spanX <= columns always holds) but reachable through the public package API with a narrow column count.

Notes

  • No behavior change for the column counts the app actually uses: for columns >= 4, min(4, columns) == 4.
  • spanY is intentionally left unclamped — the grid grows rows on demand via ensureRows, so there is no vertical out-of-bounds.
  • The clamp-test assertions check engine.cells directly (each row stays within the column count; a .large tile reserves a full 4x2 block with one origin), so they lock in the place() cell-writing fix, not just the snapshot() value.
  • Verified: swift test --package-path Packages/TileGridEngine → 32 tests pass; app builds clean (xcodebuild build -scheme Hemera).

adborbas and others added 3 commits July 16, 2026 18:30
place(tile:atRow:col:) wrote grid cells using the unclamped
tile.size.spanX while the placement loop and canPlace use
min(spanX, columns). A tile wider than the grid (e.g. a .large tile
in a 2-column grid) wrote past the row end and trapped with an index
out of range. snapshot() reported the same unclamped span, so the
reported span exceeded the reserved cells.

Clamp spanX to columns in both place() and snapshot() so the reserved
cells, the written cells, and the reported span agree. No behavior
change for the in-app column counts (4/8/12, max tile width 4).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Convert the two multi-line // explanatory comments added in the clamp
regression tests to the project's /** ... */ block convention.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
place_tileWiderThanGrid_clampsWithoutCrashing previously asserted only
on snapshot().placements.first?.spanX, which snapshot() recomputes from
tile.size — so it validated the snapshot() clamp but not the cells
place() actually wrote (that was only covered implicitly by "did not
trap"). Assert on engine.cells: every row stays within the column count,
and a .large tile reserves a full 4x2 block with exactly one origin.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@adborbas
adborbas merged commit b0fc29e into main Jul 16, 2026
1 check passed
@adborbas
adborbas deleted the fix/tilegridengine-spanx branch July 16, 2026 20:35
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