Skip to content

perf: bake platformer level tiles into chunked images + merge colliders - #212

Merged
markwpearce merged 2 commits into
mainfrom
tilemap-baking-collider-merging
Sep 12, 2026
Merged

perf: bake platformer level tiles into chunked images + merge colliders#212
markwpearce merged 2 commits into
mainfrom
tilemap-baking-collider-merging

Conversation

@markwpearce

Copy link
Copy Markdown
Owner

Summary

Investigates examples/platformer performance: the level's static ground was one Image + one RectangleCollider per tile (233 tiles), costing a per-frame SceneObject in the renderer's depth sort and a collider in the compositor's collision checks for every tile regardless of whether it ever moves.

  • BGE.TileMap.bakeTileMapImages() / mergeTileColliderRuns() (new, generic engine namespace, src/source/utils/tilemap/): bakes a sparse tile grid into a handful of composited bitmaps (one per occupied chunk, sized/anchored to that chunk's actual tile bounding box — so a tile whose visual offset pushes it past a grid-aligned bucket boundary is never clipped) and merges same-tag collider runs within a row.
  • examples/platformer/Level.bs rewritten to use them: colliders 244→24, scene objects 257→34, on the same level layout.
  • Engine fix: SceneObject.isPotentiallyOnScreen() now checks a screen-aligned object's full bounding box (via getBoundingPoints()) instead of collapsing to a single anchor point, so a large baked chunk (or any large Image/Rectangle/Circle/Text) isn't wholesale-culled just because its own anchor corner left the frustum while another corner is still on screen. Regression test in SceneObjectImage.spec.bs.
  • Game.debugPrintPerfStats(): new opt-in (off by default, zero cost when off) console-only per-frame stats — entity/collider counts and phase timing (UI input / update+collision / entity draw / swap) — independent of, and a faster cadence than, the existing GC print. This is the tool that surfaced everything else in this PR.
  • chunkSize capped at 128 in Level.bs: binary search on both the BrightScript Simulator and a real device found that >=192 silently fails to render a screen-aligned Image at all, despite provably-correct content (confirmed via GetByteArray and a tmp:/ PNG round-trip) and the engine's own draw path reporting success — a real, unexplained platform limit, not a bug in this baking logic. Filed as Support multi-frame loading for Room setup (spread heavy onCreate work like tile baking across frames) #211 (multi-frame room loading) for the constructive follow-up rather than chasing the exact native cause further.

Testing

  • npm run check (lint + validate + headless Rooibos suite): 1105 passed.
  • Verified visually on both the BrightScript Simulator and a real Roku device (sideload + screenshot) — ground renders correctly, character stands flush on top, before/after perf numbers captured via debugPrintPerfStats().

Related

🤖 Generated with Claude Code

markwpearce and others added 2 commits September 11, 2026 23:20
Investigates examples/platformer perf: the level's static ground was
one Image + one RectangleCollider per tile (233 tiles), costing a
per-frame SceneObject in the renderer's depth sort and a collider in
the compositor's collision checks for every tile regardless of motion.

- BGE.TileMap.bakeTileMapImages()/mergeTileColliderRuns(): new generic
  engine helpers that bake a sparse tile grid into a handful of
  composited bitmaps (one per occupied chunk, sized/anchored to each
  chunk's actual tile bounding box - never clips a tile whose visual
  offset pushes it past a grid-aligned bucket boundary) and merge
  same-tag collider runs within a row, respectively.
- examples/platformer/Level.bs rewritten to use them: colliders
  244->24, scene objects 257->34 on the same level.
- Engine fix: SceneObject.isPotentiallyOnScreen() now checks a screen-
  aligned object's full bounding box (via getBoundingPoints()) instead
  of a single anchor point, so a large baked chunk (or any large
  Image/Rectangle/Circle/Text) isn't wholesale-culled just because its
  own anchor corner left the frustum while another corner is still on
  screen - regression test in SceneObjectImage.spec.bs.
- Game.debugPrintPerfStats(): new opt-in (off by default, zero cost
  when off), console-only per-frame stats - entity/collider counts and
  phase timing (UI input / update+collision / entity draw / swap) -
  independent of and faster-cadence than the existing GC print. This
  is what surfaced the above findings.
- chunkSize capped at 128 in Level.bs: binary search on both the
  BrightScript Simulator and a real device found >=192 silently fails
  to render a screen-aligned Image at all, despite provably-correct
  content (GetByteArray + tmp:/ PNG round-trip) and the engine's own
  draw path reporting success - a real, unexplained platform limit,
  not a bug in this baking logic. Filed as issue #211 (multi-frame
  room loading) for the constructive follow-up.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@markwpearce
markwpearce merged commit 21354ca into main Sep 12, 2026
4 checks passed
@markwpearce
markwpearce deleted the tilemap-baking-collider-merging branch September 12, 2026 12:59
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