Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions wiki/empty-viewport-postmortem.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Postmortem: Scene loads but viewport appears empty (2D and 3D)

**Date:** 2026-09-03
**Environment:** `apps/editor` standalone app, Next.js 16.2.9 (Turbopack), dev server on :3002, Windows
**Scene:** `bbca88920109` ("hagia-sophia-ground-floor", 104 nodes — site → building → level → 101 `hagia-sophia:*` plugin nodes)

## Symptom

Scene list shows the scene with the correct node count, editor chrome renders (catalog, tabs, level badge), zero console errors — but the viewport shows only the orange site outline and ground plate. No geometry in 2D or 3D. Affected **both** the Hagia Sophia scene and simpler scenes (e.g. garden-house), though less visibly.

Three independent bugs stacked on top of each other produced this. All three had to be fixed before the model appeared.

---

## Bug 1 — Camera auto-framing was accidentally deleted

- **File:** `packages/editor/src/components/editor/index.tsx`
- **Introduced by:** commit `e688792c` "Refactor first-person and spawn selection handling" (Apr 29) — removed both the `useAutoFrame` import and the `useAutoFrame()` call.
- **Effect:** nothing emitted `camera-controls:fit-scene` when a scene loaded, so the camera stayed at the hardcoded first-load default `setLookAt(20, 20, 20, ...)` (`custom-camera-controls.tsx`). For any building larger than ~20 m the camera sits inside the footprint → "empty" view with zero errors.
- **Fix:** re-added the import and the `useAutoFrame()` call in the Editor component.

## Bug 2 — Level-follow effect clobbered the framing right after it ran

- **File:** `packages/editor/src/components/editor/custom-camera-controls.tsx` (level-follow effect)
- **Effect (proven with temporary `[cam-debug]` logs in the dev log):** on load the sequence was: auto-frame emit → `fit-scene` handled → camera framed correctly → *then* the level-follow effect ran for the first time (`currentLevelId: null → 'level_…'`) with `firstLoadWas: true` and executed `setLookAt(20,20,20,0,0,0)` + `moveTo(0, 0, 0)` — teleporting the camera back inside the building. On slow (direct URL) loads a later scene re-apply re-framed and masked the bug; on fast (client-side navigation from `/scenes`) loads nothing did.
- **Fix:** the first-load default pose now only applies when the scene store is empty (scene-less editor), and `moveTo` on level change only fires on a *real* level switch (`previousLevelId !== null && previousLevelId !== currentLevelId`) — the initial `null → level` transition is skipped.
- **Hardening:** added a last-word re-frame effect in `index.tsx` that emits `camera-controls:fit-scene` once the viewer signals scene-ready, so any load-driven camera effect that runs after auto-frame cannot win.

## Bug 3 — Scene's `installedPlugins` list excluded the Hagia Sophia plugin (the actual "nothing renders" cause)

- **Mechanism:** the scene graph stores an explicit `installedPlugins` array. `NodeRenderer` (`packages/viewer/src/components/renderers/node-renderer.tsx`) calls `isNodeKindEnabled(node.type, installedPlugins)` (`packages/core/src/registry/registry.ts`) and returns `null` for kinds whose plugin id is not in the list — in **both 2D and 3D**. This scene's list was `["pascal:trees", "pascal:streetscape"]` with no `"pascal:hagia-sophia"`, so all 101 hs-\* nodes rendered nothing despite `visible: true` and intact geometry definitions.
- **Why the list was stale:** `applySceneGraphToEditor` (`packages/editor/src/lib/scene.ts`) uses a scene's `installedPlugins` verbatim when present (`hasExplicitPluginInstallState: true`); default-installed plugins never apply. The list was frozen at an early save before the HS plugin existed, and explicitly-installed lists never pick up plugins added later.
- **Fix (data, not code):** added `"pascal:hagia-sophia"` to the scene's `installedPlugins` via `PUT /api/scenes/bbca88920109` (scene now v12, all 104 nodes intact). Verified headless: dome + colonnade render.

---

## Verification

Headless (patchright) fresh loads and the real user flow (`/scenes` → open scene): camera frames the colonnade, dome geometry visible in 3D. Confirmed by the user locally after hard refresh.

## Known issues discovered on the way (unfixed)

1. **`PUT /api/scenes/:id` returns 500 but commits.** Response: `{error: "internal_error", message: "cannot commit - no transaction is active"}` — yet each PUT bumps the version and persists. The failure is in a secondary transaction after the main save (likely the SSE event append) in `packages/mcp/src/storage/sqlite-scene-store.ts`. Editor autosaves go through the same endpoint: data lands, but users see spurious save errors. **Note:** the storage layer is under active refactor (sqljs/SQL.js driver work in the working tree) — validate against that branch first.
2. **Design flaw — plugin install state freezes.** Defaults are frozen into each scene as explicit state, so any plugin added *after* a scene was created never applies to it. Every pre-existing scene has this problem. Needs a migration (add newly-registered always-on plugins to existing scenes) or a policy change (fall back to registry defaults when the scene's list predates a plugin).
3. **Dev-global probes unreliable via `page.evaluate`** on this app (`window.__pascalCameraControls`, `window.__pascalNodeRegistry` read as undefined even when the setting code provably runs). Trust `[browser]`-prefixed dev-log lines and screenshots over in-page evaluate probes when debugging this app headlessly.

## Files changed (code fixes, uncommitted at time of writing)

- `packages/editor/src/components/editor/index.tsx` — restore `useAutoFrame` import + call; viewer-ready re-frame effect.
- `packages/editor/src/components/editor/custom-camera-controls.tsx` — level-follow clobber fix; dev-global gate removed (`process.env` is not statically replaced in source-aliased workspace packages under Turbopack, so the gate threw client-side).
- Scene data: `bbca88920109.installedPlugins += "pascal:hagia-sophia"`.
95 changes: 95 additions & 0 deletions wiki/hagia-sophia-modeling-standard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Hagia Sophia — Geometric Modeling Standard (Symmetria Spec)

**Status:** Project modeling standard for the `plugin-hagia-sophia` reconstruction
**Applies to:** `packages/editor`, `packages/mcp` (scene tools), `plugin-hagia-sophia`
**Companion doc:** [learning-versions.md](./learning-versions.md) — how each version of the model is produced and documented
**Postmortem for the session that produced this standard:** [empty-viewport-postmortem.md](./empty-viewport-postmortem.md)

---

## 0. Scope and historicity

This document defines the **parametric design spec used by this project's reconstruction** — it is the convention contributors must follow so every version of the model is built the same way.

Claims fall into two classes and are labeled accordingly throughout:

- **[H]** Historical (mainstream scholarship): construction 532–537 CE; architects Anthemius of Tralles and Isidore of Miletus; great dome ≈ 31 m diameter; nave arcades in six divisions per side; imperial monograms of Justinian and Theodora as marble roundels.
- **[S] Spec convention** (this project's design system, not established historical fact): the monogram-diameter module, the 6 → 10 → 16 → 60 → 360 number progression, the π-bearing latitude reference, the Lyra/Vega celestial mapping, and the 123.6° entrance heading. These govern **how we generate the model**, not what we claim about the building.

---

## 1. Design philosophy: symmetria

Every element is an integer multiple of one module, so the whole model is generated from a single number [S]. In the parametric pipeline this is enforced literally: no dimension may appear in code that is not `n × D` for an integer `n` from the approved set `{6, 10, 16, 36, 60, 360}`.

## 2. The module: monogram diameter `D`

- **[H]** The imperial monograms of Justinian and Theodora (carved in marble within white marble rings) are the symbolic reference.
- **[S]** Their diameter `D` is the fundamental unit of the reconstruction.
- **Derived scale:** the great dome ≈ 31 m [H] is 60 units [S] → **D ≈ 0.517 m**. All scene coordinates are meters computed from this.
- **Open question (flagged, unresolved):** 360 D for the entrance→apse nave length gives ≈ 186 m, which exceeds the historical footprint. Either the multiplier applies to a different span (nave *width* system, full nave + atrium) or the count is not a length. Do not silently "fix" this — versions must record their interpretation in the version doc.

## 3. Modular scaling table

| Multiplier | Dimension in units | Element |
|---|---|---|
| ×1 | 1.0 | Monogram diameter — primary reference circle |
| ×6 | 6.0 | Column capital height |
| ×6 | 6.0 | Queen's circle (Theodora's position) diameter |
| ×16 | 16.0 | Coronation central disc diameter |
| ×36 | 36.0 (6×6) | Emperor's entrance height |
| ×60 | ≈31 m | Great dome diameter (also the divine/star unit ×10 reference) |
| ×360 | — | Nave length, imperial entrance → apse (**unresolved**, see §2) |

**Number progression:** 6 → 10 → 16 → 60 → 360 [S]. Ratios 6:10, 10:16, 16:60, 60:360. Pythagorean rationale: 6 perfect (1+2+3 = 6, 1×2×3 = 6); 10 tetractys/divine; 16 = 4² material completion; 60 = 6 × 10; 360 full circle.

## 4. Circle network

1. **Primary [H/S]:** monogram roundels — marble with white ring banding; above the imperial entrance, at Theodora's honor position, at the coronation site.
2. **Secondary [H/S]:** the great dome (~31 m) with its white fenestration ring; six arcade openings per nave side; six groups of six columns.
3. **Tertiary [S]:** ceremonial floor discs — 16 banded (white marble band) + 16 unbanded; coronation central disc (16 D); queen's circle (6 D).

All circular geometry is generated from radius and π; each circle's C/d is exactly π by construction, and tolerance checks below verify the built model honours that.

## 5. Coordinate system for the model

- **Origin:** the imperial entrance threshold.
- **+X** = east (along the 28.9800° E longitude axis) [S]; **+Z** = north (toward 41.0085° N, the π-bearing latitude reference) [S]; **+Y** = up.
- **Entrance heading:** 123.6° clockwise from true north defines the nave axis [S]. Convert to the editor's Y-rotation convention when placing oriented kinds.
- **Grid unit:** `D ≈ 0.517 m`. Snap positions to multiples of D/2 where subdivision is needed.

## 6. Mapping to pascal node kinds

| Element | Kind | Status |
|---|---|---|
| Columns (6 groups × 6) | `hagia-sophia:column` | exists (colonnade scenes) |
| Arches / arcades | `hagia-sophia:arch` | exists |
| Great dome + semi-domes | `hagia-sophia:dome` | exists |
| Pendentives | `hagia-sophia:pendentive` | exists |
| Piers | `hagia-sophia:pier` | exists |
| Floor discs (32) | `hagia-sophia:disc` (variant: banded/unbanded) | **proposed** |
| Monogram roundels (wall) | `hagia-sophia:medallion` | **proposed** |

Geometry-only kinds (per `wiki/architecture/node-definitions.md`): `def.geometry` + `GeometrySystem` own the mount; no custom renderer/system/tool unless interaction demands it.

## 7. Model validation checklist (per version)

These are **checks on the generated model** (headless screenshots + scene queries via MCP), reproducible in CI or a verify script:

1. All multiples are integer multiples of D (spot-check capital heights = 6 D, queen's circle = 6 D, coronation disc = 16 D, entrance height = 36 D).
2. Every circular kind's geometry uses radius×π paths (no polygonal approximations above 0.5% chord error).
3. Counts match the spec: 6 groups of 6 columns; 6 arcade divisions per side; 16 + 16 floor discs.
4. Mirror symmetry about the nave axis: paired element positions differ by < 1% of span.
5. Entrance heading applied: nave axis at 123.6° from north (compare against site outline).
6. Version doc records any interpretation decisions (esp. the ×360 tension in §2).

## 8. Glossary

| Term | Meaning |
|---|---|
| Symmetria | Balance/harmony of all elements through numerical ratios [S] |
| `D` | Monogram diameter — the module (≈ 0.517 m derived) [S] |
| Tetractys | Pythagorean 1-2-3-4-10 divine number [S] |
| Vega / Lyra | Celestial counterpart to the entrance / aligned constellation [S] |
| Ring Nebula (M57) | Ringed celestial object paralleling the circle motif [S] |
| Banded disc | Floor disc with white marble ring (one of 16) [S] |
60 changes: 60 additions & 0 deletions wiki/learning-versions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Learning Versions — Workflow

**Purpose:** make the Hagia Sophia reconstruction a documented, open-source learning journey. Every iteration of the model is a **version**: a scene snapshot produced through the Pascal MCP, plus a wiki page explaining what changed, why, and what was learned. PRs stack so each step is reviewable in isolation.

**Spec:** [hagia-sophia-modeling-standard.md](./hagia-sophia-modeling-standard.md)
**Postmortem:** [empty-viewport-postmortem.md](./empty-viewport-postmortem.md)

---

## 1. What a learning version is

| Artifact | Where |
|---|---|
| Scene snapshot (versioned save) | scene store, via Pascal MCP |
| Geometry export | `export-json` output archived in `wiki/versions/assets/` (optional) |
| Screenshot | `wiki/versions/assets/<version>.png` |
| Version page | `wiki/versions/<version>.md` |
| Code changes | one PR in the stack (if code changed) |

## 2. Workflow (via the Pascal MCP)

The MCP server (`packages/mcp`, stdio or HTTP transport) exposes the scene tools used below. Every version is created **through the MCP**, never by editing the DB directly:

1. `list-scenes` — locate the working scene (e.g. `hagia-sophia-ground-floor`).
2. `get-scene` / `scene-query` / `find-nodes` — read current graph state.
3. `apply-patch` / `place-item` / `create-level` / geometry kinds — make the iteration's change.
4. `save-scene` — persist; the store bumps the scene version (this is the version marker).
5. Verify headless (fresh load of the scene URL, screenshot) — record console errors as evidence of a clean run.
6. Write `wiki/versions/<version>.md`: goal, graph delta (nodes added/changed with counts), spec sections exercised, interpretation decisions (§2/§7 of the standard), screenshot reference, MCP commands used.
7. If code changed to enable the version, that change lands as its own PR in the stack; the version page links the PR.

**Naming:** `hs-v<N>-<slug>` (e.g. `hs-v1-massing`, `hs-v2-colonnade`, `hs-v3-dome`). Scene save metadata records the same label in the scene name suffix.

## 3. PR stack

Each PR builds on the previous branch; merge order = stack order. No PR mixes docs + code unless the code exists only to serve the doc.

| # | Branch | Type | Contents |
|---|---|---|---|
| 1 | `docs/hs-standard` | docs | postmortem + modeling standard + this workflow doc |
| 2 | `fix/camera-framing` | fix | restore `useAutoFrame`; level-follow clobber fix; viewer-ready re-frame |
| 3 | `fix/scene-put-transaction` | fix(mcp) | PUT `/api/scenes/:id` 500-but-commits (secondary transaction) |
| 4 | `fix/installed-plugins-migration` | feat(editor) | stale `installedPlugins` fallback/migration for pre-existing scenes |
| 5+ | `feat/hs-<version>` | feat(plugin) | kinds/features needed by learning versions (e.g. `hagia-sophia:disc`, `hagia-sophia:medallion`) |

## 4. Version log

| Version | Scene version | Goal | Doc |
|---|---|---|---|
| (baseline) | bbca88920109 v12 | colonnade + dome mass, renders correctly | — |
| hs-v1-massing | planned | site + level + massing boxes to spec grid | _pending_ |
| hs-v2-colonnade | — | 6 groups × 6 columns on module | _pending_ |
| hs-v3-dome | — | dome + pendentives on 60-unit ring | _pending_ |

## 5. Rules for contributors

- One version = one purpose = one doc page. No "misc improvements" versions.
- Every dimension in a version traces to the standard's scaling table; anything else is flagged in the version doc, not hidden.
- Never edit scene data outside the MCP tools (the API path silently 500s post-commit until the transaction fix lands — see postmortem §1).
- Screenshots are evidence: include the viewport after a clean fresh load, with console error count.