---
phase: 06-client-rebuild-mvp-gate-cli-08-hard-milestone
plan: 14
type: doc-only-schema-discovery
status: complete
---

[doc->REQ-AST-01]

# 06-14 Schema Discovery

Pre-task D-29: pin the actual JSON field names from extracted sprite and room meta.json files. Downstream tasks (1, 2, 3) use ONLY the names in this document — no assumed names.

---

## §Sprite meta.json schema

| File path | Top-level keys present | Notes |
|-----------|----------------------|-------|
| `extracted/client-5-8/sprites/0000-NaviStandD/meta.json` | `bboxBottom`, `bboxLeft`, `bboxMode`, `bboxRight`, `bboxTop`, `blockVersion`, `height`, `id`, `name`, `originX`, `originY`, `precise`, `preload`, `smoothEdges`, `transparent`, `width` | width=36, height=48; single-frame (1 × img_000.bmp); transparent=true |
| `extracted/client-5-8/sprites/0023-Tile1/meta.json` | `bboxBottom`, `bboxLeft`, `bboxMode`, `bboxRight`, `bboxTop`, `blockVersion`, `height`, `id`, `name`, `originX`, `originY`, `precise`, `preload`, `smoothEdges`, `transparent`, `width` | width=44, height=40; transparent=false; canonical floor tile (D-28) |
| `extracted/client-5-8/sprites/0024-TSide1/meta.json` | `bboxBottom`, `bboxLeft`, `bboxMode`, `bboxRight`, `bboxTop`, `blockVersion`, `height`, `id`, `name`, `originX`, `originY`, `precise`, `preload`, `smoothEdges`, `transparent`, `width` | width=44, height=4 — thin top trim, NOT a full-tile wall |
| `extracted/client-5-8/sprites/0025-VBorder/meta.json` | `bboxBottom`, `bboxLeft`, `bboxMode`, `bboxRight`, `bboxTop`, `blockVersion`, `height`, `id`, `name`, `originX`, `originY`, `precise`, `preload`, `smoothEdges`, `transparent`, `width` | width=1, height=40 — single-pixel-wide vertical edge trim, NOT a full-tile wall |
| `extracted/client-5-8/sprites/0026-HBorder/meta.json` | `bboxBottom`, `bboxLeft`, `bboxMode`, `bboxRight`, `bboxTop`, `blockVersion`, `height`, `id`, `name`, `originX`, `originY`, `precise`, `preload`, `smoothEdges`, `transparent`, `width` | width=44, height=1 — single-pixel-tall horizontal edge trim, NOT a full-tile wall |
| `extracted/client-5-8/sprites/0027-NaviRunD/meta.json` | `bboxBottom`, `bboxLeft`, `bboxMode`, `bboxRight`, `bboxTop`, `blockVersion`, `height`, `id`, `name`, `originX`, `originY`, `precise`, `preload`, `smoothEdges`, `transparent`, `width` | width=36, height=48; multi-frame (img_000..img_005); transparent=true |
| `extracted/client-5-8/sprites/0028-NaviRunR/meta.json` | `bboxBottom`, `bboxLeft`, `bboxMode`, `bboxRight`, `bboxTop`, `blockVersion`, `height`, `id`, `name`, `originX`, `originY`, `precise`, `preload`, `smoothEdges`, `transparent`, `width` | width=36, height=48; transparent=true; restored per CONTEXT line-336 omission fix |

**Critical:** `frame_count` is NOT a field in extracted meta.json. Derive the frame count from the number of `frames/img_*.bmp` files in the sprite directory.

---

## §Room meta.json schema

| File path | Top-level keys present | views[0] keys |
|-----------|----------------------|---------------|
| `extracted/client-5-8/rooms/0058-BNCentral/meta.json` | `bgColor`, `blockVersion`, `caption`, `drawBgColor`, `enableViews`, `height`, `id`, `name`, `persistent`, `speed`, `views`, `width` | `hBorder`, `hSpeed`, `objectFollowing`, `portH`, `portW`, `portX`, `portY`, `vBorder`, `vSpeed`, `viewH`, `viewW`, `viewX`, `viewY`, `visible` |

Views array: 8 entries total; views[0] is the active player-following view (visible=true, hBorder=304, vBorder=224, viewW=640, viewH=480, objectFollowing=0).

---

## §Locked field-name table

| Concept | Extracted JSON field | Aseprite output JSON field | Notes |
|---------|---------------------|----------------------------|-------|
| Sprite frame width | `width` | `frame_w` | Bootstrap maps one to the other |
| Sprite frame height | `height` | `frame_h` | |
| Origin x | `originX` | `origin.x` (in meta.origin) | |
| Origin y | `originY` | `origin.y` | |
| Bbox left | `bboxLeft` | (not in Aseprite output; preserved in meta as-is) | |
| Bbox right | `bboxRight` | (same) | |
| Bbox top | `bboxTop` | (same) | |
| Bbox bottom | `bboxBottom` | (same) | |
| Frame count | (count of `frames/img_*.bmp`) | `frame_count` | Derive from directory listing — NOT from meta.json |
| Room width (px) | `width` | n/a | room meta |
| Room height (px) | `height` | n/a | room meta |
| Room tick rate | `speed` | n/a | confirm 30 (BNCentral speed=30) |
| Camera hBorder | `views[0].hBorder` | n/a | 304 per BNCentral views[0] |
| Camera vBorder | `views[0].vBorder` | n/a | 224 |
| Camera viewport w/h | `views[0].viewW` / `viewH` | n/a | 640 × 480 |
| Object following | `views[0].objectFollowing` | n/a | 0 = local player object in BNO |

---

## §D-28 confirmation

Floor tile dimensions are **44 × 40 px** per `extracted/client-5-8/sprites/0023-Tile1/meta.json` (`width: 44, height: 40, bboxRight: 43, bboxBottom: 39`). A 20 × 20 tile grid therefore yields a world of **880 × 800 px** (20 × 44 = 880, 20 × 40 = 800). This is the canonical synthetic MVP room size per D-28 + D-29. NEVER use 32×32 or any other tile size.

---

## §Wall sprite resolution (LOCKED per WARNING 4 fix)

No extracted 44×40 wall sprite exists. Verified by reading three candidate meta.json files:

- `0024-TSide1`: **44 × 4** (thin top trim — four pixels tall only)
- `0025-VBorder`: **1 × 40** (single-pixel-wide vertical edge)
- `0026-HBorder`: **44 × 1** (single-pixel-tall horizontal edge)

None of these are full-tile (44 × 40). Therefore:

- Task 1 hard-codes `WORLD_SPRITE_IDS = ['0023-Tile1']` (floor only — no wall sprite)
- The 1-tile wall border is drawn by `apps/client/src/render/RoomRenderer.ts` using a Phaser primitive (`scene.add.rectangle(...)`) at the four room edge strips
- Wall color: `#2C3E50` (dark slate — high contrast against the black background and the floor tile)
- Faithful wall art defers to Phase 7 PAR-03 alongside BNCentral conversion

---

## §Open observations

- **transparent flag**: Navi sprites have `transparent: true`; 0023-Tile1 has `transparent: false`. Bootstrap must preserve per-pixel alpha (sharp RGBA pipeline already handles this).
- **smoothEdges flag**: All observed sprites have `smoothEdges: false` — consistent with pixel art; NEAREST filter is mandatory (D-12).
- **bboxMode**: All sprites have `bboxMode: 0` (precise bounding box). Not used by the bootstrap output, but preserved in sidecar for future collision derivation.
- **blockVersion**: All sprites have `blockVersion: 400`; BNCentral room has `blockVersion: 520`. Not consumed by bootstrap or room builder.
- **objectFollowing**: BNCentral views[0] has `objectFollowing: 0`. In GameMaker 5.3a, `0` in this field means "follow the player object with id 0" — i.e., the local player. Maps to Phaser `this.cameras.main.startFollow(localPlayerSprite)`.
