From 6c3a96b26eef61dfaeedcb4dba887c9f0da01b63 Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 8 Jun 2026 11:21:46 +0200 Subject: [PATCH] diag(render): flap re-diagnosed as portal-flood re-clip DRIFT; physics + camera REFUTED The 2026-06-08 AM "physics rest micro-jitter" diagnosis is refuted with primary evidence (door-recheck 216K standstill records: 0 position re-snaps; player byte-stable during the flap). Two adversarial verification sub-agents confirmed: - Retail roots the render at the camera viewer_cell (swept from the player via SmartBox::update_viewer 0x453ce0; DrawInside(viewer_cell) 0x453aa0) and toggles DrawInside / LScape::draw -- so acdream's eye-cell rooting + inside/outside toggle are RETAIL-FAITHFUL. The locked-design "root at player cell" is wrong. - The flap is render membership instability, eye-motion-driven: the visible-cell set oscillates (8<->3) as the eye sweeps monotonically. Root = the re-enqueue-on-growth DRIFT (PortalVisibilityBuilder.cs:322, MaxReprocessPerCell =16) re-clipping each grown cell every round -> sub-cm eye jitter flips membership. Fix (spec, not yet implemented): verbatim port of retail's enqueue-once flood (ConstructView + AddViewToPortals): enqueue once on first discovery, clip each cell's portals once, union late growth in place (AddToCell) + draw-reorder (FixCellList), never re-enqueue. Kills the drift; rooting/camera/seal untouched. This commit lands VERIFIED GROUNDWORK + design only: - spec: docs/superpowers/specs/2026-06-08-portal-flood-enqueue-once-port-design.md - findings: docs/research/2026-06-08-flap-physics-diagnosis-REFUTED-its-render-membership.md - [pv-input] probe gains rawPlayer + yaw (disambiguates the varying input) - 4 GREEN physics rest-stability tests (prove rest is bit-stable -> flap not physics) - apparatus: launch-flap-capture.ps1, analyze_flap_live.py, find_burst.py - captured fixtures: tests/.../Fixtures/flap-doorway/0xA9B4017{0..5}.json Co-Authored-By: Claude Opus 4.8 (1M context) --- analyze_flap_live.py | 44 + ...diagnosis-REFUTED-its-render-membership.md | 127 + ...8-portal-flood-enqueue-once-port-design.md | 219 + find_burst.py | 30 + launch-flap-capture.ps1 | 23 + src/AcDream.App/Rendering/GameWindow.cs | 8 +- .../Fixtures/flap-doorway/0xA9B40170.json | 413 ++ .../Fixtures/flap-doorway/0xA9B40171.json | 3773 +++++++++++++++++ .../Fixtures/flap-doorway/0xA9B40172.json | 1963 +++++++++ .../Fixtures/flap-doorway/0xA9B40173.json | 413 ++ .../Fixtures/flap-doorway/0xA9B40174.json | 583 +++ .../Fixtures/flap-doorway/0xA9B40175.json | 413 ++ .../Input/PlayerMovementControllerTests.cs | 91 + .../Physics/CellarUpTrajectoryReplayTests.cs | 132 + 14 files changed, 8231 insertions(+), 1 deletion(-) create mode 100644 analyze_flap_live.py create mode 100644 docs/research/2026-06-08-flap-physics-diagnosis-REFUTED-its-render-membership.md create mode 100644 docs/superpowers/specs/2026-06-08-portal-flood-enqueue-once-port-design.md create mode 100644 find_burst.py create mode 100644 launch-flap-capture.ps1 create mode 100644 tests/AcDream.Core.Tests/Fixtures/flap-doorway/0xA9B40170.json create mode 100644 tests/AcDream.Core.Tests/Fixtures/flap-doorway/0xA9B40171.json create mode 100644 tests/AcDream.Core.Tests/Fixtures/flap-doorway/0xA9B40172.json create mode 100644 tests/AcDream.Core.Tests/Fixtures/flap-doorway/0xA9B40173.json create mode 100644 tests/AcDream.Core.Tests/Fixtures/flap-doorway/0xA9B40174.json create mode 100644 tests/AcDream.Core.Tests/Fixtures/flap-doorway/0xA9B40175.json diff --git a/analyze_flap_live.py b/analyze_flap_live.py new file mode 100644 index 00000000..b1b32b45 --- /dev/null +++ b/analyze_flap_live.py @@ -0,0 +1,44 @@ +import sys, re, math +from collections import Counter + +pat = re.compile( + r'outRoot=(\w) flood=(\d+) eye=\(([^)]+)\) player=\(([^)]+)\) ' + r'rawPlayer=\(([^)]+)\) yaw=([-\d.]+)') +rows = [] +for l in sys.stdin: + m = pat.search(l) + if not m: + continue + rows.append(( + m.group(1), int(m.group(2)), + tuple(float(x) for x in m.group(3).split(',')), # eye + tuple(float(x) for x in m.group(4).split(',')), # player (RenderPosition) + tuple(float(x) for x in m.group(5).split(',')), # rawPlayer (physics body) + float(m.group(6)))) # yaw +print("parsed pv-input rows:", len(rows)) +if not rows: + raise SystemExit + +print("flood histogram (outRoot,flood)->count:", dict(Counter((r[0], r[1]) for r in rows))) + +def rng(idx): + return [max(r[idx][k] for r in rows) - min(r[idx][k] for r in rows) for k in range(3)] +print(f"eye range over window (m): {[round(v,6) for v in rng(2)]}") +print(f"render-pos range over window (m): {[round(v,6) for v in rng(3)]}") +print(f"raw-phys range over window (m): {[round(v,6) for v in rng(4)]}") +print(f"yaw range over window (rad): {round(max(r[5] for r in rows)-min(r[5] for r in rows),6)}") + +flips = 0 +samples = [] +for i in range(1, len(rows)): + a, b = rows[i-1], rows[i] + if a[1] == b[1]: + continue + flips += 1 + ed = math.dist(a[2], b[2]); pd = math.dist(a[3], b[3]) + rd = math.dist(a[4], b[4]); yd = abs(b[5]-a[5]) + if len(samples) < 18: + samples.append(f"{b[0]} {a[1]}->{b[1]:<2} eye={ed*1000:7.3f}mm rend={pd*1e6:8.1f}um raw={rd*1e6:8.1f}um yaw={yd*1000:8.4f}mrad") +print(f"flood flips in window: {flips}") +for s in samples: + print(" ", s) diff --git a/docs/research/2026-06-08-flap-physics-diagnosis-REFUTED-its-render-membership.md b/docs/research/2026-06-08-flap-physics-diagnosis-REFUTED-its-render-membership.md new file mode 100644 index 00000000..8e6b733e --- /dev/null +++ b/docs/research/2026-06-08-flap-physics-diagnosis-REFUTED-its-render-membership.md @@ -0,0 +1,127 @@ +# Handoff/Findings — the "physics rest µm-jitter" flap diagnosis is REFUTED; the flap is a RENDER membership instability at the grazing doorway portal — 2026-06-08 (PM) + +> **Supersedes** `docs/research/2026-06-08-flap-rootcause-physics-rest-handoff.md` (the AM handoff). That +> handoff asked to be treated as a suspect statement and verified — this session verified it with primary +> evidence and it does **not** hold. The flap is **not physics** and **not camera drift**. It is the +> portal-flood **membership flickering non-monotonically at the grazing doorway portal as the camera eye +> sweeps** (i.e. while you turn the camera at the doorway). This is the SPEC's §2.2 diagnosis +> (`2026-06-08-portal-flood-membership-stability-design.md`), NOT its refuted §4 enqueue-once fix. + +--- + +## 1. What was claimed (AM handoff) vs what the evidence shows + +**AM claim:** the flap's varying input is a **physics resting-position µm jitter** — `_body.Position` +blips ~1 ULP between ticks at rest → `RenderPosition` (Lerp of physics) jitters → eye jitters → flood +flips. Fix = physics rest-stability (broaden `kill_velocity`, hold contact plane). + +**Refuted by three independent pieces of primary evidence:** + +### A. The physics body is bit-stable at standstill — it does NOT blip +`door-recheck-capture.jsonl` (515 MB, 238,342 `ResolveWithTransition` records, captured standing at the +doorway, cells `0xA9B40170/0171/0174/0175/0031`): +- **216,300 true-standstill records** (zero velocity, `currentPos==targetPos`). +- **0** resolve re-snaps (`result.position != input` never happens at standstill). +- **0** cross-tick `currentPos` drift (the body position is carried forward byte-identically). +- The `grounded-but-cp=none` contradictory state DOES occur (3.5% of frames) but produces **no** position + blip. + +Confirmed independently with **4 new deterministic tests** (all GREEN — they PROVE rest is bit-stable): +- `PlayerMovementControllerTests.Update_AtRestNoInput_RenderPositionBitStableAcrossManyFrames` (flat terrain) +- `PlayerMovementControllerTests.Update_WalkThenStop_SettlesToBitStableRest` (flat terrain, post-motion) +- `CellarUpTrajectoryReplayTests.IndoorCellarFloor_AtRestZeroOffset_BodyPositionBitStable` (indoor cell, resolver loop) +- `CellarUpTrajectoryReplayTests.IndoorCell_FullController_AtRestNoInput_RenderPositionBitStable` (indoor cell, full controller loop) + +### B. On the actual flap frames, the PLAYER position is byte-identical +`pvinput.log` window 7748–7758 (a clean `flood 6↔2` flap), player `RenderPosition` for **11 consecutive +frames**: `(155.632858, 13.527222, 94.000000)` — **byte-identical**. The physics output the camera reads +does not move at all during the flap. (The 1-ULP *player* blip the AM handoff cited is at the **outdoor** +`flood=1` records — a red herring, not the indoor flap.) + +### C. The EYE moves while the player is still — and the camera DOES settle when idle +Same window: the eye orbits smoothly ~1 mm/frame (X ↓, Y ↑, Z constant) — a slow **camera rotation** +around the stationary player. And: +- Of **888** flood flips in the capture, only **1** had a byte-identical eye (and that one is the + outdoor→indoor root switch). Every other flip had a moved eye → **the flood is deterministic in the + eye; it changes only when the eye moves** (matches `Build_IsDeterministic_*`). +- Longest **indoor byte-identical-eye runs: 203 / 181 / 178 frames (~3.4 s)** — within each, the flood is + a **single constant value** (no flicker). **61%** of indoor frames have a byte-stable eye. +- ⇒ The camera **settles** at rest (no boom drift, no spring oscillation). When the eye is still, the + flood is stable. The flap fires **only while the eye is moving**. + +## 2. The actual mechanism + +When the camera eye **sweeps** through the grazing doorway portal (you turn the camera at the threshold), +the deep cell cluster `{0172,0173,0174,0175}` flickers in/out — flood `6,6,6,2,2,6,6,6,2,6,2` — i.e. +**non-monotonic membership across a monotonic eye sweep**. A correct visibility flood would transition +the deep cluster in/out **once** as the grazing portal closes; instead its clip flips empty↔non-empty as +the eye crosses and re-crosses the knife-edge. This is the SPEC's §2.2 diagnosis (the grazing portal's +clip / re-clip drift makes `clippedRegion.Count` flip `0↔N`, dropping the deep cluster on empty-clip +frames). + +It is **NOT** physics (A, B). It is **NOT** camera drift/oscillation (C: eye byte-stable ~3.4 s when +idle). It is a **render-side portal-flood membership instability at grazing angles**, surfaced by camera +rotation. + +## 3. Status of prior fixes / diagnoses + +- **AM physics-rest fix** — would not have fixed the flap (physics rest is already bit-stable). Do not pursue. +- **SPEC §2.2 diagnosis (grazing-portal membership instability)** — CONFIRMED by this evidence. +- **SPEC §4 enqueue-once fix** — already refuted in the AM handoff (retail propagates late slices via + `AddToCell`, decomp :433494; broke `Build_ViewGrowthAfterDoneCell_PropagatesNewSlicesToExit`). So the + correct fix is a render-side membership stabilization that is monotonic under a sweeping eye **without** + breaking late-slice propagation — design TBD (brainstorm). + +## 4. Apparatus (this session) +- 4 GREEN rest-stability tests (above) — keep as regression guards + evidence that physics rest is bit-stable. +- Analysis scripts (ad-hoc, `/tmp`): door-recheck standstill survey; pvinput flood-flip vs eye/player + delta buckets; indoor byte-stable-eye-run scan. Re-derivable from `pvinput.log` + `door-recheck-capture.jsonl`. +- Existing probes `[pv-input]` (`ACDREAM_PROBE_PVINPUT`) and `[render-sig]` remain the live gate. + +## 5. Next step (proposed) +Brainstorm + design a render-side fix that makes the deep-cluster membership **monotonic/stable as the eye +sweeps the grazing portal** (candidates: more robust grazing-portal clip, a retail-faithful single-process +traversal that doesn't re-clip-drift, or matching retail's exact `GetClip`/`polyClipFinish` epsilon). Then +TDD a builder test that sweeps the eye across the grazing angle and asserts monotonic membership, and +visual-gate by turning the camera at the cottage doorway. + +## 6. LIVE-CONFIRMED (2026-06-08 PM, targeted doorway capture, user-driven) + +A fresh instrumented capture (`launch-flap-capture.ps1`; `[pv-input]` enhanced with `rawPlayer`=raw +physics body pos + `yaw`; cells `0170-0175` dumped to `tests/.../Fixtures/flap-doorway/`; 84K frames) +confirms the diagnosis across every state and decomposes the flap into THREE render sub-issues. **In +every case the player render-pos AND raw physics-pos are byte-identical (0 µm) — physics is conclusively +exonerated; the flap is 100% camera-eye-driven.** + +| State (user-driven) | player moves | eye moves | flood | +|---|---|---|---| +| Idle, hands fully off | no (0 µm) | **no (0 µm)** | **stable** (no flap) | +| Turn / walk | no (0 µm) | yes (mm, yaw) | oscillates | +| Camera smoothing-glide after a turn (yaw byte-constant, eye glides monotonically, decelerating) | no | yes (mm) | **oscillates 8↔3** ← this is the "flickers while idle" the user perceived | + +Key burst (row 11167): **yaw byte-constant**, eye X glides monotonically 155.109→155.435 (18→5 mm/frame, +decelerating), flood `8→3→8…3→8`. Monotonic eye ⇒ non-monotonic membership ⇒ **render** instability (not +camera hunting). + +**Three sub-issues (all eye-driven, physics out):** +- **A — Membership oscillation:** flood non-monotonic as the eye sweeps within a *stable* root. + outside-looking-in `8↔3`; outdoor-root `17↔33` (21 flips/2500 frames); indoor-root `2↔6`. +- **B — Root toggle (the big one):** at the threshold, `outRoot` flips outdoor↔indoor as the eye crosses + the door plane → wholesale visible-set swap **≈18-33 cells ↔ 2 cells** (4 toggles in 2500 frames). This + is the "two-branch" outdoor-node-vs-indoor-cell root switch the unification was meant to remove — still + present. +- **C — Indoor-root under-inclusion:** eye just inside ⇒ `outRoot=n` flood **= 2, stable** for 2438 + frames → outdoors + other rooms missing (the indoor flood does not reach back out the exit portal / to + adjacent cells). C is B's partner: the swap *to* indoor loses the scene → "textures missing." + +**Fix scope:** core render pipeline (root resolution + flood + grazing-clip), NOT physics, NOT camera. +Spec §2.2 (membership instability) is right for A; B+C are the threshold root-resolution/flood issues. +Spec §4 enqueue-once stays refuted. Design needs brainstorming (saga has reverted speculative render +fixes — see `feedback_render_one_gate`, `feedback_verify_render_seal_before_layering`). + +Apparatus added: `launch-flap-capture.ps1`, `analyze_flap_live.py`, `find_burst.py`, fixtures +`tests/AcDream.Core.Tests/Fixtures/flap-doorway/0xA9B4017{0..5}.json`, `flap-doorway-resolve.jsonl`. + +**Memory to correct:** `project_indoor_flap_rootcause` (root is render: A membership instability + B +root-toggle + C indoor under-inclusion, all under a moving camera eye — NOT physics rest, NOT camera +drift; the "two-branch split" B is still live). diff --git a/docs/superpowers/specs/2026-06-08-portal-flood-enqueue-once-port-design.md b/docs/superpowers/specs/2026-06-08-portal-flood-enqueue-once-port-design.md new file mode 100644 index 00000000..4b7ca68e --- /dev/null +++ b/docs/superpowers/specs/2026-06-08-portal-flood-enqueue-once-port-design.md @@ -0,0 +1,219 @@ +# Portal-Flood Enqueue-Once Port — the indoor "flap" fix (verified design) + +**Date:** 2026-06-08 +**Branch:** `claude/thirsty-goldberg-51bb9b` +**Status:** Design approved (brainstorm). Pending implementation plan. + +> **Supersedes** the enqueue logic in `docs/superpowers/specs/2026-06-08-portal-flood-membership-stability-design.md` +> (whose §4 enqueue-once was an *incomplete* attempt — it dropped the `AddToCell` growth half) and the +> physics-rest direction in `docs/research/2026-06-08-flap-rootcause-physics-rest-handoff.md` (refuted). +> **Diagnosis evidence:** `docs/research/2026-06-08-flap-physics-diagnosis-REFUTED-its-render-membership.md` +> + this session's two adversarial verification agents (retail decomp + acdream code/data). + +--- + +## 1. Summary + +The indoor render **flap** (interior textures battling / popping in and out at a building doorway) is a +**render-side portal-flood membership instability**: as the camera **eye** moves (turning the camera, or +the camera's smoothing-glide after a turn), the set of cells the flood deems visible **oscillates** +(e.g. `8↔3`) even though the eye sweeps **monotonically**. The root is acdream's **re-enqueue-on-growth +"drift"** in `PortalVisibilityBuilder.Build` (`cs:322`, `MaxReprocessPerCell = 16`): a cell whose view +grows is re-enqueued and its portals **re-clipped from the grown (drifted) view** each round; under +sub-cm eye motion each frame re-clips slightly differently → the visible set flips. + +The fix is a **verbatim port of retail's enqueue-once portal traversal** (`PView::ConstructView` + +`AddViewToPortals`): a cell is enqueued **only on first discovery**; its portals are clipped **exactly +once** (at pop); later growth into an already-discovered cell is unioned **incrementally in place** +(`AddToCell`) and its draw-list slot re-ordered (`FixCellList`) — **never re-enqueued, never re-clipped +from scratch**. This makes the visible set a deterministic function of the **root + geometry**, so it no +longer drifts with eye jitter. Localized to `PortalVisibilityBuilder`. No camera, physics, rooting, clip- +math, or seal change. + +--- + +## 2. Root cause — verified this session + +### 2.1 What the flap is NOT (refuted with primary evidence) +- **Not physics.** `door-recheck-capture.jsonl`: **216,300 standstill physics records, 0 position + re-snaps** — the body is byte-stable at rest. Deterministic tests (flat terrain + indoor cell, resolver + + full controller) confirm: a resting body holds a byte-identical position. The 2026-06-08 AM + "physics rest µm-jitter" diagnosis is refuted. +- **Not the camera rooting or the inside/outside toggle.** Verified against retail (agent 1): + `SmartBox::RenderNormalMode` (0x453aa0) calls **`DrawInside(viewer_cell)`** (decomp 92675), and + `SmartBox::update_viewer` (0x453ce0) sets `viewer_cell` from a **swept `CTransition`** seeded at the + **player's cell** (`init_path(cell_1, …)` 92866 → `viewer_cell = sphere_path.curr_cell` 92871). So + rooting at the camera's `viewer_cell` and toggling `DrawInside`/`LScape::draw` are **retail-faithful**. + The locked-design claim "root at the player cell" (`2026-06-02 …redesign-design.md` §1.5) is **wrong**; + acdream's current `clipRoot = viewerRoot ?? _outdoorNode` (eye-cell rooting) is correct and stays. +- **Not camera drift at rest.** When the eye is byte-stable (hands-off idle), the flood is rock-stable + (203/181/178-frame byte-identical-eye runs hold a single flood value). The camera settles; the flap + fires **only while the eye moves**. + +### 2.2 What the flap IS (verified — agent 2 + live capture) +- The flood oscillates **only when the eye moves**: across ~7,800 flood flips, **3** had a byte-identical + eye (all startup/streaming); **~87 %** of eye-motion flips have a **byte-identical player** position. + A clean burst (yaw byte-constant, eye gliding monotonically 18→5 mm/frame as the camera settles) shows + flood `8→3→8…` — **non-monotonic membership under a monotonic eye sweep**. +- The mechanism is the **re-enqueue/re-clip drift**: `PortalVisibilityBuilder.cs:322` + `if (grew && popCounts.GetValueOrDefault(neighbourId) < MaxReprocessPerCell && queued.Add(neighbourId)) + todo.Insert(neighbour, dist);` re-enqueues a grown neighbour up to 16×; each re-process re-clips the + cell's portals from its grown view, so sub-cm eye jitter walks `ClipToRegion`'s surviving-vertex count + across the empty/non-empty boundary → the deep cluster `{0172-0175}` drops/returns → the flap. +- **Sub-issue "C" (indoor flood=2 / "missing textures") is mostly a *symptom* of this drift**, not a + missing seal: the landscape-through-the-door seal **is** present in the indoor path + (`RetailPViewRenderer.DrawInside` → `DrawLandscapeThroughOutsideView`). When the flood drops `8→3`, + the `OutsideView`/terrain/cell clip shrinks → things vanish. Fixing the drift removes the symptom. + +--- + +## 3. Retail grounding (the traversal being ported) + +All from `docs/research/named-retail/acclient_2013_pseudo_c.txt`: + +- **`PView::ConstructView`** (0x5a57b0, :433750): `InitCell(root)` + `InsCellTodoList(root)`, then a loop + that **pops one cell at a time** from the todo list, **appends it to the draw list** (← that is + membership), sets `cell_view_done = 1` (:433784), runs `ClipPortals` once, then `AddViewToPortals`. +- **`PView::AddViewToPortals`** (0x5a52d0, :433446): for each visible portal to a neighbour, three cases + keyed on the neighbour's stamps (`processed_stamp` = `*(view+0x44)`, `view_stamp` = `*(view+0x38)`): + - **First discovery** (`processed_stamp == 0`, :433478): `InitCell(neighbour)` + `InsCellTodoList` + (**enqueue once**). + - **Growth** (`processed_stamp != view_stamp`, :433492): `AddToCell(neighbour)` + if already drawn + `FixCellList`; then `processed_stamp = view_stamp`. **No re-enqueue. No re-clip from scratch.** + - **Already current** (`processed_stamp == view_stamp`): **nothing**. +- **`PView::AddToCell`** (0x5a4d90, :433050): clips the cell's portals against **only the newly-added + view slices** (`for i = esi[0x11]; i < esi[0xe]`) — an **incremental** union, not a full re-clip; it + does **not** re-contribute to `OutsideView`. +- **`PView::FixCellList`** (0x5a5250, :433407) → `AdjustDrawList` (:433107): **re-orders** the grown cell + in the draw list to preserve draw order. No re-flood. +- **`PView::InitCell`** (0x5a4b70, :432896): seeds the cell's view, clips its portals against the full + incoming view, stamps with `master_timestamp`; returns whether the cell is non-empty (→ enqueue). + +So retail clips each cell's portals **exactly once** (at pop). Late growth refines a cell's own view + +draw order, never its downstream flood. This is the `cell_view_done` "process each cell once" guarantee. + +--- + +## 4. The fix (design) + +**Scope: `PortalVisibilityBuilder.Build` only.** Replace the re-enqueue-on-growth fixpoint with retail's +enqueue-once traversal. Concretely: + +**Change A — enqueue-once (`Build` ~308-328).** Today: + + var nview = GetOrCreate(frame.CellViews, neighbourId); + bool grew = AddRegion(nview, clippedRegion); // union in place (= retail AddToCell) + if (grew && popCounts.GetValueOrDefault(neighbourId) < MaxReprocessPerCell && queued.Add(neighbourId)) + todo.Insert(neighbour, dist); // RE-ENQUEUE on growth ← the drift + +New: enqueue a neighbour into `todo` **only on first discovery** — i.e. when it has **no `CellViews` +entry yet** (retail `processed_stamp == 0` → `InitCell` + `InsCellTodoList`). On growth into an +already-discovered neighbour, **keep `AddRegion`** (incremental union = `AddToCell`) and re-order it in +the draw list if already present (`FixCellList`, §Change C), but **do not** re-insert into `todo`. + +**Change B — remove the re-enqueue machinery.** Delete `MaxReprocessPerCell`, `popCounts`, and the +per-pop re-enqueue / `queued`-reset logic in the pop loop. Termination is now by construction (each cell +enqueued ≤1, popped ≤1; ≤N cells total), matching retail `cell_view_done`. The `MaxReprocessPerCell` cap +existed **only** as a termination band-aid for the re-enqueue — with enqueue-once it is dead. + +**Change C — draw-list re-order on growth (`FixCellList`).** When growth unions into an +already-discovered cell that is **already in `OrderedVisibleCells`**, re-position it to preserve +closest-first draw order (retail `AdjustDrawList` :433107). If acdream's `OrderedVisibleCells` is already +distance-sorted at assembly time and order is not load-bearing for correctness, this degrades to a no-op +— confirm during implementation; do **not** add ordering machinery the renderer doesn't consume. + +**Unchanged (explicitly):** the per-portal clip (`ProjectToClip`/`ClipToRegion`), the +`EyeInsidePortalOpening` degenerate-portal guard (`Build:235-244`), the reciprocal `OtherPortalClip`, the +`OutsideView` exit contribution, the rooting (`clipRoot = viewerRoot ?? _outdoorNode`), the camera, and +the landscape-through-door seal. No new predicate, no robustness heuristic, no hysteresis. + +**Why this is the flap fix, not a band-aid:** with each cell's portals clipped once, the visible set is a +deterministic function of `(root, geometry)` — independent of the per-round re-clip path. Sub-cm eye +jitter changes the *projection* (and thus what's drawn within each clipped cell, correctly) but no longer +changes *which cells are members*. The membership stops oscillating; the textures stop battling. + +--- + +## 5. The `Build_ViewGrowthAfterDoneCell` question (open item, resolve during implementation) + +The re-enqueue was added 2026-06-07 "to propagate late-discovered slices to exit portals," and +`PortalVisibilityBuilderTests.Build_ViewGrowthAfterDoneCell_PropagatesNewSlicesToExit` encodes that. But +the decomp shows retail's `AddToCell` (:433050) only clips the cell's **own** portals against new slices ++ re-orders draw position — it does **not** re-contribute to `OutsideView` (the exit slice is emitted by +`ClipPortals` at pop, once). So "late growth reaches the exit/OutsideView" appears to be **non-retail**. + +**Action:** read `PView::ClipPortals` (the OutsideView contribution site) during implementation to +confirm. If confirmed, this test encodes the non-faithful re-enqueue behavior and is **corrected to +match retail** (late growth refines the cell's view + draw order, not the OutsideView). It will **not** +be satisfied by reinstating the re-enqueue. If the OutsideView tests +(`Builder_Cellar_WindowClippedToStairwell`, look-in tests) shrink, that is the retail behavior, handled +retail's way — not by re-adding the drift. + +--- + +## 6. Testing (TDD) + +The flap manifests only under live µm/mm eye motion at a specific grazing geometry, so the **visual gate +is acceptance**; the unit layer pins determinism + guards regressions. + +1. **Deterministic eye-sweep stability (new, the RED→GREEN driver).** In `AcDream.App.Tests` + (alongside `PortalVisibilityBuilderTests`, since `PortalVisibilityBuilder` is an App-layer type), build + the flood at a sequence of eye positions stepping across the grazing door angle (sub-cm steps + reproducing the live sweep). **Assert each cell's membership across the sweep is a single contiguous + run** — no `present→absent→present` (or `absent→present→absent`) flicker for any cell. That is the + precise anti-flap property (the live capture showed `8→3→8→3`, multiple transitions per cell). RED + under the re-enqueue drift; GREEN after enqueue-once. *Fixture note:* the captured dumps live at + `tests/AcDream.Core.Tests/Fixtures/flap-doorway/0xA9B4017{0..5}.json`; the test must reach them + (shared path or copied into `AcDream.App.Tests/Fixtures`) and the cells must carry the portal graph + + clip planes `Build` consumes. If the cell-dump format omits portals/clip-planes, the impl plan either + extends the dump or synthesizes a minimal doorway portal topology reproducing the grazing geometry — + surface this as the first implementation step, do not silently weaken the test. +2. **Enqueue-once termination + dedup (new).** Diamond (a cell reachable from two parents) + cycle + fixtures: assert the flood terminates with `MaxReprocessPerCell` removed, `OrderedVisibleCells` is + deduped, each reachable cell present exactly once, and (if a per-cell pop counter is cheap to surface) + each cell popped ≤1. +3. **No membership regression.** `Build_IsDeterministic_*`, `Build_EyeStandingInInteriorPortal_FloodsNeighbour`, + `Build_CollapsedInteriorPortalNearEyeBeyondHalfMeter_FloodsNeighbour`, + `Build_DegeneratePortalToTheSide_NotFlooded_NoOverInclusion` (#95 guard), and the cellar/window/look-in + tests stay **green**. `Build_ViewGrowthAfterDoneCell_PropagatesNewSlicesToExit` is handled per §5. +4. **Visual gate (user) — acceptance.** At the cottage doorway: turn the camera back and forth and walk + through — the interior rooms render steadily, no battling/popping; the `[pv-input]` flood is stable + for a given eye pose. Re-run with `launch-flap-capture.ps1`. + +`dotnet build` + `dotnet test` green before the visual gate. + +--- + +## 7. Scope / non-goals + +- **In scope:** `PortalVisibilityBuilder.Build` enqueue logic (enqueue-once; remove + `MaxReprocessPerCell`/`popCounts`/re-enqueue; incremental union on growth; draw-order re-position) + the + new/updated tests; reading `ClipPortals` to settle §5. +- **Non-goals (deferred / untouched):** + - **No rooting change** — eye-cell rooting (`clipRoot = viewerRoot ?? _outdoorNode`) is retail-faithful + (§2.1). The locked design's "root at player cell" is refuted, not implemented. + - **No clip-math change** (`ProjectToClip`/`ClipToRegion`), no `EyeInsidePortalOpening` change, no + overlap predicate, no hysteresis/robustness heuristic. + - **No camera, physics, or seal change.** The landscape-through-door seal already exists; C is a symptom + of the drift and resolves with it. + - The 4 GREEN physics rest-stability tests added this session stay as regression guards (they document + that physics rest is bit-stable → the flap is not physics). + +--- + +## 8. Apparatus + references + +- **Diagnosis + verification:** `docs/research/2026-06-08-flap-physics-diagnosis-REFUTED-its-render-membership.md`; + this session's two adversarial verification agents (retail decomp CONFIRMED rooting/seal; acdream + code/data CONFIRMED physics-out + eye-driven + the `cs:322` drift). +- **Captured fixtures:** `tests/AcDream.Core.Tests/Fixtures/flap-doorway/0xA9B4017{0..5}.json`; + `flap-doorway-resolve.jsonl`. Apparatus: `launch-flap-capture.ps1`, `analyze_flap_live.py`, + `find_burst.py`, the `[pv-input]` probe (`ACDREAM_PROBE_PVINPUT`, now logs eye/player/rawPlayer/yaw). +- **Retail decomp anchors:** `ConstructView` :433750, `AddViewToPortals` :433446, `InitCell` :432896, + `AddToCell` :433050, `FixCellList` :433407 / `AdjustDrawList` :433107, `InsCellTodoList` :433183, + `SmartBox::update_viewer` :92761, `SmartBox::RenderNormalMode` :92635. +- **Superseded:** `2026-06-08-portal-flood-membership-stability-design.md` §4 (incomplete enqueue-once); + `2026-06-08-flap-rootcause-physics-rest-handoff.md` (physics direction, refuted). +- **Memory to correct after ship:** `project_indoor_flap_rootcause` (root = the `PortalVisibilityBuilder` + re-enqueue/re-clip **drift** under a moving eye; rooting/toggle is retail-faithful; physics + camera + exonerated). diff --git a/find_burst.py b/find_burst.py new file mode 100644 index 00000000..ef58eca9 --- /dev/null +++ b/find_burst.py @@ -0,0 +1,30 @@ +import sys, re +path = sys.argv[1] +pat = re.compile(r'flood=(\d+) eye=\(([^)]+)\) player=\(([^)]+)\) rawPlayer=\(([^)]+)\) yaw=([-\d.]+)') +rows = [] +with open(path, encoding='utf-8', errors='ignore') as fh: + for l in fh: + m = pat.search(l) + if m: + rows.append((int(m.group(1)), + tuple(float(x) for x in m.group(2).split(',')), + float(m.group(5)))) +print("total pv-input rows:", len(rows)) +# find first window of 25 frames containing >=4 flood changes (an oscillation burst) +def changes(seg): return sum(1 for i in range(1, len(seg)) if seg[i][0] != seg[i-1][0]) +W = 25 +start = None +for i in range(len(rows)-W): + if changes(rows[i:i+W]) >= 4: + start = i; break +if start is None: + print("no oscillation burst found"); sys.exit() +print(f"burst at row {start}; dumping {W+8} frames (flood, eyeX,eyeY,eyeZ, dEyeX_mm,dEyeY_mm, yaw):") +prev = None +for r in rows[start:start+W+8]: + fl, e, yaw = r + dx = (e[0]-prev[0])*1000 if prev else 0.0 + dy = (e[1]-prev[1])*1000 if prev else 0.0 + mark = " <" if (prev and fl != prevfl) else "" + print(f" flood={fl} eye=({e[0]:.6f},{e[1]:.6f},{e[2]:.6f}) dX={dx:+7.3f}mm dY={dy:+7.3f}mm yaw={yaw:.6f}{mark}") + prev = e; prevfl = fl diff --git a/launch-flap-capture.ps1 b/launch-flap-capture.ps1 new file mode 100644 index 00000000..7530998a --- /dev/null +++ b/launch-flap-capture.ps1 @@ -0,0 +1,23 @@ +$env:ACDREAM_DAT_DIR = "$env:USERPROFILE\Documents\Asheron's Call" +$env:ACDREAM_LIVE = "1" +$env:ACDREAM_TEST_HOST = "127.0.0.1" +$env:ACDREAM_TEST_PORT = "9000" +$env:ACDREAM_TEST_USER = "testaccount" +$env:ACDREAM_TEST_PASS = "testpassword" + +# Targeted flap capture (2026-06-08). Goal: pin what VARIES at the grazing +# doorway pose when the deep rooms flicker — especially the IDLE case. +# +# 1. [pv-input] : per frame — eye + player(RenderPos) + rawPlayer(physics) + yaw + flood. +# If the flood flickers, exactly one of those is the varying input. +# 2. CAPTURE_RESOLVE : full physics body JSONL per resolve (confirm body bit-stable at the doorway). +# 3. DUMP_CELLS : dump doorway cell geometry as fixtures so a deterministic +# eye-sweep builder test can be written without the live client. +Remove-Item Env:\ACDREAM_PROBE_FLAP -ErrorAction SilentlyContinue +$env:ACDREAM_PROBE_PVINPUT = "1" +$env:ACDREAM_CAPTURE_RESOLVE = "flap-doorway-resolve.jsonl" +$env:ACDREAM_DUMP_CELLS = "0xA9B40170,0xA9B40171,0xA9B40172,0xA9B40173,0xA9B40174,0xA9B40175,0xA9B40031" +$env:ACDREAM_DUMP_CELLS_DIR = "tests\AcDream.Core.Tests\Fixtures\flap-doorway" + +dotnet run --project src\AcDream.App\AcDream.App.csproj --no-build -c Debug 2>&1 | + Tee-Object -FilePath "flap-doorway-pvinput.log" diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index a0b7aa60..63f3cc48 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -7569,8 +7569,14 @@ public sealed class GameWindow : IDisposable { var vp = envCellViewProj; char pvOutRoot = ReferenceEquals(clipRoot, _outdoorNode) ? 'Y' : 'n'; + // 2026-06-08: disambiguate the idle flap. eye=camera eye-point (drives the flood); + // player=RenderPosition (Lerp of physics, what the eye chases); rawPlayer=raw physics + // body Position; yaw=camera/player heading (F8 rad to catch micro-drift). If the flood + // flickers while idle, exactly one of {eye, player, rawPlayer, yaw} is the varying input. + var pvRawPlayer = _playerController?.Position ?? playerViewPos; + float pvYaw = _playerController?.Yaw ?? 0f; Console.WriteLine(System.FormattableString.Invariant( - $"[pv-input] outRoot={pvOutRoot} flood={pvFrame.OrderedVisibleCells.Count} eye=({camPos.X:F6},{camPos.Y:F6},{camPos.Z:F6}) player=({playerViewPos.X:F6},{playerViewPos.Y:F6},{playerViewPos.Z:F6}) vp=[{vp.M11:F6} {vp.M13:F6} {vp.M22:F6} {vp.M31:F6} {vp.M33:F6} {vp.M41:F6} {vp.M42:F6} {vp.M43:F6}]")); + $"[pv-input] outRoot={pvOutRoot} flood={pvFrame.OrderedVisibleCells.Count} eye=({camPos.X:F6},{camPos.Y:F6},{camPos.Z:F6}) player=({playerViewPos.X:F6},{playerViewPos.Y:F6},{playerViewPos.Z:F6}) rawPlayer=({pvRawPlayer.X:F6},{pvRawPlayer.Y:F6},{pvRawPlayer.Z:F6}) yaw={pvYaw:F8} vp=[{vp.M11:F6} {vp.M13:F6} {vp.M22:F6} {vp.M31:F6} {vp.M33:F6} {vp.M41:F6} {vp.M42:F6} {vp.M43:F6}]")); } sigPvFrame = pviewResult.PortalFrame; diff --git a/tests/AcDream.Core.Tests/Fixtures/flap-doorway/0xA9B40170.json b/tests/AcDream.Core.Tests/Fixtures/flap-doorway/0xA9B40170.json new file mode 100644 index 00000000..346b1e55 --- /dev/null +++ b/tests/AcDream.Core.Tests/Fixtures/flap-doorway/0xA9B40170.json @@ -0,0 +1,413 @@ +{ + "CellId": 2847146352, + "WorldTransform": { + "M11": -1, + "M12": 8.74228E-08, + "M13": 0, + "M14": 0, + "M21": -8.74228E-08, + "M22": -1, + "M23": 0, + "M24": 0, + "M31": 0, + "M32": 0, + "M33": 1, + "M34": 0, + "M41": 161.929, + "M42": 7.50315, + "M43": 94, + "M44": 1 + }, + "InverseWorldTransform": { + "M11": -1, + "M12": -8.74228E-08, + "M13": 0, + "M14": -0, + "M21": 8.74228E-08, + "M22": -1, + "M23": 0, + "M24": 0, + "M31": 0, + "M32": 0, + "M33": 1, + "M34": -0, + "M41": 161.929, + "M42": 7.5031643, + "M43": -94, + "M44": 1 + }, + "ResolvedPolygons": [ + { + "Id": 0, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -1, + "Y": 0, + "Z": 0 + }, + "D": 7.45189 + }, + "Vertices": [ + { + "X": 7.45189, + "Y": -8.609, + "Z": 0 + }, + { + "X": 7.45189, + "Y": -8.34616, + "Z": 2.5 + }, + { + "X": 7.45189, + "Y": -7.65, + "Z": 2.5 + }, + { + "X": 7.45189, + "Y": -7.65, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 1, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1.2430552E-08, + "Z": 1 + }, + "D": 1.0701463E-07 + }, + "Vertices": [ + { + "X": 5.54731, + "Y": -7.65, + "Z": -1.19209E-08 + }, + { + "X": 5.54731, + "Y": -8.609, + "Z": 0 + }, + { + "X": 7.45189, + "Y": -8.609, + "Z": 0 + }, + { + "X": 7.45189, + "Y": -7.65, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 2, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": -5.54731 + }, + "Vertices": [ + { + "X": 5.54731, + "Y": -7.65, + "Z": 2.5 + }, + { + "X": 5.54731, + "Y": -8.34616, + "Z": 2.5 + }, + { + "X": 5.54731, + "Y": -8.609, + "Z": 0 + }, + { + "X": 5.54731, + "Y": -7.65, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 3, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 0, + "Z": -1 + }, + "D": 2.5 + }, + "Vertices": [ + { + "X": 7.45189, + "Y": -7.65, + "Z": 2.5 + }, + { + "X": 7.45189, + "Y": -8.34616, + "Z": 2.5 + }, + { + "X": 5.54731, + "Y": -8.34616, + "Z": 2.5 + }, + { + "X": 5.54731, + "Y": -7.65, + "Z": 2.5 + } + ] + } + ], + "PortalPolygons": [ + { + "Id": 0, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 0, + "Z": -1 + }, + "D": 2.5 + }, + "Vertices": [ + { + "X": 7.45189, + "Y": -7.65, + "Z": 2.5 + }, + { + "X": 7.45189, + "Y": -8.34616, + "Z": 2.5 + }, + { + "X": 5.54731, + "Y": -8.34616, + "Z": 2.5 + }, + { + "X": 5.54731, + "Y": -7.65, + "Z": 2.5 + } + ] + }, + { + "Id": 1, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -1, + "Y": 0, + "Z": 0 + }, + "D": 7.45189 + }, + "Vertices": [ + { + "X": 7.45189, + "Y": -8.609, + "Z": 0 + }, + { + "X": 7.45189, + "Y": -8.34616, + "Z": 2.5 + }, + { + "X": 7.45189, + "Y": -7.65, + "Z": 2.5 + }, + { + "X": 7.45189, + "Y": -7.65, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 2, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1.2430552E-08, + "Z": 1 + }, + "D": 1.0701463E-07 + }, + "Vertices": [ + { + "X": 5.54731, + "Y": -7.65, + "Z": -1.19209E-08 + }, + { + "X": 5.54731, + "Y": -8.609, + "Z": 0 + }, + { + "X": 7.45189, + "Y": -8.609, + "Z": 0 + }, + { + "X": 7.45189, + "Y": -7.65, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 3, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": -5.54731 + }, + "Vertices": [ + { + "X": 5.54731, + "Y": -7.65, + "Z": 2.5 + }, + { + "X": 5.54731, + "Y": -8.34616, + "Z": 2.5 + }, + { + "X": 5.54731, + "Y": -8.609, + "Z": 0 + }, + { + "X": 5.54731, + "Y": -7.65, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 4, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -0.99451864, + "Z": 0.10455982 + }, + "D": -8.5618105 + }, + "Vertices": [ + { + "X": 5.54731, + "Y": -8.609, + "Z": 0 + }, + { + "X": 7.45189, + "Y": -8.609, + "Z": 0 + }, + { + "X": 7.45189, + "Y": -8.34616, + "Z": 2.5 + }, + { + "X": 5.54731, + "Y": -8.34616, + "Z": 2.5 + } + ] + }, + { + "Id": 5, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": -7.65 + }, + "Vertices": [ + { + "X": 7.45189, + "Y": -7.65, + "Z": -1.19209E-08 + }, + { + "X": 7.45189, + "Y": -7.65, + "Z": 2.5 + }, + { + "X": 5.54731, + "Y": -7.65, + "Z": 2.5 + }, + { + "X": 5.54731, + "Y": -7.65, + "Z": -1.19209E-08 + } + ] + } + ], + "Portals": [ + { + "OtherCellId": 65535, + "PolygonId": 4, + "Flags": 5 + }, + { + "OtherCellId": 369, + "PolygonId": 5, + "Flags": 3 + } + ], + "VisibleCellIds": [ + 2847146351, + 2847146353, + 2847146354, + 2847146355, + 2847146356, + 2847146357 + ] +} \ No newline at end of file diff --git a/tests/AcDream.Core.Tests/Fixtures/flap-doorway/0xA9B40171.json b/tests/AcDream.Core.Tests/Fixtures/flap-doorway/0xA9B40171.json new file mode 100644 index 00000000..48ea0445 --- /dev/null +++ b/tests/AcDream.Core.Tests/Fixtures/flap-doorway/0xA9B40171.json @@ -0,0 +1,3773 @@ +{ + "CellId": 2847146353, + "WorldTransform": { + "M11": -1, + "M12": 8.74228E-08, + "M13": 0, + "M14": 0, + "M21": -8.74228E-08, + "M22": -1, + "M23": 0, + "M24": 0, + "M31": 0, + "M32": 0, + "M33": 1, + "M34": 0, + "M41": 161.929, + "M42": 7.50315, + "M43": 94, + "M44": 1 + }, + "InverseWorldTransform": { + "M11": -1, + "M12": -8.74228E-08, + "M13": 0, + "M14": -0, + "M21": 8.74228E-08, + "M22": -1, + "M23": 0, + "M24": 0, + "M31": 0, + "M32": 0, + "M33": 1, + "M34": -0, + "M41": 161.929, + "M42": 7.5031643, + "M43": -94, + "M44": 1 + }, + "ResolvedPolygons": [ + { + "Id": 0, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 1.15 + }, + "Vertices": [ + { + "X": 5.6728, + "Y": 1.15, + "Z": 6 + }, + { + "X": 5.6728, + "Y": 1.15, + "Z": 7.3 + }, + { + "X": 4.1, + "Y": 1.15, + "Z": 7.3 + }, + { + "X": 4.1, + "Y": 1.15, + "Z": 4.3 + } + ] + }, + { + "Id": 1, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 1.15 + }, + "Vertices": [ + { + "X": 7.4113, + "Y": 1.15, + "Z": 7.3 + }, + { + "X": 5.6728, + "Y": 1.15, + "Z": 7.3 + }, + { + "X": 5.6728, + "Y": 1.15, + "Z": 6 + }, + { + "X": 7.4113, + "Y": 1.15, + "Z": 6 + } + ] + }, + { + "Id": 2, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 1.15 + }, + "Vertices": [ + { + "X": 7.4113, + "Y": 1.15, + "Z": 8.52 + }, + { + "X": 5.6728, + "Y": 1.15, + "Z": 8.52 + }, + { + "X": 5.6728, + "Y": 1.15, + "Z": 7.3 + }, + { + "X": 7.4113, + "Y": 1.15, + "Z": 7.3 + } + ] + }, + { + "Id": 3, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 1.15 + }, + "Vertices": [ + { + "X": 5.6728, + "Y": 1.15, + "Z": 8.52 + }, + { + "X": 7.4113, + "Y": 1.15, + "Z": 8.52 + }, + { + "X": 9, + "Y": 1.15, + "Z": 9.2 + } + ] + }, + { + "Id": 4, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 1.15 + }, + "Vertices": [ + { + "X": 7.4113, + "Y": 1.15, + "Z": 8.52 + }, + { + "X": 7.4113, + "Y": 1.15, + "Z": 7.3 + }, + { + "X": 9, + "Y": 1.15, + "Z": 7.3 + }, + { + "X": 9, + "Y": 1.15, + "Z": 9.2 + } + ] + }, + { + "Id": 5, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 1.15 + }, + "Vertices": [ + { + "X": 9, + "Y": 1.15, + "Z": 4.3 + }, + { + "X": 9, + "Y": 1.15, + "Z": 7.3 + }, + { + "X": 7.4113, + "Y": 1.15, + "Z": 7.3 + }, + { + "X": 7.4113, + "Y": 1.15, + "Z": 6 + } + ] + }, + { + "Id": 6, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -1, + "Y": 0, + "Z": 0 + }, + "D": 9 + }, + "Vertices": [ + { + "X": 9, + "Y": -1.336, + "Z": 6 + }, + { + "X": 9, + "Y": 1.15, + "Z": 7.3 + }, + { + "X": 9, + "Y": 1.15, + "Z": 0 + } + ] + }, + { + "Id": 7, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -1, + "Y": 0, + "Z": 0 + }, + "D": 9 + }, + "Vertices": [ + { + "X": 9, + "Y": -1.336, + "Z": 6 + }, + { + "X": 9, + "Y": -1.336, + "Z": 7.3 + }, + { + "X": 9, + "Y": 1.15, + "Z": 7.3 + } + ] + }, + { + "Id": 8, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -1, + "Y": 0, + "Z": 0 + }, + "D": 9 + }, + "Vertices": [ + { + "X": 9, + "Y": -1.336, + "Z": 6 + }, + { + "X": 9, + "Y": -4.57, + "Z": 6 + }, + { + "X": 9, + "Y": -4.57, + "Z": 9 + }, + { + "X": 9, + "Y": -1.336, + "Z": 9 + } + ] + }, + { + "Id": 9, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -1, + "Y": 0, + "Z": 0 + }, + "D": 9 + }, + "Vertices": [ + { + "X": 9, + "Y": -4.57, + "Z": 6 + }, + { + "X": 9, + "Y": -1.336, + "Z": 6 + }, + { + "X": 9, + "Y": 1.15, + "Z": 0 + } + ] + }, + { + "Id": 10, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -1, + "Y": 0, + "Z": 0 + }, + "D": 9 + }, + "Vertices": [ + { + "X": 9, + "Y": -7.65, + "Z": 7.3 + }, + { + "X": 9, + "Y": -4.57, + "Z": 6 + }, + { + "X": 9, + "Y": 1.15, + "Z": 0 + }, + { + "X": 9, + "Y": -7.65, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 11, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -1, + "Y": 0, + "Z": 0 + }, + "D": 9 + }, + "Vertices": [ + { + "X": 9, + "Y": -7.65, + "Z": 7.3 + }, + { + "X": 9, + "Y": -4.57, + "Z": 7.3 + }, + { + "X": 9, + "Y": -4.57, + "Z": 6 + } + ] + }, + { + "Id": 12, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -1, + "Y": 0, + "Z": 0 + }, + "D": 9 + }, + "Vertices": [ + { + "X": 9, + "Y": -4.57, + "Z": 7.3 + }, + { + "X": 9, + "Y": -7.65, + "Z": 7.3 + }, + { + "X": 9, + "Y": -7.65, + "Z": 9.2 + } + ] + }, + { + "Id": 13, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 7.65 + }, + "Vertices": [ + { + "X": 9, + "Y": -7.65, + "Z": 7.3 + }, + { + "X": 7.4113, + "Y": -7.65, + "Z": 7.3 + }, + { + "X": 7.4113, + "Y": -7.65, + "Z": 8.52 + }, + { + "X": 9, + "Y": -7.65, + "Z": 9.2 + } + ] + }, + { + "Id": 14, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 7.65 + }, + "Vertices": [ + { + "X": 7.4113, + "Y": -7.65, + "Z": 6 + }, + { + "X": 7.4113, + "Y": -7.65, + "Z": 7.3 + }, + { + "X": 9, + "Y": -7.65, + "Z": 7.3 + }, + { + "X": 9, + "Y": -7.65, + "Z": 4.3 + } + ] + }, + { + "Id": 15, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 7.65 + }, + "Vertices": [ + { + "X": 5.6728, + "Y": -7.65, + "Z": 7.3 + }, + { + "X": 7.4113, + "Y": -7.65, + "Z": 7.3 + }, + { + "X": 7.4113, + "Y": -7.65, + "Z": 6 + }, + { + "X": 5.6728, + "Y": -7.65, + "Z": 6 + } + ] + }, + { + "Id": 16, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 7.65 + }, + "Vertices": [ + { + "X": 5.6728, + "Y": -7.65, + "Z": 7.3 + }, + { + "X": 5.6728, + "Y": -7.65, + "Z": 8.52 + }, + { + "X": 7.4113, + "Y": -7.65, + "Z": 8.52 + }, + { + "X": 7.4113, + "Y": -7.65, + "Z": 7.3 + } + ] + }, + { + "Id": 17, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 7.65 + }, + "Vertices": [ + { + "X": 5.6728, + "Y": -7.65, + "Z": 8.52 + }, + { + "X": 5.6728, + "Y": -7.65, + "Z": 7.3 + }, + { + "X": 4.1, + "Y": -7.65, + "Z": 7.3 + }, + { + "X": 4.1, + "Y": -7.65, + "Z": 9.2 + } + ] + }, + { + "Id": 18, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 7.65 + }, + "Vertices": [ + { + "X": 4.1, + "Y": -7.65, + "Z": 4.3 + }, + { + "X": 4.1, + "Y": -7.65, + "Z": 7.3 + }, + { + "X": 5.6728, + "Y": -7.65, + "Z": 7.3 + }, + { + "X": 5.6728, + "Y": -7.65, + "Z": 6 + } + ] + }, + { + "Id": 19, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": -4.1 + }, + "Vertices": [ + { + "X": 4.1, + "Y": 1.15, + "Z": 4.3 + }, + { + "X": 4.1, + "Y": 1.15, + "Z": 7.3 + }, + { + "X": 4.1, + "Y": -7.65, + "Z": 7.3 + }, + { + "X": 4.1, + "Y": -7.65, + "Z": 4.3 + } + ] + }, + { + "Id": 20, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": -4.1 + }, + "Vertices": [ + { + "X": 4.1, + "Y": -7.65, + "Z": 9.2 + }, + { + "X": 4.1, + "Y": -7.65, + "Z": 7.3 + }, + { + "X": 4.1, + "Y": 1.15, + "Z": 7.3 + }, + { + "X": 4.1, + "Y": 1.15, + "Z": 9.2 + } + ] + }, + { + "Id": 21, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 1.15 + }, + "Vertices": [ + { + "X": 4.1, + "Y": 1.15, + "Z": 7.3 + }, + { + "X": 5.6728, + "Y": 1.15, + "Z": 7.3 + }, + { + "X": 5.6728, + "Y": 1.15, + "Z": 8.52 + }, + { + "X": 4.1, + "Y": 1.15, + "Z": 9.2 + } + ] + }, + { + "Id": 22, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 1.15 + }, + "Vertices": [ + { + "X": 5.6728, + "Y": 1.15, + "Z": 8.52 + }, + { + "X": 9, + "Y": 1.15, + "Z": 9.2 + }, + { + "X": 4.1, + "Y": 1.15, + "Z": 9.2 + } + ] + }, + { + "Id": 23, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 0, + "Z": -1 + }, + "D": 9.2 + }, + "Vertices": [ + { + "X": 9, + "Y": -7.65, + "Z": 9.2 + }, + { + "X": 4.1, + "Y": -7.65, + "Z": 9.2 + }, + { + "X": 4.1, + "Y": 1.15, + "Z": 9.2 + }, + { + "X": 9, + "Y": 1.15, + "Z": 9.2 + } + ] + }, + { + "Id": 24, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 7.65 + }, + "Vertices": [ + { + "X": 7.4113, + "Y": -7.65, + "Z": 8.52 + }, + { + "X": 4.1, + "Y": -7.65, + "Z": 9.2 + }, + { + "X": 9, + "Y": -7.65, + "Z": 9.2 + } + ] + }, + { + "Id": 25, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 7.65 + }, + "Vertices": [ + { + "X": 7.4113, + "Y": -7.65, + "Z": 8.52 + }, + { + "X": 5.6728, + "Y": -7.65, + "Z": 8.52 + }, + { + "X": 4.1, + "Y": -7.65, + "Z": 9.2 + } + ] + }, + { + "Id": 26, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -1, + "Y": 0, + "Z": 0 + }, + "D": 9 + }, + "Vertices": [ + { + "X": 9, + "Y": 1.15, + "Z": 9.2 + }, + { + "X": 9, + "Y": -1.336, + "Z": 9 + }, + { + "X": 9, + "Y": -4.57, + "Z": 9 + }, + { + "X": 9, + "Y": -7.65, + "Z": 9.2 + } + ] + }, + { + "Id": 27, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -1, + "Y": 0, + "Z": 0 + }, + "D": 9 + }, + "Vertices": [ + { + "X": 9, + "Y": 1.15, + "Z": 9.2 + }, + { + "X": 9, + "Y": -1.336, + "Z": 7.3 + }, + { + "X": 9, + "Y": -1.336, + "Z": 9 + } + ] + }, + { + "Id": 28, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -1, + "Y": 0, + "Z": 0 + }, + "D": 9 + }, + "Vertices": [ + { + "X": 9, + "Y": 1.15, + "Z": 9.2 + }, + { + "X": 9, + "Y": 1.15, + "Z": 7.3 + }, + { + "X": 9, + "Y": -1.336, + "Z": 7.3 + } + ] + }, + { + "Id": 29, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -1, + "Y": 0, + "Z": 0 + }, + "D": 9 + }, + "Vertices": [ + { + "X": 9, + "Y": -4.57, + "Z": 9 + }, + { + "X": 9, + "Y": -4.57, + "Z": 7.3 + }, + { + "X": 9, + "Y": -7.65, + "Z": 9.2 + } + ] + }, + { + "Id": 30, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": -4.1 + }, + "Vertices": [ + { + "X": 4.1, + "Y": 1.15, + "Z": 4.3 + }, + { + "X": 4.1, + "Y": -7.65, + "Z": 4.3 + }, + { + "X": 4.1, + "Y": -7.65, + "Z": 2.85 + }, + { + "X": 4.1, + "Y": 1.15, + "Z": 2.85 + } + ] + }, + { + "Id": 31, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 7.65 + }, + "Vertices": [ + { + "X": 4.1, + "Y": -7.65, + "Z": 4.3 + }, + { + "X": 9, + "Y": -7.65, + "Z": 4.3 + }, + { + "X": 9, + "Y": -7.65, + "Z": 2.85 + }, + { + "X": 4.1, + "Y": -7.65, + "Z": 2.85 + } + ] + }, + { + "Id": 32, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 7.65 + }, + "Vertices": [ + { + "X": 4.1, + "Y": -7.65, + "Z": 4.3 + }, + { + "X": 5.6728, + "Y": -7.65, + "Z": 6 + }, + { + "X": 7.4113, + "Y": -7.65, + "Z": 6 + }, + { + "X": 9, + "Y": -7.65, + "Z": 4.3 + } + ] + }, + { + "Id": 33, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 7.65 + }, + "Vertices": [ + { + "X": 7.8, + "Y": -7.65, + "Z": -1.19209E-08 + }, + { + "X": 7.8, + "Y": -7.65, + "Z": 2.85 + }, + { + "X": 9, + "Y": -7.65, + "Z": 2.85 + }, + { + "X": 9, + "Y": -7.65, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 34, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 7.65 + }, + "Vertices": [ + { + "X": 7.8, + "Y": -7.65, + "Z": -1.19209E-08 + }, + { + "X": 7.45189, + "Y": -7.65, + "Z": -1.19209E-08 + }, + { + "X": 7.45189, + "Y": -7.65, + "Z": 2.5 + }, + { + "X": 7.8, + "Y": -7.65, + "Z": 2.85 + } + ] + }, + { + "Id": 35, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 0, + "Z": 1 + }, + "D": 1.19209E-08 + }, + "Vertices": [ + { + "X": 4.1, + "Y": -7.65, + "Z": -1.19209E-08 + }, + { + "X": 9, + "Y": -7.65, + "Z": -1.19209E-08 + }, + { + "X": 7, + "Y": -2.85, + "Z": -1.19209E-08 + }, + { + "X": 4.1, + "Y": 1.15, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 36, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 7.65 + }, + "Vertices": [ + { + "X": 7.45189, + "Y": -7.65, + "Z": 2.5 + }, + { + "X": 5.2, + "Y": -7.65, + "Z": 2.85 + }, + { + "X": 7.8, + "Y": -7.65, + "Z": 2.85 + } + ] + }, + { + "Id": 37, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 7.65 + }, + "Vertices": [ + { + "X": 7.45189, + "Y": -7.65, + "Z": 2.5 + }, + { + "X": 5.54731, + "Y": -7.65, + "Z": 2.5 + }, + { + "X": 5.2, + "Y": -7.65, + "Z": 2.85 + } + ] + }, + { + "Id": 38, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 7.65 + }, + "Vertices": [ + { + "X": 5.54731, + "Y": -7.65, + "Z": 2.5 + }, + { + "X": 5.54731, + "Y": -7.65, + "Z": -1.19209E-08 + }, + { + "X": 5.2, + "Y": -7.65, + "Z": -1.19209E-08 + }, + { + "X": 5.2, + "Y": -7.65, + "Z": 2.85 + } + ] + }, + { + "Id": 39, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 7.65 + }, + "Vertices": [ + { + "X": 4.1, + "Y": -7.65, + "Z": -1.19209E-08 + }, + { + "X": 4.1, + "Y": -7.65, + "Z": 2.85 + }, + { + "X": 5.2, + "Y": -7.65, + "Z": 2.85 + }, + { + "X": 5.2, + "Y": -7.65, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 40, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": -4.1 + }, + "Vertices": [ + { + "X": 4.1, + "Y": -2.2, + "Z": -1.19209E-08 + }, + { + "X": 4.1, + "Y": -2.2, + "Z": 2.85 + }, + { + "X": 4.1, + "Y": -7.65, + "Z": 2.85 + }, + { + "X": 4.1, + "Y": -7.65, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 41, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": -4.1 + }, + "Vertices": [ + { + "X": 4.1, + "Y": -2.2, + "Z": -1.19209E-08 + }, + { + "X": 4.1, + "Y": -1.85189, + "Z": -1.19209E-08 + }, + { + "X": 4.1, + "Y": -1.85189, + "Z": 2.5 + }, + { + "X": 4.1, + "Y": -2.2, + "Z": 2.85 + } + ] + }, + { + "Id": 42, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": -4.1 + }, + "Vertices": [ + { + "X": 4.1, + "Y": -1.85189, + "Z": 2.5 + }, + { + "X": 4.1, + "Y": 0.4, + "Z": 2.85 + }, + { + "X": 4.1, + "Y": -2.2, + "Z": 2.85 + } + ] + }, + { + "Id": 43, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": -4.1 + }, + "Vertices": [ + { + "X": 4.1, + "Y": 0.052686, + "Z": 2.5 + }, + { + "X": 4.1, + "Y": 0.0526863, + "Z": -1.19209E-08 + }, + { + "X": 4.1, + "Y": 0.4, + "Z": -1.19209E-08 + }, + { + "X": 4.1, + "Y": 0.4, + "Z": 2.85 + } + ] + }, + { + "Id": 44, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": -4.1 + }, + "Vertices": [ + { + "X": 4.1, + "Y": 0.4, + "Z": -1.19209E-08 + }, + { + "X": 4.1, + "Y": 1.15, + "Z": -1.19209E-08 + }, + { + "X": 4.1, + "Y": 1.15, + "Z": 2.85 + }, + { + "X": 4.1, + "Y": 0.4, + "Z": 2.85 + } + ] + }, + { + "Id": 45, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 1.15 + }, + "Vertices": [ + { + "X": 4.1, + "Y": 1.15, + "Z": -1.19209E-08 + }, + { + "X": 5.35, + "Y": 1.15, + "Z": -1.19209E-08 + }, + { + "X": 5.35, + "Y": 1.15, + "Z": 4.3 + }, + { + "X": 4.1, + "Y": 1.15, + "Z": 4.3 + } + ] + }, + { + "Id": 46, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 0, + "Z": 1 + }, + "D": 1.19209E-08 + }, + "Vertices": [ + { + "X": 7, + "Y": -2.85, + "Z": -1.19209E-08 + }, + { + "X": 7, + "Y": 1.15, + "Z": -1.19209E-08 + }, + { + "X": 4.1, + "Y": 1.15, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 47, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": -4.1 + }, + "Vertices": [ + { + "X": 4.1, + "Y": -1.85189, + "Z": 2.5 + }, + { + "X": 4.1, + "Y": 0.052686, + "Z": 2.5 + }, + { + "X": 4.1, + "Y": 0.4, + "Z": 2.85 + } + ] + }, + { + "Id": 48, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -5.96045E-09, + "Y": -2.4835207E-09, + "Z": 1 + }, + "D": 4.6566015E-08 + }, + "Vertices": [ + { + "X": 9, + "Y": -7.65, + "Z": -1.19209E-08 + }, + { + "X": 9, + "Y": -2.85, + "Z": 1.11022E-16 + }, + { + "X": 7, + "Y": -2.85, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 49, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 1.15 + }, + "Vertices": [ + { + "X": 7.7, + "Y": 1.15, + "Z": 4.3 + }, + { + "X": 7.7, + "Y": 1.15, + "Z": -1.19209E-08 + }, + { + "X": 9, + "Y": 1.15, + "Z": 0 + }, + { + "X": 9, + "Y": 1.15, + "Z": 4.3 + } + ] + }, + { + "Id": 50, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 1.15 + }, + "Vertices": [ + { + "X": 7.7, + "Y": 1.15, + "Z": -1.19209E-08 + }, + { + "X": 7.7, + "Y": 1.15, + "Z": 1.35 + }, + { + "X": 5.35, + "Y": 1.15, + "Z": 1.35 + }, + { + "X": 5.35, + "Y": 1.15, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 51, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 1.15 + }, + "Vertices": [ + { + "X": 7.7, + "Y": 1.15, + "Z": 3.9 + }, + { + "X": 5.35, + "Y": 1.15, + "Z": 3.9 + }, + { + "X": 5.35, + "Y": 1.15, + "Z": 1.35 + }, + { + "X": 7.7, + "Y": 1.15, + "Z": 1.35 + } + ] + }, + { + "Id": 52, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 1.15 + }, + "Vertices": [ + { + "X": 5.35, + "Y": 1.15, + "Z": 4.3 + }, + { + "X": 5.35, + "Y": 1.15, + "Z": 3.9 + }, + { + "X": 7.7, + "Y": 1.15, + "Z": 3.9 + }, + { + "X": 7.7, + "Y": 1.15, + "Z": 4.3 + } + ] + }, + { + "Id": 53, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 1.15 + }, + "Vertices": [ + { + "X": 9, + "Y": 1.15, + "Z": 4.3 + }, + { + "X": 7.4113, + "Y": 1.15, + "Z": 6 + }, + { + "X": 5.6728, + "Y": 1.15, + "Z": 6 + }, + { + "X": 4.1, + "Y": 1.15, + "Z": 4.3 + } + ] + } + ], + "PortalPolygons": [ + { + "Id": 0, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 1.15 + }, + "Vertices": [ + { + "X": 5.6728, + "Y": 1.15, + "Z": 6 + }, + { + "X": 5.6728, + "Y": 1.15, + "Z": 7.3 + }, + { + "X": 4.1, + "Y": 1.15, + "Z": 7.3 + }, + { + "X": 4.1, + "Y": 1.15, + "Z": 4.3 + } + ] + }, + { + "Id": 1, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 7.65 + }, + "Vertices": [ + { + "X": 4.1, + "Y": -7.65, + "Z": 4.3 + }, + { + "X": 4.1, + "Y": -7.65, + "Z": 7.3 + }, + { + "X": 5.6728, + "Y": -7.65, + "Z": 7.3 + }, + { + "X": 5.6728, + "Y": -7.65, + "Z": 6 + } + ] + }, + { + "Id": 2, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 7.65 + }, + "Vertices": [ + { + "X": 4.1, + "Y": -7.65, + "Z": 4.3 + }, + { + "X": 5.6728, + "Y": -7.65, + "Z": 6 + }, + { + "X": 7.4113, + "Y": -7.65, + "Z": 6 + }, + { + "X": 9, + "Y": -7.65, + "Z": 4.3 + } + ] + }, + { + "Id": 3, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 0, + "Z": -1 + }, + "D": 9.2 + }, + "Vertices": [ + { + "X": 9, + "Y": -7.65, + "Z": 9.2 + }, + { + "X": 4.1, + "Y": -7.65, + "Z": 9.2 + }, + { + "X": 4.1, + "Y": 1.15, + "Z": 9.2 + }, + { + "X": 9, + "Y": 1.15, + "Z": 9.2 + } + ] + }, + { + "Id": 4, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 7.65 + }, + "Vertices": [ + { + "X": 7.4113, + "Y": -7.65, + "Z": 6 + }, + { + "X": 7.4113, + "Y": -7.65, + "Z": 7.3 + }, + { + "X": 9, + "Y": -7.65, + "Z": 7.3 + }, + { + "X": 9, + "Y": -7.65, + "Z": 4.3 + } + ] + }, + { + "Id": 5, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 7.65 + }, + "Vertices": [ + { + "X": 4.1, + "Y": -7.65, + "Z": -1.19209E-08 + }, + { + "X": 4.1, + "Y": -7.65, + "Z": 2.85 + }, + { + "X": 5.2, + "Y": -7.65, + "Z": 2.85 + }, + { + "X": 5.2, + "Y": -7.65, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 6, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 1.15 + }, + "Vertices": [ + { + "X": 4.1, + "Y": 1.15, + "Z": -1.19209E-08 + }, + { + "X": 5.35, + "Y": 1.15, + "Z": -1.19209E-08 + }, + { + "X": 5.35, + "Y": 1.15, + "Z": 4.3 + }, + { + "X": 4.1, + "Y": 1.15, + "Z": 4.3 + } + ] + }, + { + "Id": 7, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 1.15 + }, + "Vertices": [ + { + "X": 7.7, + "Y": 1.15, + "Z": 4.3 + }, + { + "X": 7.7, + "Y": 1.15, + "Z": -1.19209E-08 + }, + { + "X": 9, + "Y": 1.15, + "Z": 0 + }, + { + "X": 9, + "Y": 1.15, + "Z": 4.3 + } + ] + }, + { + "Id": 8, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 1.15 + }, + "Vertices": [ + { + "X": 7.7, + "Y": 1.15, + "Z": -1.19209E-08 + }, + { + "X": 7.7, + "Y": 1.15, + "Z": 1.35 + }, + { + "X": 5.35, + "Y": 1.15, + "Z": 1.35 + }, + { + "X": 5.35, + "Y": 1.15, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 9, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 1.15 + }, + "Vertices": [ + { + "X": 7.7, + "Y": 1.15, + "Z": 3.9 + }, + { + "X": 5.35, + "Y": 1.15, + "Z": 3.9 + }, + { + "X": 5.35, + "Y": 1.15, + "Z": 1.35 + }, + { + "X": 7.7, + "Y": 1.15, + "Z": 1.35 + } + ] + }, + { + "Id": 10, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 1.15 + }, + "Vertices": [ + { + "X": 5.35, + "Y": 1.15, + "Z": 4.3 + }, + { + "X": 5.35, + "Y": 1.15, + "Z": 3.9 + }, + { + "X": 7.7, + "Y": 1.15, + "Z": 3.9 + }, + { + "X": 7.7, + "Y": 1.15, + "Z": 4.3 + } + ] + }, + { + "Id": 11, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": -4.1 + }, + "Vertices": [ + { + "X": 4.1, + "Y": 1.15, + "Z": 4.3 + }, + { + "X": 4.1, + "Y": 1.15, + "Z": 7.3 + }, + { + "X": 4.1, + "Y": -7.65, + "Z": 7.3 + }, + { + "X": 4.1, + "Y": -7.65, + "Z": 4.3 + } + ] + }, + { + "Id": 12, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": -4.1 + }, + "Vertices": [ + { + "X": 4.1, + "Y": -7.65, + "Z": 9.2 + }, + { + "X": 4.1, + "Y": -7.65, + "Z": 7.3 + }, + { + "X": 4.1, + "Y": 1.15, + "Z": 7.3 + }, + { + "X": 4.1, + "Y": 1.15, + "Z": 9.2 + } + ] + }, + { + "Id": 13, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 1.15 + }, + "Vertices": [ + { + "X": 9, + "Y": 1.15, + "Z": 4.3 + }, + { + "X": 9, + "Y": 1.15, + "Z": 7.3 + }, + { + "X": 7.4113, + "Y": 1.15, + "Z": 7.3 + }, + { + "X": 7.4113, + "Y": 1.15, + "Z": 6 + } + ] + }, + { + "Id": 14, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 1.15 + }, + "Vertices": [ + { + "X": 9, + "Y": 1.15, + "Z": 4.3 + }, + { + "X": 7.4113, + "Y": 1.15, + "Z": 6 + }, + { + "X": 5.6728, + "Y": 1.15, + "Z": 6 + }, + { + "X": 4.1, + "Y": 1.15, + "Z": 4.3 + } + ] + }, + { + "Id": 15, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -1, + "Y": 0, + "Z": 0 + }, + "D": 9 + }, + "Vertices": [ + { + "X": 9, + "Y": -1.336, + "Z": 6 + }, + { + "X": 9, + "Y": -4.57, + "Z": 6 + }, + { + "X": 9, + "Y": -4.57, + "Z": 9 + }, + { + "X": 9, + "Y": -1.336, + "Z": 9 + } + ] + }, + { + "Id": 16, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 1.15 + }, + "Vertices": [ + { + "X": 7.4113, + "Y": 1.15, + "Z": 7.3 + }, + { + "X": 5.6728, + "Y": 1.15, + "Z": 7.3 + }, + { + "X": 5.6728, + "Y": 1.15, + "Z": 6 + }, + { + "X": 7.4113, + "Y": 1.15, + "Z": 6 + } + ] + }, + { + "Id": 17, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -1, + "Y": 0, + "Z": 0 + }, + "D": 9 + }, + "Vertices": [ + { + "X": 9, + "Y": -4.57, + "Z": 9 + }, + { + "X": 9, + "Y": -4.57, + "Z": 7.3 + }, + { + "X": 9, + "Y": -7.65, + "Z": 9.2 + } + ] + }, + { + "Id": 18, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 1.15 + }, + "Vertices": [ + { + "X": 7.4113, + "Y": 1.15, + "Z": 8.52 + }, + { + "X": 5.6728, + "Y": 1.15, + "Z": 8.52 + }, + { + "X": 5.6728, + "Y": 1.15, + "Z": 7.3 + }, + { + "X": 7.4113, + "Y": 1.15, + "Z": 7.3 + } + ] + }, + { + "Id": 19, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 7.65 + }, + "Vertices": [ + { + "X": 5.6728, + "Y": -7.65, + "Z": 7.3 + }, + { + "X": 7.4113, + "Y": -7.65, + "Z": 7.3 + }, + { + "X": 7.4113, + "Y": -7.65, + "Z": 6 + }, + { + "X": 5.6728, + "Y": -7.65, + "Z": 6 + } + ] + }, + { + "Id": 20, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -1, + "Y": 0, + "Z": 0 + }, + "D": 9 + }, + "Vertices": [ + { + "X": 9, + "Y": -4.57, + "Z": 7.3 + }, + { + "X": 9, + "Y": -7.65, + "Z": 7.3 + }, + { + "X": 9, + "Y": -7.65, + "Z": 9.2 + } + ] + }, + { + "Id": 21, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 7.65 + }, + "Vertices": [ + { + "X": 5.6728, + "Y": -7.65, + "Z": 7.3 + }, + { + "X": 5.6728, + "Y": -7.65, + "Z": 8.52 + }, + { + "X": 7.4113, + "Y": -7.65, + "Z": 8.52 + }, + { + "X": 7.4113, + "Y": -7.65, + "Z": 7.3 + } + ] + }, + { + "Id": 22, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 7.65 + }, + "Vertices": [ + { + "X": 4.1, + "Y": -7.65, + "Z": 4.3 + }, + { + "X": 9, + "Y": -7.65, + "Z": 4.3 + }, + { + "X": 9, + "Y": -7.65, + "Z": 2.85 + }, + { + "X": 4.1, + "Y": -7.65, + "Z": 2.85 + } + ] + }, + { + "Id": 23, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 7.65 + }, + "Vertices": [ + { + "X": 7.8, + "Y": -7.65, + "Z": -1.19209E-08 + }, + { + "X": 7.8, + "Y": -7.65, + "Z": 2.85 + }, + { + "X": 9, + "Y": -7.65, + "Z": 2.85 + }, + { + "X": 9, + "Y": -7.65, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 24, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -1, + "Y": 0, + "Z": 0 + }, + "D": 9 + }, + "Vertices": [ + { + "X": 9, + "Y": 1.15, + "Z": 9.2 + }, + { + "X": 9, + "Y": -1.336, + "Z": 7.3 + }, + { + "X": 9, + "Y": -1.336, + "Z": 9 + } + ] + }, + { + "Id": 25, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -1, + "Y": 0, + "Z": 0 + }, + "D": 9 + }, + "Vertices": [ + { + "X": 9, + "Y": 1.15, + "Z": 9.2 + }, + { + "X": 9, + "Y": 1.15, + "Z": 7.3 + }, + { + "X": 9, + "Y": -1.336, + "Z": 7.3 + } + ] + }, + { + "Id": 26, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": -4.1 + }, + "Vertices": [ + { + "X": 4.1, + "Y": 1.15, + "Z": 4.3 + }, + { + "X": 4.1, + "Y": -7.65, + "Z": 4.3 + }, + { + "X": 4.1, + "Y": -7.65, + "Z": 2.85 + }, + { + "X": 4.1, + "Y": 1.15, + "Z": 2.85 + } + ] + }, + { + "Id": 27, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": -4.1 + }, + "Vertices": [ + { + "X": 4.1, + "Y": -2.2, + "Z": -1.19209E-08 + }, + { + "X": 4.1, + "Y": -2.2, + "Z": 2.85 + }, + { + "X": 4.1, + "Y": -7.65, + "Z": 2.85 + }, + { + "X": 4.1, + "Y": -7.65, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 28, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": -4.1 + }, + "Vertices": [ + { + "X": 4.1, + "Y": 0.4, + "Z": -1.19209E-08 + }, + { + "X": 4.1, + "Y": 1.15, + "Z": -1.19209E-08 + }, + { + "X": 4.1, + "Y": 1.15, + "Z": 2.85 + }, + { + "X": 4.1, + "Y": 0.4, + "Z": 2.85 + } + ] + }, + { + "Id": 29, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -1, + "Y": 0, + "Z": 0 + }, + "D": 9 + }, + "Vertices": [ + { + "X": 9, + "Y": 1.15, + "Z": 9.2 + }, + { + "X": 9, + "Y": -1.336, + "Z": 9 + }, + { + "X": 9, + "Y": -4.57, + "Z": 9 + }, + { + "X": 9, + "Y": -7.65, + "Z": 9.2 + } + ] + }, + { + "Id": 30, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 0, + "Z": 1 + }, + "D": 1.19209E-08 + }, + "Vertices": [ + { + "X": 4.1, + "Y": -7.65, + "Z": -1.19209E-08 + }, + { + "X": 9, + "Y": -7.65, + "Z": -1.19209E-08 + }, + { + "X": 7, + "Y": -2.85, + "Z": -1.19209E-08 + }, + { + "X": 4.1, + "Y": 1.15, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 31, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 0, + "Z": 1 + }, + "D": 1.19209E-08 + }, + "Vertices": [ + { + "X": 7, + "Y": -2.85, + "Z": -1.19209E-08 + }, + { + "X": 7, + "Y": 1.15, + "Z": -1.19209E-08 + }, + { + "X": 4.1, + "Y": 1.15, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 32, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -5.96045E-09, + "Y": -2.4835207E-09, + "Z": 1 + }, + "D": 4.6566015E-08 + }, + "Vertices": [ + { + "X": 9, + "Y": -7.65, + "Z": -1.19209E-08 + }, + { + "X": 9, + "Y": -2.85, + "Z": 1.11022E-16 + }, + { + "X": 7, + "Y": -2.85, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 33, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -1, + "Y": 0, + "Z": 0 + }, + "D": 9 + }, + "Vertices": [ + { + "X": 9, + "Y": -7.65, + "Z": 7.3 + }, + { + "X": 9, + "Y": -4.57, + "Z": 7.3 + }, + { + "X": 9, + "Y": -4.57, + "Z": 6 + } + ] + }, + { + "Id": 34, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -1, + "Y": 0, + "Z": 0 + }, + "D": 9 + }, + "Vertices": [ + { + "X": 9, + "Y": -7.65, + "Z": 7.3 + }, + { + "X": 9, + "Y": -4.57, + "Z": 6 + }, + { + "X": 9, + "Y": 1.15, + "Z": 0 + }, + { + "X": 9, + "Y": -7.65, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 35, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -1, + "Y": 0, + "Z": 0 + }, + "D": 9 + }, + "Vertices": [ + { + "X": 9, + "Y": -4.57, + "Z": 6 + }, + { + "X": 9, + "Y": -1.336, + "Z": 6 + }, + { + "X": 9, + "Y": 1.15, + "Z": 0 + } + ] + }, + { + "Id": 36, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -1, + "Y": 0, + "Z": 0 + }, + "D": 9 + }, + "Vertices": [ + { + "X": 9, + "Y": -1.336, + "Z": 6 + }, + { + "X": 9, + "Y": 1.15, + "Z": 7.3 + }, + { + "X": 9, + "Y": 1.15, + "Z": 0 + } + ] + }, + { + "Id": 37, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -1, + "Y": 0, + "Z": 0 + }, + "D": 9 + }, + "Vertices": [ + { + "X": 9, + "Y": -1.336, + "Z": 6 + }, + { + "X": 9, + "Y": -1.336, + "Z": 7.3 + }, + { + "X": 9, + "Y": 1.15, + "Z": 7.3 + } + ] + }, + { + "Id": 38, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 1.15 + }, + "Vertices": [ + { + "X": 4.1, + "Y": 1.15, + "Z": 7.3 + }, + { + "X": 5.6728, + "Y": 1.15, + "Z": 7.3 + }, + { + "X": 5.6728, + "Y": 1.15, + "Z": 8.52 + }, + { + "X": 4.1, + "Y": 1.15, + "Z": 9.2 + } + ] + }, + { + "Id": 39, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 1.15 + }, + "Vertices": [ + { + "X": 5.6728, + "Y": 1.15, + "Z": 8.52 + }, + { + "X": 7.4113, + "Y": 1.15, + "Z": 8.52 + }, + { + "X": 9, + "Y": 1.15, + "Z": 9.2 + } + ] + }, + { + "Id": 40, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 1.15 + }, + "Vertices": [ + { + "X": 5.6728, + "Y": 1.15, + "Z": 8.52 + }, + { + "X": 9, + "Y": 1.15, + "Z": 9.2 + }, + { + "X": 4.1, + "Y": 1.15, + "Z": 9.2 + } + ] + }, + { + "Id": 41, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 1.15 + }, + "Vertices": [ + { + "X": 7.4113, + "Y": 1.15, + "Z": 8.52 + }, + { + "X": 7.4113, + "Y": 1.15, + "Z": 7.3 + }, + { + "X": 9, + "Y": 1.15, + "Z": 7.3 + }, + { + "X": 9, + "Y": 1.15, + "Z": 9.2 + } + ] + }, + { + "Id": 42, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 7.65 + }, + "Vertices": [ + { + "X": 9, + "Y": -7.65, + "Z": 7.3 + }, + { + "X": 7.4113, + "Y": -7.65, + "Z": 7.3 + }, + { + "X": 7.4113, + "Y": -7.65, + "Z": 8.52 + }, + { + "X": 9, + "Y": -7.65, + "Z": 9.2 + } + ] + }, + { + "Id": 43, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 7.65 + }, + "Vertices": [ + { + "X": 7.4113, + "Y": -7.65, + "Z": 8.52 + }, + { + "X": 5.6728, + "Y": -7.65, + "Z": 8.52 + }, + { + "X": 4.1, + "Y": -7.65, + "Z": 9.2 + } + ] + }, + { + "Id": 44, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 7.65 + }, + "Vertices": [ + { + "X": 7.4113, + "Y": -7.65, + "Z": 8.52 + }, + { + "X": 4.1, + "Y": -7.65, + "Z": 9.2 + }, + { + "X": 9, + "Y": -7.65, + "Z": 9.2 + } + ] + }, + { + "Id": 45, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 7.65 + }, + "Vertices": [ + { + "X": 5.6728, + "Y": -7.65, + "Z": 8.52 + }, + { + "X": 5.6728, + "Y": -7.65, + "Z": 7.3 + }, + { + "X": 4.1, + "Y": -7.65, + "Z": 7.3 + }, + { + "X": 4.1, + "Y": -7.65, + "Z": 9.2 + } + ] + }, + { + "Id": 46, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 7.65 + }, + "Vertices": [ + { + "X": 7.8, + "Y": -7.65, + "Z": -1.19209E-08 + }, + { + "X": 7.45189, + "Y": -7.65, + "Z": -1.19209E-08 + }, + { + "X": 7.45189, + "Y": -7.65, + "Z": 2.5 + }, + { + "X": 7.8, + "Y": -7.65, + "Z": 2.85 + } + ] + }, + { + "Id": 47, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 7.65 + }, + "Vertices": [ + { + "X": 7.45189, + "Y": -7.65, + "Z": 2.5 + }, + { + "X": 5.54731, + "Y": -7.65, + "Z": 2.5 + }, + { + "X": 5.2, + "Y": -7.65, + "Z": 2.85 + } + ] + }, + { + "Id": 48, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 7.65 + }, + "Vertices": [ + { + "X": 7.45189, + "Y": -7.65, + "Z": 2.5 + }, + { + "X": 5.2, + "Y": -7.65, + "Z": 2.85 + }, + { + "X": 7.8, + "Y": -7.65, + "Z": 2.85 + } + ] + }, + { + "Id": 49, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 7.65 + }, + "Vertices": [ + { + "X": 5.54731, + "Y": -7.65, + "Z": 2.5 + }, + { + "X": 5.54731, + "Y": -7.65, + "Z": -1.19209E-08 + }, + { + "X": 5.2, + "Y": -7.65, + "Z": -1.19209E-08 + }, + { + "X": 5.2, + "Y": -7.65, + "Z": 2.85 + } + ] + }, + { + "Id": 50, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": -4.1 + }, + "Vertices": [ + { + "X": 4.1, + "Y": -2.2, + "Z": -1.19209E-08 + }, + { + "X": 4.1, + "Y": -1.85189, + "Z": -1.19209E-08 + }, + { + "X": 4.1, + "Y": -1.85189, + "Z": 2.5 + }, + { + "X": 4.1, + "Y": -2.2, + "Z": 2.85 + } + ] + }, + { + "Id": 51, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": -4.1 + }, + "Vertices": [ + { + "X": 4.1, + "Y": -1.85189, + "Z": 2.5 + }, + { + "X": 4.1, + "Y": 0.052686, + "Z": 2.5 + }, + { + "X": 4.1, + "Y": 0.4, + "Z": 2.85 + } + ] + }, + { + "Id": 52, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": -4.1 + }, + "Vertices": [ + { + "X": 4.1, + "Y": -1.85189, + "Z": 2.5 + }, + { + "X": 4.1, + "Y": 0.4, + "Z": 2.85 + }, + { + "X": 4.1, + "Y": -2.2, + "Z": 2.85 + } + ] + }, + { + "Id": 53, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": -4.1 + }, + "Vertices": [ + { + "X": 4.1, + "Y": 0.052686, + "Z": 2.5 + }, + { + "X": 4.1, + "Y": 0.0526863, + "Z": -1.19209E-08 + }, + { + "X": 4.1, + "Y": 0.4, + "Z": -1.19209E-08 + }, + { + "X": 4.1, + "Y": 0.4, + "Z": 2.85 + } + ] + }, + { + "Id": 54, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": -7.65 + }, + "Vertices": [ + { + "X": 7.45189, + "Y": -7.65, + "Z": -1.19209E-08 + }, + { + "X": 7.45189, + "Y": -7.65, + "Z": 2.5 + }, + { + "X": 5.54731, + "Y": -7.65, + "Z": 2.5 + }, + { + "X": 5.54731, + "Y": -7.65, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 55, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -1, + "Y": 0, + "Z": 0 + }, + "D": 4.1 + }, + "Vertices": [ + { + "X": 4.1, + "Y": 0.052686, + "Z": 2.5 + }, + { + "X": 4.1, + "Y": 0.0526863, + "Z": -1.19209E-08 + }, + { + "X": 4.1, + "Y": -1.85189, + "Z": -1.19209E-08 + }, + { + "X": 4.1, + "Y": -1.85189, + "Z": 2.5 + } + ] + }, + { + "Id": 56, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 5.96045E-09, + "Y": -1.387775E-17, + "Z": -1 + }, + "D": -5.364405E-08 + }, + "Vertices": [ + { + "X": 9, + "Y": -2.85, + "Z": 1.11022E-16 + }, + { + "X": 7, + "Y": -2.85, + "Z": -1.19209E-08 + }, + { + "X": 7, + "Y": 1.15, + "Z": -1.19209E-08 + }, + { + "X": 9, + "Y": 1.15, + "Z": 0 + } + ] + } + ], + "Portals": [ + { + "OtherCellId": 368, + "PolygonId": 54, + "Flags": 1 + }, + { + "OtherCellId": 371, + "PolygonId": 55, + "Flags": 1 + }, + { + "OtherCellId": 373, + "PolygonId": 56, + "Flags": 1 + } + ], + "VisibleCellIds": [ + 2847146351, + 2847146352, + 2847146354, + 2847146355, + 2847146356, + 2847146357 + ] +} \ No newline at end of file diff --git a/tests/AcDream.Core.Tests/Fixtures/flap-doorway/0xA9B40172.json b/tests/AcDream.Core.Tests/Fixtures/flap-doorway/0xA9B40172.json new file mode 100644 index 00000000..1be3dfce --- /dev/null +++ b/tests/AcDream.Core.Tests/Fixtures/flap-doorway/0xA9B40172.json @@ -0,0 +1,1963 @@ +{ + "CellId": 2847146354, + "WorldTransform": { + "M11": -1, + "M12": 8.74228E-08, + "M13": 0, + "M14": 0, + "M21": -8.74228E-08, + "M22": -1, + "M23": 0, + "M24": 0, + "M31": 0, + "M32": 0, + "M33": 1, + "M34": 0, + "M41": 161.929, + "M42": 7.50315, + "M43": 94, + "M44": 1 + }, + "InverseWorldTransform": { + "M11": -1, + "M12": -8.74228E-08, + "M13": 0, + "M14": -0, + "M21": 8.74228E-08, + "M22": -1, + "M23": 0, + "M24": 0, + "M31": 0, + "M32": 0, + "M33": 1, + "M34": -0, + "M41": 161.929, + "M42": 7.5031643, + "M43": -94, + "M44": 1 + }, + "ResolvedPolygons": [ + { + "Id": 0, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0.8289255, + "Y": 0, + "Z": -0.55935895 + }, + "D": 4.809128 + }, + "Vertices": [ + { + "X": -2.9, + "Y": 3, + "Z": 4.3 + }, + { + "X": -1.14552, + "Y": 2.38419E-08, + "Z": 6.9 + }, + { + "X": -2.9, + "Y": -3.2, + "Z": 4.3 + } + ] + }, + { + "Id": 1, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -0.65493053, + "Z": -0.7556891 + }, + "D": 5.214255 + }, + "Vertices": [ + { + "X": 1.94952, + "Y": 2.38419E-08, + "Z": 6.9 + }, + { + "X": -1.14552, + "Y": 2.38419E-08, + "Z": 6.9 + }, + { + "X": -2.9, + "Y": 3, + "Z": 4.3 + }, + { + "X": 3.9, + "Y": 3, + "Z": 4.3 + } + ] + }, + { + "Id": 2, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 0.63059264, + "Z": -0.776114 + }, + "D": 5.3551865 + }, + "Vertices": [ + { + "X": -1.14552, + "Y": 2.38419E-08, + "Z": 6.9 + }, + { + "X": 1.94952, + "Y": 2.38419E-08, + "Z": 6.9 + }, + { + "X": 3.9, + "Y": -3.2, + "Z": 4.3 + }, + { + "X": -2.9, + "Y": -3.2, + "Z": 4.3 + } + ] + }, + { + "Id": 3, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -0.7999291, + "Y": 0, + "Z": -0.60009456 + }, + "D": 5.7001305 + }, + "Vertices": [ + { + "X": 3.9, + "Y": -3.2, + "Z": 4.3 + }, + { + "X": 1.94952, + "Y": 2.38419E-08, + "Z": 6.9 + }, + { + "X": 3.9, + "Y": 3, + "Z": 4.3 + } + ] + }, + { + "Id": 4, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -1, + "Y": 0, + "Z": 0 + }, + "D": 3.9 + }, + "Vertices": [ + { + "X": 3.9, + "Y": 3, + "Z": 4.3 + }, + { + "X": 3.9, + "Y": 3, + "Z": 2.9 + }, + { + "X": 3.9, + "Y": -3.2, + "Z": 2.9 + }, + { + "X": 3.9, + "Y": -3.2, + "Z": 4.3 + } + ] + }, + { + "Id": 5, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 3 + }, + "Vertices": [ + { + "X": -2.9, + "Y": 3, + "Z": 2.9 + }, + { + "X": 3.9, + "Y": 3, + "Z": 2.9 + }, + { + "X": 3.9, + "Y": 3, + "Z": 4.3 + }, + { + "X": -2.9, + "Y": 3, + "Z": 4.3 + } + ] + }, + { + "Id": 6, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 3 + }, + "Vertices": [ + { + "X": -1, + "Y": 3, + "Z": -1.19209E-08 + }, + { + "X": -1, + "Y": 3, + "Z": 2.9 + }, + { + "X": -2.9, + "Y": 3, + "Z": 2.9 + }, + { + "X": -2.9, + "Y": 3, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 7, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 3 + }, + "Vertices": [ + { + "X": -1, + "Y": 3, + "Z": -1.19209E-08 + }, + { + "X": 2, + "Y": 3, + "Z": -1.19209E-08 + }, + { + "X": 2, + "Y": 3, + "Z": 1 + }, + { + "X": -1, + "Y": 3, + "Z": 1 + } + ] + }, + { + "Id": 8, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 0, + "Z": 1 + }, + "D": 1.19209E-08 + }, + "Vertices": [ + { + "X": 3.9, + "Y": 3, + "Z": -1.19209E-08 + }, + { + "X": -2.9, + "Y": 3, + "Z": -1.19209E-08 + }, + { + "X": -2.9, + "Y": -3.2, + "Z": -1.19209E-08 + }, + { + "X": 3.9, + "Y": -3.2, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 9, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 3 + }, + "Vertices": [ + { + "X": 2, + "Y": 3, + "Z": 2.9 + }, + { + "X": 2, + "Y": 3, + "Z": -1.19209E-08 + }, + { + "X": 3.9, + "Y": 3, + "Z": -1.19209E-08 + }, + { + "X": 3.9, + "Y": 3, + "Z": 2.9 + } + ] + }, + { + "Id": 10, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 3 + }, + "Vertices": [ + { + "X": 2, + "Y": 3, + "Z": 2.9 + }, + { + "X": -1, + "Y": 3, + "Z": 2.9 + }, + { + "X": -1, + "Y": 3, + "Z": 1 + }, + { + "X": 2, + "Y": 3, + "Z": 1 + } + ] + }, + { + "Id": 11, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -1, + "Y": 0, + "Z": 0 + }, + "D": 3.9 + }, + "Vertices": [ + { + "X": 3.9, + "Y": 0.0526863, + "Z": 0 + }, + { + "X": 3.9, + "Y": 0.052686, + "Z": 2.9 + }, + { + "X": 3.9, + "Y": 3, + "Z": 2.9 + }, + { + "X": 3.9, + "Y": 3, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 12, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -1, + "Y": 0, + "Z": 0 + }, + "D": 3.9 + }, + "Vertices": [ + { + "X": 3.9, + "Y": 0.0526863, + "Z": 2.5 + }, + { + "X": 3.9, + "Y": -1.8519, + "Z": 2.5 + }, + { + "X": 3.9, + "Y": -1.8519, + "Z": 2.9 + }, + { + "X": 3.9, + "Y": 0.052686, + "Z": 2.9 + } + ] + }, + { + "Id": 13, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 3.2 + }, + "Vertices": [ + { + "X": 1.68, + "Y": -3.2, + "Z": 1 + }, + { + "X": 1.68, + "Y": -3.2, + "Z": 2.9 + }, + { + "X": 3.9, + "Y": -3.2, + "Z": 2.9 + }, + { + "X": 3.9, + "Y": -3.2, + "Z": 1 + } + ] + }, + { + "Id": 14, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -1, + "Y": 0, + "Z": 0 + }, + "D": 3.9 + }, + "Vertices": [ + { + "X": 3.9, + "Y": -1.8519, + "Z": 2.9 + }, + { + "X": 3.9, + "Y": -1.85189, + "Z": 0 + }, + { + "X": 3.9, + "Y": -3.2, + "Z": -1.19209E-08 + }, + { + "X": 3.9, + "Y": -3.2, + "Z": 2.9 + } + ] + }, + { + "Id": 15, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 3.2 + }, + "Vertices": [ + { + "X": -2.9, + "Y": -3.2, + "Z": -1.19209E-08 + }, + { + "X": -2.9, + "Y": -3.2, + "Z": 1 + }, + { + "X": 3.9, + "Y": -3.2, + "Z": 1 + }, + { + "X": 3.9, + "Y": -3.2, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 16, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": 2.9 + }, + "Vertices": [ + { + "X": -2.9, + "Y": -1.05, + "Z": 1 + }, + { + "X": -2.9, + "Y": -1.05, + "Z": 2.9 + }, + { + "X": -2.9, + "Y": -3.2, + "Z": 2.9 + }, + { + "X": -2.9, + "Y": -3.2, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 17, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 3.2 + }, + "Vertices": [ + { + "X": 1.68, + "Y": -3.2, + "Z": 1 + }, + { + "X": -1, + "Y": -3.2, + "Z": 1 + }, + { + "X": -1, + "Y": -3.2, + "Z": 2.9 + }, + { + "X": 1.68, + "Y": -3.2, + "Z": 2.9 + } + ] + }, + { + "Id": 18, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": 2.9000003 + }, + "Vertices": [ + { + "X": -2.9, + "Y": -1.05, + "Z": 3.55 + }, + { + "X": -2.9, + "Y": 0.85, + "Z": 3.55 + }, + { + "X": -2.9, + "Y": 3, + "Z": 4.3 + } + ] + }, + { + "Id": 19, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": 2.9000003 + }, + "Vertices": [ + { + "X": -2.9, + "Y": 0.85, + "Z": 3.55 + }, + { + "X": -2.9, + "Y": 3, + "Z": 2.9 + }, + { + "X": -2.9, + "Y": 3, + "Z": 4.3 + } + ] + }, + { + "Id": 20, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": 2.9000003 + }, + "Vertices": [ + { + "X": -2.9, + "Y": 0.85, + "Z": 3.55 + }, + { + "X": -2.9, + "Y": 0.85, + "Z": 2.9 + }, + { + "X": -2.9, + "Y": 3, + "Z": 2.9 + } + ] + }, + { + "Id": 21, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": 2.9 + }, + "Vertices": [ + { + "X": -2.9, + "Y": 3, + "Z": -1.19209E-08 + }, + { + "X": -2.9, + "Y": 3, + "Z": 2.9 + }, + { + "X": -2.9, + "Y": 0.85, + "Z": 2.9 + }, + { + "X": -2.9, + "Y": 0.85, + "Z": 1 + } + ] + }, + { + "Id": 22, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": 2.9 + }, + "Vertices": [ + { + "X": -2.9, + "Y": 0.85, + "Z": 1 + }, + { + "X": -2.9, + "Y": -1.05, + "Z": 1 + }, + { + "X": -2.9, + "Y": -3.2, + "Z": -1.19209E-08 + }, + { + "X": -2.9, + "Y": 3, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 23, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": 2.9000003 + }, + "Vertices": [ + { + "X": -2.9, + "Y": -1.05, + "Z": 3.55 + }, + { + "X": -2.9, + "Y": 3, + "Z": 4.3 + }, + { + "X": -2.9, + "Y": -3.2, + "Z": 4.3 + } + ] + }, + { + "Id": 24, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": 2.9 + }, + "Vertices": [ + { + "X": -2.9, + "Y": -3.2, + "Z": 2.9 + }, + { + "X": -2.9, + "Y": -1.05, + "Z": 2.9 + }, + { + "X": -2.9, + "Y": -1.05, + "Z": 3.55 + }, + { + "X": -2.9, + "Y": -3.2, + "Z": 4.3 + } + ] + }, + { + "Id": 25, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 3.2 + }, + "Vertices": [ + { + "X": 3.9, + "Y": -3.2, + "Z": 2.9 + }, + { + "X": -2.9, + "Y": -3.2, + "Z": 2.9 + }, + { + "X": -2.9, + "Y": -3.2, + "Z": 4.3 + }, + { + "X": 3.9, + "Y": -3.2, + "Z": 4.3 + } + ] + }, + { + "Id": 26, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 3.2 + }, + "Vertices": [ + { + "X": -2.9, + "Y": -3.2, + "Z": 2.9 + }, + { + "X": -1, + "Y": -3.2, + "Z": 2.9 + }, + { + "X": -1, + "Y": -3.2, + "Z": 1 + }, + { + "X": -2.9, + "Y": -3.2, + "Z": 1 + } + ] + } + ], + "PortalPolygons": [ + { + "Id": 0, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0.8289255, + "Y": 0, + "Z": -0.55935895 + }, + "D": 4.809128 + }, + "Vertices": [ + { + "X": -2.9, + "Y": 3, + "Z": 4.3 + }, + { + "X": -1.14552, + "Y": 2.38419E-08, + "Z": 6.9 + }, + { + "X": -2.9, + "Y": -3.2, + "Z": 4.3 + } + ] + }, + { + "Id": 1, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -0.7999291, + "Y": 0, + "Z": -0.60009456 + }, + "D": 5.7001305 + }, + "Vertices": [ + { + "X": 3.9, + "Y": -3.2, + "Z": 4.3 + }, + { + "X": 1.94952, + "Y": 2.38419E-08, + "Z": 6.9 + }, + { + "X": 3.9, + "Y": 3, + "Z": 4.3 + } + ] + }, + { + "Id": 2, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 3 + }, + "Vertices": [ + { + "X": 2, + "Y": 3, + "Z": 2.9 + }, + { + "X": -1, + "Y": 3, + "Z": 2.9 + }, + { + "X": -1, + "Y": 3, + "Z": 1 + }, + { + "X": 2, + "Y": 3, + "Z": 1 + } + ] + }, + { + "Id": 3, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 3.2 + }, + "Vertices": [ + { + "X": 3.9, + "Y": -3.2, + "Z": 2.9 + }, + { + "X": -2.9, + "Y": -3.2, + "Z": 2.9 + }, + { + "X": -2.9, + "Y": -3.2, + "Z": 4.3 + }, + { + "X": 3.9, + "Y": -3.2, + "Z": 4.3 + } + ] + }, + { + "Id": 4, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 3 + }, + "Vertices": [ + { + "X": -2.9, + "Y": 3, + "Z": 2.9 + }, + { + "X": 3.9, + "Y": 3, + "Z": 2.9 + }, + { + "X": 3.9, + "Y": 3, + "Z": 4.3 + }, + { + "X": -2.9, + "Y": 3, + "Z": 4.3 + } + ] + }, + { + "Id": 5, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -1, + "Y": 0, + "Z": 0 + }, + "D": 3.9 + }, + "Vertices": [ + { + "X": 3.9, + "Y": 3, + "Z": 4.3 + }, + { + "X": 3.9, + "Y": 3, + "Z": 2.9 + }, + { + "X": 3.9, + "Y": -3.2, + "Z": 2.9 + }, + { + "X": 3.9, + "Y": -3.2, + "Z": 4.3 + } + ] + }, + { + "Id": 6, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 3 + }, + "Vertices": [ + { + "X": -1, + "Y": 3, + "Z": -1.19209E-08 + }, + { + "X": -1, + "Y": 3, + "Z": 2.9 + }, + { + "X": -2.9, + "Y": 3, + "Z": 2.9 + }, + { + "X": -2.9, + "Y": 3, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 7, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 3 + }, + "Vertices": [ + { + "X": 2, + "Y": 3, + "Z": 2.9 + }, + { + "X": 2, + "Y": 3, + "Z": -1.19209E-08 + }, + { + "X": 3.9, + "Y": 3, + "Z": -1.19209E-08 + }, + { + "X": 3.9, + "Y": 3, + "Z": 2.9 + } + ] + }, + { + "Id": 8, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 3 + }, + "Vertices": [ + { + "X": -1, + "Y": 3, + "Z": -1.19209E-08 + }, + { + "X": 2, + "Y": 3, + "Z": -1.19209E-08 + }, + { + "X": 2, + "Y": 3, + "Z": 1 + }, + { + "X": -1, + "Y": 3, + "Z": 1 + } + ] + }, + { + "Id": 9, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -1, + "Y": 0, + "Z": 0 + }, + "D": 3.9 + }, + "Vertices": [ + { + "X": 3.9, + "Y": -1.8519, + "Z": 2.9 + }, + { + "X": 3.9, + "Y": -1.85189, + "Z": 0 + }, + { + "X": 3.9, + "Y": -3.2, + "Z": -1.19209E-08 + }, + { + "X": 3.9, + "Y": -3.2, + "Z": 2.9 + } + ] + }, + { + "Id": 10, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -1, + "Y": 0, + "Z": 0 + }, + "D": 3.9 + }, + "Vertices": [ + { + "X": 3.9, + "Y": 0.0526863, + "Z": 0 + }, + { + "X": 3.9, + "Y": 0.052686, + "Z": 2.9 + }, + { + "X": 3.9, + "Y": 3, + "Z": 2.9 + }, + { + "X": 3.9, + "Y": 3, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 11, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -1, + "Y": 0, + "Z": 0 + }, + "D": 3.9 + }, + "Vertices": [ + { + "X": 3.9, + "Y": 0.0526863, + "Z": 2.5 + }, + { + "X": 3.9, + "Y": -1.8519, + "Z": 2.5 + }, + { + "X": 3.9, + "Y": -1.8519, + "Z": 2.9 + }, + { + "X": 3.9, + "Y": 0.052686, + "Z": 2.9 + } + ] + }, + { + "Id": 12, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 3.2 + }, + "Vertices": [ + { + "X": -2.9, + "Y": -3.2, + "Z": -1.19209E-08 + }, + { + "X": -2.9, + "Y": -3.2, + "Z": 1 + }, + { + "X": 3.9, + "Y": -3.2, + "Z": 1 + }, + { + "X": 3.9, + "Y": -3.2, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 13, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 3.2 + }, + "Vertices": [ + { + "X": 1.68, + "Y": -3.2, + "Z": 1 + }, + { + "X": 1.68, + "Y": -3.2, + "Z": 2.9 + }, + { + "X": 3.9, + "Y": -3.2, + "Z": 2.9 + }, + { + "X": 3.9, + "Y": -3.2, + "Z": 1 + } + ] + }, + { + "Id": 14, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 3.2 + }, + "Vertices": [ + { + "X": 1.68, + "Y": -3.2, + "Z": 1 + }, + { + "X": -1, + "Y": -3.2, + "Z": 1 + }, + { + "X": -1, + "Y": -3.2, + "Z": 2.9 + }, + { + "X": 1.68, + "Y": -3.2, + "Z": 2.9 + } + ] + }, + { + "Id": 15, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 3.2 + }, + "Vertices": [ + { + "X": -2.9, + "Y": -3.2, + "Z": 2.9 + }, + { + "X": -1, + "Y": -3.2, + "Z": 2.9 + }, + { + "X": -1, + "Y": -3.2, + "Z": 1 + }, + { + "X": -2.9, + "Y": -3.2, + "Z": 1 + } + ] + }, + { + "Id": 16, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 0.63059264, + "Z": -0.776114 + }, + "D": 5.3551865 + }, + "Vertices": [ + { + "X": -1.14552, + "Y": 2.38419E-08, + "Z": 6.9 + }, + { + "X": 1.94952, + "Y": 2.38419E-08, + "Z": 6.9 + }, + { + "X": 3.9, + "Y": -3.2, + "Z": 4.3 + }, + { + "X": -2.9, + "Y": -3.2, + "Z": 4.3 + } + ] + }, + { + "Id": 17, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -0.65493053, + "Z": -0.7556891 + }, + "D": 5.214255 + }, + "Vertices": [ + { + "X": 1.94952, + "Y": 2.38419E-08, + "Z": 6.9 + }, + { + "X": -1.14552, + "Y": 2.38419E-08, + "Z": 6.9 + }, + { + "X": -2.9, + "Y": 3, + "Z": 4.3 + }, + { + "X": 3.9, + "Y": 3, + "Z": 4.3 + } + ] + }, + { + "Id": 18, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 0, + "Z": 1 + }, + "D": 1.19209E-08 + }, + "Vertices": [ + { + "X": 3.9, + "Y": 3, + "Z": -1.19209E-08 + }, + { + "X": -2.9, + "Y": 3, + "Z": -1.19209E-08 + }, + { + "X": -2.9, + "Y": -3.2, + "Z": -1.19209E-08 + }, + { + "X": 3.9, + "Y": -3.2, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 19, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": 2.9 + }, + "Vertices": [ + { + "X": -2.9, + "Y": 0.85, + "Z": 1 + }, + { + "X": -2.9, + "Y": -1.05, + "Z": 1 + }, + { + "X": -2.9, + "Y": -3.2, + "Z": -1.19209E-08 + }, + { + "X": -2.9, + "Y": 3, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 20, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": 2.9 + }, + "Vertices": [ + { + "X": -2.9, + "Y": -1.05, + "Z": 1 + }, + { + "X": -2.9, + "Y": -1.05, + "Z": 2.9 + }, + { + "X": -2.9, + "Y": -3.2, + "Z": 2.9 + }, + { + "X": -2.9, + "Y": -3.2, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 21, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": 2.9 + }, + "Vertices": [ + { + "X": -2.9, + "Y": 3, + "Z": -1.19209E-08 + }, + { + "X": -2.9, + "Y": 3, + "Z": 2.9 + }, + { + "X": -2.9, + "Y": 0.85, + "Z": 2.9 + }, + { + "X": -2.9, + "Y": 0.85, + "Z": 1 + } + ] + }, + { + "Id": 22, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": 2.9 + }, + "Vertices": [ + { + "X": -2.9, + "Y": -3.2, + "Z": 2.9 + }, + { + "X": -2.9, + "Y": -1.05, + "Z": 2.9 + }, + { + "X": -2.9, + "Y": -1.05, + "Z": 3.55 + }, + { + "X": -2.9, + "Y": -3.2, + "Z": 4.3 + } + ] + }, + { + "Id": 23, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": 2.9000003 + }, + "Vertices": [ + { + "X": -2.9, + "Y": -1.05, + "Z": 3.55 + }, + { + "X": -2.9, + "Y": 0.85, + "Z": 3.55 + }, + { + "X": -2.9, + "Y": 3, + "Z": 4.3 + } + ] + }, + { + "Id": 24, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": 2.9000003 + }, + "Vertices": [ + { + "X": -2.9, + "Y": -1.05, + "Z": 3.55 + }, + { + "X": -2.9, + "Y": 3, + "Z": 4.3 + }, + { + "X": -2.9, + "Y": -3.2, + "Z": 4.3 + } + ] + }, + { + "Id": 25, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": 2.9000003 + }, + "Vertices": [ + { + "X": -2.9, + "Y": 0.85, + "Z": 3.55 + }, + { + "X": -2.9, + "Y": 3, + "Z": 2.9 + }, + { + "X": -2.9, + "Y": 3, + "Z": 4.3 + } + ] + }, + { + "Id": 26, + "NumPoints": 3, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": 2.9000003 + }, + "Vertices": [ + { + "X": -2.9, + "Y": 0.85, + "Z": 3.55 + }, + { + "X": -2.9, + "Y": 0.85, + "Z": 2.9 + }, + { + "X": -2.9, + "Y": 3, + "Z": 2.9 + } + ] + }, + { + "Id": 27, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": -3.9 + }, + "Vertices": [ + { + "X": 3.9, + "Y": -1.8519, + "Z": 2.5 + }, + { + "X": 3.9, + "Y": -1.85189, + "Z": 0 + }, + { + "X": 3.9, + "Y": 0.0526863, + "Z": 0 + }, + { + "X": 3.9, + "Y": 0.0526863, + "Z": 2.5 + } + ] + }, + { + "Id": 28, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": 2.9 + }, + "Vertices": [ + { + "X": -2.9, + "Y": 0.85, + "Z": 3.55 + }, + { + "X": -2.9, + "Y": -1.05, + "Z": 3.55 + }, + { + "X": -2.9, + "Y": -1.05, + "Z": 1 + }, + { + "X": -2.9, + "Y": 0.85, + "Z": 1 + } + ] + } + ], + "Portals": [ + { + "OtherCellId": 371, + "PolygonId": 27, + "Flags": 1 + }, + { + "OtherCellId": 367, + "PolygonId": 28, + "Flags": 3 + } + ], + "VisibleCellIds": [ + 2847146351, + 2847146352, + 2847146353, + 2847146355, + 2847146356, + 2847146357 + ] +} \ No newline at end of file diff --git a/tests/AcDream.Core.Tests/Fixtures/flap-doorway/0xA9B40173.json b/tests/AcDream.Core.Tests/Fixtures/flap-doorway/0xA9B40173.json new file mode 100644 index 00000000..aa6a9d84 --- /dev/null +++ b/tests/AcDream.Core.Tests/Fixtures/flap-doorway/0xA9B40173.json @@ -0,0 +1,413 @@ +{ + "CellId": 2847146355, + "WorldTransform": { + "M11": -1, + "M12": 8.74228E-08, + "M13": 0, + "M14": 0, + "M21": -8.74228E-08, + "M22": -1, + "M23": 0, + "M24": 0, + "M31": 0, + "M32": 0, + "M33": 1, + "M34": 0, + "M41": 161.929, + "M42": 7.50315, + "M43": 94, + "M44": 1 + }, + "InverseWorldTransform": { + "M11": -1, + "M12": -8.74228E-08, + "M13": 0, + "M14": -0, + "M21": 8.74228E-08, + "M22": -1, + "M23": 0, + "M24": 0, + "M31": 0, + "M32": 0, + "M33": 1, + "M34": -0, + "M41": 161.929, + "M42": 7.5031643, + "M43": -94, + "M44": 1 + }, + "ResolvedPolygons": [ + { + "Id": 0, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 5.9604556E-08, + "Y": 0, + "Z": 1 + }, + "D": -2.3245777E-07 + }, + "Vertices": [ + { + "X": 3.9, + "Y": 0.0526863, + "Z": 0 + }, + { + "X": 3.9, + "Y": -1.85189, + "Z": 0 + }, + { + "X": 4.1, + "Y": -1.85189, + "Z": -1.19209E-08 + }, + { + "X": 4.1, + "Y": 0.0526863, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 1, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -7.54372E-07, + "Y": -1, + "Z": -6.0349706E-08 + }, + "D": 0.052689318 + }, + "Vertices": [ + { + "X": 4.1, + "Y": 0.052686, + "Z": 2.5 + }, + { + "X": 3.9, + "Y": 0.0526863, + "Z": 2.5 + }, + { + "X": 3.9, + "Y": 0.0526863, + "Z": 0 + }, + { + "X": 4.1, + "Y": 0.0526863, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 2, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 0, + "Z": -1 + }, + "D": 2.5 + }, + "Vertices": [ + { + "X": 4.1, + "Y": -1.85189, + "Z": 2.5 + }, + { + "X": 3.9, + "Y": -1.8519, + "Z": 2.5 + }, + { + "X": 3.9, + "Y": 0.0526863, + "Z": 2.5 + }, + { + "X": 4.1, + "Y": 0.052686, + "Z": 2.5 + } + ] + }, + { + "Id": 3, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -2.5033974E-05, + "Y": 1, + "Z": 2.002716E-06 + }, + "D": 1.8519901 + }, + "Vertices": [ + { + "X": 3.9, + "Y": -1.85189, + "Z": 0 + }, + { + "X": 3.9, + "Y": -1.8519, + "Z": 2.5 + }, + { + "X": 4.1, + "Y": -1.85189, + "Z": 2.5 + }, + { + "X": 4.1, + "Y": -1.85189, + "Z": -1.19209E-08 + } + ] + } + ], + "PortalPolygons": [ + { + "Id": 0, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 0, + "Z": -1 + }, + "D": 2.5 + }, + "Vertices": [ + { + "X": 4.1, + "Y": -1.85189, + "Z": 2.5 + }, + { + "X": 3.9, + "Y": -1.8519, + "Z": 2.5 + }, + { + "X": 3.9, + "Y": 0.0526863, + "Z": 2.5 + }, + { + "X": 4.1, + "Y": 0.052686, + "Z": 2.5 + } + ] + }, + { + "Id": 1, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -2.5033974E-05, + "Y": 1, + "Z": 2.002716E-06 + }, + "D": 1.8519901 + }, + "Vertices": [ + { + "X": 3.9, + "Y": -1.85189, + "Z": 0 + }, + { + "X": 3.9, + "Y": -1.8519, + "Z": 2.5 + }, + { + "X": 4.1, + "Y": -1.85189, + "Z": 2.5 + }, + { + "X": 4.1, + "Y": -1.85189, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 2, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 5.9604556E-08, + "Y": 0, + "Z": 1 + }, + "D": -2.3245777E-07 + }, + "Vertices": [ + { + "X": 3.9, + "Y": 0.0526863, + "Z": 0 + }, + { + "X": 3.9, + "Y": -1.85189, + "Z": 0 + }, + { + "X": 4.1, + "Y": -1.85189, + "Z": -1.19209E-08 + }, + { + "X": 4.1, + "Y": 0.0526863, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 3, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -7.54372E-07, + "Y": -1, + "Z": -6.0349706E-08 + }, + "D": 0.052689318 + }, + "Vertices": [ + { + "X": 4.1, + "Y": 0.052686, + "Z": 2.5 + }, + { + "X": 3.9, + "Y": 0.0526863, + "Z": 2.5 + }, + { + "X": 3.9, + "Y": 0.0526863, + "Z": 0 + }, + { + "X": 4.1, + "Y": 0.0526863, + "Z": -1.19209E-08 + } + ] + }, + { + "Id": 4, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -1, + "Y": 0, + "Z": 0 + }, + "D": 4.1 + }, + "Vertices": [ + { + "X": 4.1, + "Y": 0.052686, + "Z": 2.5 + }, + { + "X": 4.1, + "Y": 0.0526863, + "Z": -1.19209E-08 + }, + { + "X": 4.1, + "Y": -1.85189, + "Z": -1.19209E-08 + }, + { + "X": 4.1, + "Y": -1.85189, + "Z": 2.5 + } + ] + }, + { + "Id": 5, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": -3.9 + }, + "Vertices": [ + { + "X": 3.9, + "Y": -1.8519, + "Z": 2.5 + }, + { + "X": 3.9, + "Y": -1.85189, + "Z": 0 + }, + { + "X": 3.9, + "Y": 0.0526863, + "Z": 0 + }, + { + "X": 3.9, + "Y": 0.0526863, + "Z": 2.5 + } + ] + } + ], + "Portals": [ + { + "OtherCellId": 369, + "PolygonId": 4, + "Flags": 3 + }, + { + "OtherCellId": 370, + "PolygonId": 5, + "Flags": 3 + } + ], + "VisibleCellIds": [ + 2847146351, + 2847146352, + 2847146353, + 2847146354, + 2847146356, + 2847146357 + ] +} \ No newline at end of file diff --git a/tests/AcDream.Core.Tests/Fixtures/flap-doorway/0xA9B40174.json b/tests/AcDream.Core.Tests/Fixtures/flap-doorway/0xA9B40174.json new file mode 100644 index 00000000..a47a8941 --- /dev/null +++ b/tests/AcDream.Core.Tests/Fixtures/flap-doorway/0xA9B40174.json @@ -0,0 +1,583 @@ +{ + "CellId": 2847146356, + "WorldTransform": { + "M11": -1, + "M12": 8.74228E-08, + "M13": 0, + "M14": 0, + "M21": -8.74228E-08, + "M22": -1, + "M23": 0, + "M24": 0, + "M31": 0, + "M32": 0, + "M33": 1, + "M34": 0, + "M41": 161.929, + "M42": 7.50315, + "M43": 94, + "M44": 1 + }, + "InverseWorldTransform": { + "M11": -1, + "M12": -8.74228E-08, + "M13": 0, + "M14": -0, + "M21": 8.74228E-08, + "M22": -1, + "M23": 0, + "M24": 0, + "M31": 0, + "M32": 0, + "M33": 1, + "M34": -0, + "M41": 161.929, + "M42": 7.5031643, + "M43": -94, + "M44": 1 + }, + "ResolvedPolygons": [ + { + "Id": 0, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 0, + "Z": 1 + }, + "D": 3.999 + }, + "Vertices": [ + { + "X": 9, + "Y": -2.85, + "Z": -3.999 + }, + { + "X": 9, + "Y": 2.98, + "Z": -3.999 + }, + { + "X": -2.664, + "Y": 2.98, + "Z": -3.999 + }, + { + "X": -2.664, + "Y": -2.85, + "Z": -3.999 + } + ] + }, + { + "Id": 1, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -1, + "Y": 0, + "Z": 0 + }, + "D": 9 + }, + "Vertices": [ + { + "X": 9, + "Y": -2.85, + "Z": -0.364 + }, + { + "X": 9, + "Y": 2.98, + "Z": -0.364 + }, + { + "X": 9, + "Y": 2.98, + "Z": -3.999 + }, + { + "X": 9, + "Y": -2.85, + "Z": -3.999 + } + ] + }, + { + "Id": 2, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 0, + "Z": -1 + }, + "D": -0.364 + }, + "Vertices": [ + { + "X": 9, + "Y": 2.98, + "Z": -0.364 + }, + { + "X": 9, + "Y": 1.15, + "Z": -0.364 + }, + { + "X": 7, + "Y": 1.15, + "Z": -0.364 + }, + { + "X": -2.664, + "Y": 2.98, + "Z": -0.364 + } + ] + }, + { + "Id": 3, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 2.98 + }, + "Vertices": [ + { + "X": 9, + "Y": 2.98, + "Z": -3.999 + }, + { + "X": 9, + "Y": 2.98, + "Z": -0.364 + }, + { + "X": -2.664, + "Y": 2.98, + "Z": -0.364 + }, + { + "X": -2.664, + "Y": 2.98, + "Z": -3.999 + } + ] + }, + { + "Id": 4, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 0, + "Z": -1 + }, + "D": -0.364 + }, + "Vertices": [ + { + "X": 7, + "Y": 1.15, + "Z": -0.364 + }, + { + "X": 7, + "Y": -2.85, + "Z": -0.364 + }, + { + "X": -2.664, + "Y": -2.85, + "Z": -0.364 + }, + { + "X": -2.664, + "Y": 2.98, + "Z": -0.364 + } + ] + }, + { + "Id": 5, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 2.85 + }, + "Vertices": [ + { + "X": -2.664, + "Y": -2.85, + "Z": -0.364 + }, + { + "X": 9, + "Y": -2.85, + "Z": -0.364 + }, + { + "X": 9, + "Y": -2.85, + "Z": -3.999 + }, + { + "X": -2.664, + "Y": -2.85, + "Z": -3.999 + } + ] + }, + { + "Id": 6, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": 2.664 + }, + "Vertices": [ + { + "X": -2.664, + "Y": 2.98, + "Z": -3.999 + }, + { + "X": -2.664, + "Y": 2.98, + "Z": -0.364 + }, + { + "X": -2.664, + "Y": -2.85, + "Z": -0.364 + }, + { + "X": -2.664, + "Y": -2.85, + "Z": -3.999 + } + ] + } + ], + "PortalPolygons": [ + { + "Id": 0, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 0, + "Z": 1 + }, + "D": 3.999 + }, + "Vertices": [ + { + "X": 9, + "Y": -2.85, + "Z": -3.999 + }, + { + "X": 9, + "Y": 2.98, + "Z": -3.999 + }, + { + "X": -2.664, + "Y": 2.98, + "Z": -3.999 + }, + { + "X": -2.664, + "Y": -2.85, + "Z": -3.999 + } + ] + }, + { + "Id": 1, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 2.85 + }, + "Vertices": [ + { + "X": -2.664, + "Y": -2.85, + "Z": -0.364 + }, + { + "X": 9, + "Y": -2.85, + "Z": -0.364 + }, + { + "X": 9, + "Y": -2.85, + "Z": -3.999 + }, + { + "X": -2.664, + "Y": -2.85, + "Z": -3.999 + } + ] + }, + { + "Id": 2, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -1, + "Y": 0, + "Z": 0 + }, + "D": 9 + }, + "Vertices": [ + { + "X": 9, + "Y": -2.85, + "Z": -0.364 + }, + { + "X": 9, + "Y": 2.98, + "Z": -0.364 + }, + { + "X": 9, + "Y": 2.98, + "Z": -3.999 + }, + { + "X": 9, + "Y": -2.85, + "Z": -3.999 + } + ] + }, + { + "Id": 3, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 2.98 + }, + "Vertices": [ + { + "X": 9, + "Y": 2.98, + "Z": -3.999 + }, + { + "X": 9, + "Y": 2.98, + "Z": -0.364 + }, + { + "X": -2.664, + "Y": 2.98, + "Z": -0.364 + }, + { + "X": -2.664, + "Y": 2.98, + "Z": -3.999 + } + ] + }, + { + "Id": 4, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": 2.664 + }, + "Vertices": [ + { + "X": -2.664, + "Y": 2.98, + "Z": -3.999 + }, + { + "X": -2.664, + "Y": 2.98, + "Z": -0.364 + }, + { + "X": -2.664, + "Y": -2.85, + "Z": -0.364 + }, + { + "X": -2.664, + "Y": -2.85, + "Z": -3.999 + } + ] + }, + { + "Id": 5, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 0, + "Z": -1 + }, + "D": -0.364 + }, + "Vertices": [ + { + "X": 9, + "Y": 2.98, + "Z": -0.364 + }, + { + "X": 9, + "Y": 1.15, + "Z": -0.364 + }, + { + "X": 7, + "Y": 1.15, + "Z": -0.364 + }, + { + "X": -2.664, + "Y": 2.98, + "Z": -0.364 + } + ] + }, + { + "Id": 6, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 0, + "Z": -1 + }, + "D": -0.364 + }, + "Vertices": [ + { + "X": 7, + "Y": 1.15, + "Z": -0.364 + }, + { + "X": 7, + "Y": -2.85, + "Z": -0.364 + }, + { + "X": -2.664, + "Y": -2.85, + "Z": -0.364 + }, + { + "X": -2.664, + "Y": 2.98, + "Z": -0.364 + } + ] + }, + { + "Id": 7, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 0, + "Z": 1 + }, + "D": 0.364 + }, + "Vertices": [ + { + "X": 7, + "Y": 1.15, + "Z": -0.364 + }, + { + "X": 7, + "Y": -2.85, + "Z": -0.364 + }, + { + "X": 9, + "Y": -2.85, + "Z": -0.364 + }, + { + "X": 9, + "Y": 1.15, + "Z": -0.364 + } + ] + } + ], + "Portals": [ + { + "OtherCellId": 373, + "PolygonId": 7, + "Flags": 1 + } + ], + "VisibleCellIds": [ + 2847146351, + 2847146352, + 2847146353, + 2847146354, + 2847146355, + 2847146357 + ] +} \ No newline at end of file diff --git a/tests/AcDream.Core.Tests/Fixtures/flap-doorway/0xA9B40175.json b/tests/AcDream.Core.Tests/Fixtures/flap-doorway/0xA9B40175.json new file mode 100644 index 00000000..49a53370 --- /dev/null +++ b/tests/AcDream.Core.Tests/Fixtures/flap-doorway/0xA9B40175.json @@ -0,0 +1,413 @@ +{ + "CellId": 2847146357, + "WorldTransform": { + "M11": -1, + "M12": 8.74228E-08, + "M13": 0, + "M14": 0, + "M21": -8.74228E-08, + "M22": -1, + "M23": 0, + "M24": 0, + "M31": 0, + "M32": 0, + "M33": 1, + "M34": 0, + "M41": 161.929, + "M42": 7.50315, + "M43": 94, + "M44": 1 + }, + "InverseWorldTransform": { + "M11": -1, + "M12": -8.74228E-08, + "M13": 0, + "M14": -0, + "M21": 8.74228E-08, + "M22": -1, + "M23": 0, + "M24": 0, + "M31": 0, + "M32": 0, + "M33": 1, + "M34": -0, + "M41": 161.929, + "M42": 7.5031643, + "M43": -94, + "M44": 1 + }, + "ResolvedPolygons": [ + { + "Id": 0, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 1.15 + }, + "Vertices": [ + { + "X": 9, + "Y": 1.15, + "Z": 0 + }, + { + "X": 7, + "Y": 1.15, + "Z": -1.19209E-08 + }, + { + "X": 7, + "Y": 1.15, + "Z": -0.364 + }, + { + "X": 9, + "Y": 1.15, + "Z": -0.364 + } + ] + }, + { + "Id": 1, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 2.85 + }, + "Vertices": [ + { + "X": 7, + "Y": -2.85, + "Z": -0.364 + }, + { + "X": 7, + "Y": -2.85, + "Z": -1.19209E-08 + }, + { + "X": 9, + "Y": -2.85, + "Z": 1.11022E-16 + }, + { + "X": 9, + "Y": -2.85, + "Z": -0.364 + } + ] + }, + { + "Id": 2, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": -7 + }, + "Vertices": [ + { + "X": 7, + "Y": 1.15, + "Z": -1.19209E-08 + }, + { + "X": 7, + "Y": -2.85, + "Z": -1.19209E-08 + }, + { + "X": 7, + "Y": -2.85, + "Z": -0.364 + }, + { + "X": 7, + "Y": 1.15, + "Z": -0.364 + } + ] + }, + { + "Id": 3, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -1, + "Y": 0, + "Z": 0 + }, + "D": 9 + }, + "Vertices": [ + { + "X": 9, + "Y": -2.85, + "Z": 1.11022E-16 + }, + { + "X": 9, + "Y": 1.15, + "Z": 0 + }, + { + "X": 9, + "Y": 1.15, + "Z": -0.364 + }, + { + "X": 9, + "Y": -2.85, + "Z": -0.364 + } + ] + } + ], + "PortalPolygons": [ + { + "Id": 0, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": -1, + "Z": 0 + }, + "D": 1.15 + }, + "Vertices": [ + { + "X": 9, + "Y": 1.15, + "Z": 0 + }, + { + "X": 7, + "Y": 1.15, + "Z": -1.19209E-08 + }, + { + "X": 7, + "Y": 1.15, + "Z": -0.364 + }, + { + "X": 9, + "Y": 1.15, + "Z": -0.364 + } + ] + }, + { + "Id": 1, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 1, + "Y": 0, + "Z": 0 + }, + "D": -7 + }, + "Vertices": [ + { + "X": 7, + "Y": 1.15, + "Z": -1.19209E-08 + }, + { + "X": 7, + "Y": -2.85, + "Z": -1.19209E-08 + }, + { + "X": 7, + "Y": -2.85, + "Z": -0.364 + }, + { + "X": 7, + "Y": 1.15, + "Z": -0.364 + } + ] + }, + { + "Id": 2, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 1, + "Z": 0 + }, + "D": 2.85 + }, + "Vertices": [ + { + "X": 7, + "Y": -2.85, + "Z": -0.364 + }, + { + "X": 7, + "Y": -2.85, + "Z": -1.19209E-08 + }, + { + "X": 9, + "Y": -2.85, + "Z": 1.11022E-16 + }, + { + "X": 9, + "Y": -2.85, + "Z": -0.364 + } + ] + }, + { + "Id": 3, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": -1, + "Y": 0, + "Z": 0 + }, + "D": 9 + }, + "Vertices": [ + { + "X": 9, + "Y": -2.85, + "Z": 1.11022E-16 + }, + { + "X": 9, + "Y": 1.15, + "Z": 0 + }, + { + "X": 9, + "Y": 1.15, + "Z": -0.364 + }, + { + "X": 9, + "Y": -2.85, + "Z": -0.364 + } + ] + }, + { + "Id": 4, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 5.96045E-09, + "Y": -1.387775E-17, + "Z": -1 + }, + "D": -5.364405E-08 + }, + "Vertices": [ + { + "X": 9, + "Y": -2.85, + "Z": 1.11022E-16 + }, + { + "X": 7, + "Y": -2.85, + "Z": -1.19209E-08 + }, + { + "X": 7, + "Y": 1.15, + "Z": -1.19209E-08 + }, + { + "X": 9, + "Y": 1.15, + "Z": 0 + } + ] + }, + { + "Id": 5, + "NumPoints": 4, + "SidesType": 0, + "Plane": { + "Normal": { + "X": 0, + "Y": 0, + "Z": 1 + }, + "D": 0.364 + }, + "Vertices": [ + { + "X": 7, + "Y": 1.15, + "Z": -0.364 + }, + { + "X": 7, + "Y": -2.85, + "Z": -0.364 + }, + { + "X": 9, + "Y": -2.85, + "Z": -0.364 + }, + { + "X": 9, + "Y": 1.15, + "Z": -0.364 + } + ] + } + ], + "Portals": [ + { + "OtherCellId": 369, + "PolygonId": 4, + "Flags": 3 + }, + { + "OtherCellId": 372, + "PolygonId": 5, + "Flags": 3 + } + ], + "VisibleCellIds": [ + 2847146351, + 2847146352, + 2847146353, + 2847146354, + 2847146355, + 2847146356 + ] +} \ No newline at end of file diff --git a/tests/AcDream.Core.Tests/Input/PlayerMovementControllerTests.cs b/tests/AcDream.Core.Tests/Input/PlayerMovementControllerTests.cs index add3dae9..1dd41b37 100644 --- a/tests/AcDream.Core.Tests/Input/PlayerMovementControllerTests.cs +++ b/tests/AcDream.Core.Tests/Input/PlayerMovementControllerTests.cs @@ -34,6 +34,97 @@ public class PlayerMovementControllerTests Assert.Equal(96f, result.Position.Y, precision: 1); } + // ── Indoor-flap root cause: resting-body bit-stability ──────────────────── + // + // The indoor render "flap" (textures battling at the cottage doorway) is + // portal-flood membership instability. PortalVisibilityBuilder.Build is a + // proven-deterministic pure function, so the membership can only flip if its + // INPUT (the camera eye, derived from the player RenderPosition) varies. + // Live 6-dp capture (pvinput.log:54) shows the player RenderPosition carries + // a perpetual ~1-ULP flicker at rest (Z 94.000000 <-> 93.999992 — exactly one + // float mantissa step). ComputeRenderPosition is Vector3.Lerp(_prevPhysicsPos, + // _currPhysicsPos, alpha), and Lerp(a, a, t) == a exactly, so a jittering + // RenderPosition at rest means the physics body's resting Position is NOT + // bit-stable between ticks. Retail's authoritative local position is bit-stable + // at rest (validate_transition -> kill_velocity on every grounded contact), so + // retail never flaps. + // + // This test pins the physics-side invariant: a grounded body with no input + // must hold a byte-identical position across many frames. It PASSES — which + // is itself the evidence: the physics resting position is bit-stable, so the + // doorway flap is NOT a physics-rest jitter. See + // docs/research/2026-06-08-flap-physics-diagnosis-REFUTED-its-render-membership.md + // (the flap is render-side portal-flood membership instability at the grazing + // doorway portal under a sweeping camera eye). Kept as a regression guard. + [Fact] + public void Update_AtRestNoInput_RenderPositionBitStableAcrossManyFrames() + { + var engine = MakeFlatEngine(); + var controller = new PlayerMovementController(engine); + var rest = new Vector3(96f, 96f, 50f); + controller.SetPosition(rest, 0x0001); + + // Settle one frame so the resolver establishes its rest state, then + // capture the baseline the body must hold. + var settled = controller.Update(1f / 60f, new MovementInput()); + Vector3 baselineRender = settled.RenderPosition; + Vector3 baselinePhysics = settled.Position; + + // Hold still for ~10 s of 60 Hz frames (crosses MinQuantum every ~2 + // frames, so the 30 Hz physics tick fires throughout — same cadence as + // live). Any deviation, even one ULP, is the flap's root cause. + float maxRenderDev = 0f; + float maxPhysicsDev = 0f; + for (int i = 0; i < 600; i++) + { + var r = controller.Update(1f / 60f, new MovementInput()); + maxRenderDev = MathF.Max(maxRenderDev, (r.RenderPosition - baselineRender).Length()); + maxPhysicsDev = MathF.Max(maxPhysicsDev, (r.Position - baselinePhysics).Length()); + } + + Assert.True( + maxRenderDev == 0f && maxPhysicsDev == 0f, + $"resting body drifted: render={maxRenderDev * 1e6f:F3} µm, " + + $"physics={maxPhysicsDev * 1e6f:F3} µm; expected byte-identical rest"); + } + + // After walking then releasing input, the body must SETTLE to a + // byte-identical resting position — not keep blipping a residual velocity. + // This models the live flap: the player walks to the cottage doorway and + // stops, and the eye then carries a ~1-ULP jitter that flips portal-flood + // membership. Flat-terrain variant: if even this drifts, the residual-after- + // motion path is the root and it is not indoor-specific. + [Fact] + public void Update_WalkThenStop_SettlesToBitStableRest() + { + var engine = MakeFlatEngine(); + var controller = new PlayerMovementController(engine); + controller.SetPosition(new Vector3(96f, 96f, 50f), 0x0001); + controller.Yaw = 0f; + + // Walk forward ~0.5 s, then release. + for (int i = 0; i < 30; i++) + controller.Update(1f / 60f, new MovementInput(Forward: true)); + // Let velocity decay / state settle. + for (int i = 0; i < 30; i++) + controller.Update(1f / 60f, new MovementInput()); + + var settled = controller.Update(1f / 60f, new MovementInput()); + Vector3 basePos = settled.Position; + Vector3 baseRender = settled.RenderPosition; + + float maxPos = 0f, maxRender = 0f; + for (int i = 0; i < 600; i++) + { + var r = controller.Update(1f / 60f, new MovementInput()); + maxPos = MathF.Max(maxPos, (r.Position - basePos).Length()); + maxRender = MathF.Max(maxRender, (r.RenderPosition - baseRender).Length()); + } + + Assert.True(maxPos == 0f && maxRender == 0f, + $"post-walk rest drifted: pos={maxPos * 1e6f:F3} µm, render={maxRender * 1e6f:F3} µm"); + } + [Fact] public void Update_ForwardInput_MovesInFacingDirection() { diff --git a/tests/AcDream.Core.Tests/Physics/CellarUpTrajectoryReplayTests.cs b/tests/AcDream.Core.Tests/Physics/CellarUpTrajectoryReplayTests.cs index 0054a823..04b73dbb 100644 --- a/tests/AcDream.Core.Tests/Physics/CellarUpTrajectoryReplayTests.cs +++ b/tests/AcDream.Core.Tests/Physics/CellarUpTrajectoryReplayTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Numerics; +using AcDream.App.Input; using AcDream.Core.Physics; using DatReaderWriter.Enums; using DatReaderWriter.Types; @@ -136,6 +137,137 @@ public class CellarUpTrajectoryReplayTests : IDisposable // Tests // ─────────────────────────────────────────────────────────────── + /// + /// Indoor-flap root cause (2026-06-08). A body resting on the cellar + /// floor with ZERO requested motion must hold a byte-identical position + /// across many ticks — retail's authoritative local position is bit-stable + /// at rest (validate_transition → kill_velocity + SetWalkable on every + /// grounded contact, decomp :272567/:274467). + /// + /// + /// The indoor render "flap" (textures battling at the cottage doorway) is + /// portal-flood membership instability. PortalVisibilityBuilder.Build is a + /// proven-deterministic pure function, so the membership can only flip if its + /// INPUT (the camera eye, from the player RenderPosition) varies. + /// RenderPosition = Lerp(_prevPhysicsPos, _currPhysicsPos), and Lerp(a,a,t)==a, + /// so a jittering eye at rest means the physics body's resting Position is not + /// bit-stable. Flat LandCell terrain rest IS bit-stable + /// (. + /// Update_AtRestNoInput_RenderPositionBitStableAcrossManyFrames passes); the + /// instability is the INDOOR path — the floor-touch is classified + /// walkable=False (no walkable-polygon anchor), so each tick re-fires a + /// step-down probe whose re-found Z is not bit-stable. + /// + /// + /// PASSES — the indoor resting body is bit-stable even with the + /// grounded/cp=none contradictory state present. This is evidence (with the + /// flat-terrain variant) that the doorway flap is NOT a physics-rest jitter; + /// it is render-side portal-flood membership instability under a sweeping eye. + /// See docs/research/2026-06-08-flap-physics-diagnosis-REFUTED-its-render-membership.md. + /// The diagnostic log (on any future regression) names the failing per-tick + /// condition. Kept as a regression guard. + /// + [Fact] + public void IndoorCellarFloor_AtRestZeroOffset_BodyPositionBitStable() + { + var (engine, _) = BuildEngineWithCellarFixtures(); + + // Body seeded exactly at its natural resting pose on the cellar floor, + // WITH the walkable-polygon + contact-plane anchor (BuildInitialBody) — + // i.e. the most-favourable starting state. If even this drifts, the rest + // path fails to PERSIST the anchor. + var body = BuildInitialBody(); + var rest = body.Position; + uint cell = CellarId; + bool grounded = true; + + var log = new List(); + float maxDrift = 0f; + for (int tick = 1; tick <= 200; tick++) + { + // ZERO requested motion: currentPos == targetPos == rest pose. + var result = engine.ResolveWithTransition( + currentPos: body.Position, + targetPos: body.Position, + cellId: cell, + sphereRadius: SphereRadius, + sphereHeight: SphereHeight, + stepUpHeight: StepUpHeight, + stepDownHeight: StepDownHeight, + isOnGround: grounded, + body: body, + moverFlags: ObjectInfoState.IsPlayer | ObjectInfoState.EdgeSlide, + movingEntityId: 0); + + body.Position = result.Position; + cell = result.CellId; + grounded = result.IsOnGround; + + float drift = (body.Position - rest).Length(); + maxDrift = MathF.Max(maxDrift, drift); + + if (tick <= 6 || drift > 0f) + { + log.Add(string.Format(System.Globalization.CultureInfo.InvariantCulture, + "tick{0,3}: pos=({1:F7},{2:F7},{3:F7}) drift={4:F3}µm grounded={5} " + + "walkable={6} cpV={7} ts=0x{8:X}", + tick, body.Position.X, body.Position.Y, body.Position.Z, + drift * 1e6f, grounded, body.WalkablePolygonValid, + body.ContactPlaneValid, (uint)body.TransientState)); + } + } + + Assert.True(maxDrift == 0f, + $"cellar-floor rest drifted {maxDrift * 1e6f:F3} µm (expected byte-identical):\n " + + string.Join("\n ", log.Take(24))); + } + + /// + /// Indoor-flap investigation (2026-06-08) — the FULL production loop. Drives + /// (integration + flag logic + velocity, + /// not just the resolver) on the indoor cellar engine with NO input. PASSES — + /// the RenderPosition the camera reads is byte-identical at rest, confirming + /// the flap is not produced by the indoor controller rest loop. Kept as a + /// regression guard. See + /// docs/research/2026-06-08-flap-physics-diagnosis-REFUTED-its-render-membership.md. + /// + [Fact] + public void IndoorCell_FullController_AtRestNoInput_RenderPositionBitStable() + { + var (engine, _) = BuildEngineWithCellarFixtures(); + var controller = new PlayerMovementController(engine); + controller.SetPosition(InitialSphereWorld, CellarId); + + var settled = controller.Update(1f / 60f, new MovementInput()); + var basePos = settled.Position; + var baseRender = settled.RenderPosition; + + var log = new List(); + float maxPos = 0f, maxRender = 0f; + for (int i = 1; i <= 600; i++) + { + var r = controller.Update(1f / 60f, new MovementInput()); + float dp = (r.Position - basePos).Length(); + float dr = (r.RenderPosition - baseRender).Length(); + maxPos = MathF.Max(maxPos, dp); + maxRender = MathF.Max(maxRender, dr); + if (i <= 4 || dp > 0f || dr > 0f) + { + log.Add(string.Format(System.Globalization.CultureInfo.InvariantCulture, + "f{0,3}: pos=({1:F7},{2:F7},{3:F7}) render=({4:F7},{5:F7},{6:F7}) " + + "grounded={7} cell=0x{8:X8}", + i, r.Position.X, r.Position.Y, r.Position.Z, + r.RenderPosition.X, r.RenderPosition.Y, r.RenderPosition.Z, + r.IsOnGround, r.CellId)); + } + } + + Assert.True(maxPos == 0f && maxRender == 0f, + $"indoor controller rest drifted: pos={maxPos * 1e6f:F3} µm, " + + $"render={maxRender * 1e6f:F3} µm (expected byte-identical):\n " + + string.Join("\n ", log.Take(24))); + } + /// /// Confirms the harness compiles, the engine runs the simulation, /// and a trajectory comes back with the expected number of points.