# Bounding Box Feature ## Goal Add a checkbox to the map renderer web app that shows the white object bounding boxes players could toggle in the original Crusader debug view. ## Research ### ScummVM Relevant engine path: - `K:\misc\scummvm\engines\ultima\ultima8\world\item_sorter.cpp` - `K:\misc\scummvm\engines\ultima\ultima8\world\sort_item.cpp` Useful findings: - The Ultima 8 / Crusader sorter keeps explicit screen-space bbox corner values such as `_sxLeft`, `_sxRight`, `_sxTop`, `_sxBot`, `_syTop`, and `_syBot`. - The debug drawing path is labeled `Draw wire frame footpads` and uses white lines (`0xFF, 0xFF, 0xFF`). - The wireframe is not just a flat sprite rectangle. It is built from the projected 3D item extents derived from the shape dimensions and the item world position. Important excerpt of behavior: - top diamond edges are drawn first - if the item has height, vertical edges and lower edges are added - the result is closer to an isometric wireframe box / footpad than a simple 2D selection rectangle ### Pentagram Relevant engine path: - `K:\misc\pentagram\world\ItemSorter.cpp` - `K:\misc\pentagram\world\CurrentMap.cpp` Useful findings: - Pentagram uses the same core bbox projection model: world-space `x/y/z` extents become screen-space bbox extents. - The formulas match the ScummVM sorter closely enough to use them as a second reference for the projection math. - This confirmed that the current renderer should not invent a brand-new bbox model; it should reuse the original item extent math where possible. ## Implementation Renderer changes were made in: - `src/public/index.html` - `src/public/dom-elements.js` - `src/public/ui-controls.js` - `src/public/app.js` - `src/lib/build-manager.js` What changed: 1. Added a `Show white bounding boxes` checkbox in the side-panel view toggles. 2. Added an `F` hotkey that toggles bounding boxes on and off unless the current focus is inside an editable text control. 3. Added bbox visibility to the metadata panel so the current view state is explicit. 4. Promoted shape `x/y/z` dimensions from the decoded typeflag info into `shapeDefinitions` inside the scene JSON. 5. Added a client-side bbox overlay renderer that: - respects the same visibility filters as normal rendering - skips hidden items - draws after sprites so boxes remain visible - uses white wireframe lines based on projected item extents when the scene payload includes dimensions 6. Changed hover and click selection to use the projected bounding-box polygon instead of the sprite image rectangle whenever shape dimensions are available. 7. Replaced the old DOM rectangle highlight with a canvas-drawn bbox highlight: - when global bounding boxes are disabled, the focused item shows a white bbox highlight by itself - when global bounding boxes are enabled, the focused item is redrawn in blue above the white global boxes - hover enter fades in quickly and hover exit fades out more slowly 8. Added a compatibility fallback for older cached scenes: if a scene JSON does not yet contain shape dimensions, the client falls back to rectangle-based selection/highlighting instead of failing. ## Issues Encountered ### Issue 1: Existing scene payload was missing shape extents Initial problem: - The viewer already had `item.screen.left/top/right/bottom`, so a quick rectangle overlay was easy. - That was enough for a first pass, but it did not match the original Crusader-style wireframe closely. - It also meant hover selection was based on the sprite image rectangle instead of the projected bbox volume. Resolution: - The build pipeline already had access to the underlying shape dimensions from `typeflag.dat` decoding. - Those dimensions were added to `shapeDefinitions`, which let the client reconstruct a closer isometric wireframe and switch hover hit-testing over to bbox geometry without using Ghidra or adding a new server endpoint. ### Issue 2: Cache compatibility Problem: - Existing cached scene JSON files and static exports were built before the new `dimensions` field existed. Resolution: - The client now detects missing dimensions and falls back to rectangle-based selection/highlighting. - Rebuilding a map or regenerating the static site will upgrade scenes to the wireframe path automatically. ### Issue 3: Hover highlight behavior changed once boxes stopped being a DOM element Problem: - The original inspect highlight was an absolutely positioned DOM rectangle. - Once the focus outline moved to wireframe bbox geometry, the highlight had to be drawn in the same canvas render path as the scene or it would drift away from the new hit-test model. Resolution: - The highlight is now rendered directly on the canvas from the same bbox geometry used by hit-testing. - This made it straightforward to add the requested quick fade-in and slower fade-out timings. ### Issue 4: Highlight animation state was being reset every render Problem: - The inspect/highlight state is recomputed during each render pass. - The first bbox animation implementation reset the highlight timer every frame, which kept the highlight alpha at zero and made the selected bbox appear to never draw. Resolution: - The highlight animation state now resets only when the focused item actually changes. - This lets hovered inspect targets and hoverable editor objects animate correctly while still being recomputed from live bbox geometry. ### Issue 5: Keep exports unchanged Problem: - The bbox checkbox is a debug/viewer affordance, not part of the canonical map export. Resolution: - Bounding boxes are drawn only in the interactive viewport path. - The PNG export path remains unchanged and does not bake the bbox overlay into the exported map image. ## Result The web app now has a bbox checkbox that behaves like a renderer-side debug overlay and is grounded in the same item extent math used by ScummVM/Pentagram. It does not require Ghidra fallback work for the current implementation. ## Follow-up Notes - To see the closer wireframe result everywhere, rebuild dynamic caches or rerun the static export so scenes include the new `shapeDefinitions[].dimensions` data. - If exact parity with the original executable is still needed later, the next step would be verifying the original hotkey/toggle path in the game binary or locating a more explicit Crusader-specific debug toggle in engine code. That was not required for this implementation pass.