docs(#186): handoff — connector grey flap narrowed to the doorway-flap flood/pick family
Retail SEAMLESS at the same spot (user-confirmed) => real acdream bug: the eye seats in a sparse 5-poly connector cell 0xF6820118 looking back at the player's room 0116, and acdream drops 0116 (back-portal side-culled) so the doorway aperture shows the fog clear color = grey; retail keeps 0116 drawn. RULED OUT: null-root/AD-20/AD-21 (root valid, eyeInRoot=Y); the color-clear gating (retail's gated DrawCells Clear is depth/stencil, post-LScape::draw). Next step = retail cdb trace (viewer_cell + cell_draw_list at the grey pose) to pin viewer-cell PICK vs portal FLOOD, then a careful frozen-render fix. Full handoff + apparatus + DO-NOT-RETRY + code/decomp sites in the doc. Keeps the offline cell-geometry inspection test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
cd42369581
commit
9d35a9786f
3 changed files with 266 additions and 11 deletions
|
|
@ -56,18 +56,24 @@ parts of the house. Passing through → a brief **GREY** flap (world background)
|
||||||
spot → the WHOLE screen is grey and stays; **turning the camera clears it, turning back → grey again**
|
spot → the WHOLE screen is grey and stays; **turning the camera clears it, turning back → grey again**
|
||||||
(camera-direction dependent, player stationary).
|
(camera-direction dependent, player stationary).
|
||||||
|
|
||||||
**Root cause / status (BY CLASS — unconfirmed for this instance):** the doorway-FLAP family. Grey =
|
**Root cause — NARROWED (report-only; retail SEAMLESS at the same spot → real acdream bug). FULL HANDOFF:
|
||||||
the world/sky background drawn via the null-clipRoot legacy fallback (**AD-21**) when the renderer has
|
`docs/research/2026-07-08-186-connector-grey-flap-handoff.md`.** The eye seats in a sparse 5-render-poly
|
||||||
no indoor visibility root that frame. The camera-direction dependence points at the **camera-EYE
|
CONNECTOR cell `0xF6820118` (between player room `0116` and room `0117`), in the doorway threshold looking
|
||||||
viewer-cell resolution** (**AD-20**): the eye seats via the *player's* cell stab-list, so a
|
BACK toward `0116`; acdream's render DROPS `0116` (its back-portal is side-culled) so the doorway aperture
|
||||||
connecting-room cell/portal topology this house type introduces can seat the eye in the wrong/no cell
|
shows the fog clear color = grey. Retail keeps `0116` drawn. **RULED OUT (do not revisit):** null-root/AD-21
|
||||||
→ viewer root null → grey; turning re-seats the eye. **Distinct from #176/#181** (a LIT flicker,
|
(root valid indoor 88248/88249 frames); viewer-cell root-lag/AD-20 (`eyeInRoot=Y` — eye genuinely inside
|
||||||
parked) — this is a GREY/background flap. SSOT: `claude-memory/project_render_pipeline_digest.md`
|
`0118`'s BSP); the color-clear gating (retail's gated `Clear` @ DrawCells `0x005a48a9` is depth/stencil,
|
||||||
("root at VIEWER cell"; flap DO-NOT-RETRY table).
|
fires AFTER `LScape::draw` — the tempting "3-line fix" is DEAD). Confirmed via workflow `wf_362bbeda-933`
|
||||||
|
(4 tracers + synthesis) + live `ACDREAM_PROBE_FLAP` capture + offline cell dump. It's the doorway-FLAP
|
||||||
|
FLOOD/PICK family (frozen render, DO-NOT-RETRY-heavy — the deleted `EyeInsidePortalOpening` rescue, the
|
||||||
|
`PortalSideEpsilon` root-lag).
|
||||||
|
|
||||||
**Next step (apparatus, when resumed):** `ACDREAM_PROBE_FLAP=1` capture at the exact connecting room —
|
**Next step (retail cdb, do FIRST — binary MATCHES `refs/acclient.pdb`):** at the grey pose, `bp
|
||||||
stop where grey, turn to clear, turn back to grey ×2. Confirm the viewer-cell root goes null there,
|
SmartBox::update_viewer 0x00453ce0` (dump `viewer_cell` id) and/or `bp PView::DrawCells 0x005a4840` (dump
|
||||||
which cell the eye seats in, and whether the portal flood admits the connecting room.
|
`cell_draw_list`). retail `viewer_cell==0116` → PICK fix (acdream over-switches to the connector /
|
||||||
|
boom coasts the eye in at `pulledIn=-0.00`); retail roots `0118` but draws `0116` too → FLOOD fix
|
||||||
|
(retail admits the back-portal; acdream's side-CULL is the site). Apparatus + code sites + DO-NOT-RETRY
|
||||||
|
in the handoff.
|
||||||
|
|
||||||
**Acceptance:** no grey flap passing through OR stopping in the connecting room at any camera angle;
|
**Acceptance:** no grey flap passing through OR stopping in the connecting room at any camera angle;
|
||||||
regression pass on the outside→inside doorway + other multi-room houses.
|
regression pass on the outside→inside doorway + other multi-room houses.
|
||||||
|
|
|
||||||
149
docs/research/2026-07-08-186-connector-grey-flap-handoff.md
Normal file
149
docs/research/2026-07-08-186-connector-grey-flap-handoff.md
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
# Handoff: #186 — indoor GREY flap at a top-floor connecting room (new house type)
|
||||||
|
|
||||||
|
**Date:** 2026-07-08 · **Status:** ROOT CAUSE NARROWED (report-only) — fix NOT started. It lives in
|
||||||
|
the FROZEN portal-flood / viewer-cell render code (the doorway-FLAP family that cost weeks), so it needs
|
||||||
|
a **retail cdb trace** to pin the last fork BEFORE any code, then a careful change. Milestone: M1.5.
|
||||||
|
|
||||||
|
**Read these first, in order:**
|
||||||
|
1. This handoff.
|
||||||
|
2. `claude-memory/project_render_pipeline_digest.md` — the render/doorway-FLAP SSOT + DO-NOT-RETRY table
|
||||||
|
("root at VIEWER cell"; the deleted EyeInsidePortalOpening rescue; PortalSideEpsilon root-lag).
|
||||||
|
3. `docs/architecture/retail-divergence-register.md` rows **AD-17** (≤8 clip-plane / union-AABB scissor),
|
||||||
|
**AD-20** (camera-sweep fallback seeds eye cell from PLAYER cell), **AD-21** (null-clipRoot outdoor fallback).
|
||||||
|
4. `docs/ISSUES.md` #186.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. Symptom (user, live)
|
||||||
|
|
||||||
|
Top floor of a **house type not tried before**; a connecting opening/room between two parts of the house.
|
||||||
|
Passing through → a brief **GREY** flap (the framebuffer clear color). Stopping at the precise spot → the
|
||||||
|
WHOLE screen is grey and stays; **turning the camera clears it, turning back → grey again** (camera-DIRECTION
|
||||||
|
dependent, player stationary). A jump is NOT involved (that's #185). **Retail is SEAMLESS at the same spot
|
||||||
|
(user-confirmed) → this is a real acdream bug, not inherent.**
|
||||||
|
|
||||||
|
## 1. The cells + the grey pose (ACDREAM_PROBE_FLAP capture)
|
||||||
|
|
||||||
|
Landblock `0xF682`. Three cells, all envId `0x0365`, cell-struct origin `(132,84,62)`:
|
||||||
|
|
||||||
|
| cell | render polys | physics polys | portals | role |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `0xF6820116` | **15** | 12 | →`0117,0118,0113` | player's room (closed) |
|
||||||
|
| `0xF6820117` | **10** | 6 | →`0118,0116,0115,0119` | next room (closed) |
|
||||||
|
| `0xF6820118` | **5** | 3 | →`0117,0116` | **thin connector** (root when grey) |
|
||||||
|
|
||||||
|
Grey-pose facts (from `[flap]`/`[flap-cam]`/`[flap-sweep]`, 88,249 frames; the grey frames are `vis=1`
|
||||||
|
`root=0xF6820118`, ~6,725 frames):
|
||||||
|
- `[flap-cam]`: `root=viewerCell=0xF6820118 playerCell=0xF6820116 eyeInRoot=Y playerInRoot=n
|
||||||
|
terrain=Skip outVisible=False eye=(134.81,83.59,69.07) player=(137.30,83.32,66.82)`.
|
||||||
|
- `[flap]` grey line: `root=0118 | p0->0x0117 D=0.14 TRV proj=4 clip=0 | p1->0x0116 D=0.18 CULL proj=5
|
||||||
|
clip=4 || outPolys=0 vis=1`. (Dominant D to 0117 ≈ 0.14; ALWAYS positive 0.00–0.50 → eye is genuinely
|
||||||
|
INSIDE 0118, never across the plane.)
|
||||||
|
- `[flap-sweep]`: `ok=True resolved=Y pulledIn=-0.00` (the chase boom reaches FULL distance, NO collision
|
||||||
|
pull-in, seating the eye in the connector).
|
||||||
|
|
||||||
|
**Read of the geometry:** the eye sits in the sparse connector `0118`, in the doorway threshold, looking
|
||||||
|
BACK toward the player's room `0116`. `p1->0116` is ON-screen (proj=5 clip=4) but side-CULLED (eye ~0.18–0.31
|
||||||
|
on `0116`'s side of the p1 plane — a back-portal). `p0->0117` is genuinely OFF-screen (`clip=0` with proj≥3
|
||||||
|
= off-screen per the builder's own probe doc, `PortalVisibilityBuilder.cs:818-821`). So no neighbour floods,
|
||||||
|
only the 5-poly connector draws, and its shell doesn't cover the forward view → the framebuffer clear color
|
||||||
|
(fog, `GameWindow.cs:8968`) shows through = GREY. Turning re-aims off the back-portal → a room floods → picture.
|
||||||
|
|
||||||
|
## 2. What is CONFIRMED / RULED OUT (workflow `wf_362bbeda-933`, 4 tracers + synthesis, + my own re-checks)
|
||||||
|
|
||||||
|
- ❌ **NOT null viewer-root / AD-21** — the render root is a valid indoor cell on 88,248/88,249 frames
|
||||||
|
(one lone `[outdoor-node] root=OUT`). (This refutes THE FIRST hypothesis in this investigation.)
|
||||||
|
- ❌ **NOT viewer-cell root-lag / AD-20** — `eyeInRoot=Y` at every grey frame: the eye is geometrically
|
||||||
|
INSIDE `0118`'s BSP. The root is the fresh eye-derived swept `curr_cell` (`PhysicsCameraCollisionProbe.cs:92`),
|
||||||
|
NOT a stale player-seeded fallback. (Refutes the workflow synthesis's own leading "31 cm gap" theory —
|
||||||
|
the eye is NOT across the boundary; I checked the `eyeInRoot` flag directly.)
|
||||||
|
- ❌ **NOT the flood-admission being unfaithful** — the empty-clip cull (`PortalVisibilityBuilder.cs:320-324`)
|
||||||
|
and the back-portal side-CULL (`:292-298`, `CameraOnInteriorSide :857-863`) are faithful ports:
|
||||||
|
retail `PView::ClipPortals` gates neighbour processing on `if (ecx_8 != 0)` (pc 005a5660) and
|
||||||
|
`AddViewToPortals` on `view_count != 0` (pc 005a5357). #177 already proved acdream's flood is retail-identical.
|
||||||
|
- ❌ **NOT the color clear (the tempting "3-line fix" — DEAD END, do not revisit).** Retail's DrawCells
|
||||||
|
(`0x005a4840`, decomp line 432709) has its only `Clear` INSIDE `if (outside_view.view_count > 0)`
|
||||||
|
(line 432715), right AFTER `LScape::draw` (the landscape draw, 432719). The Clear is
|
||||||
|
`Clear(4, &RGBAColor_Black, 1.0f)` (`0x820fc0 = RGBAColor_Black`) — but clearing COLOR *after* drawing the
|
||||||
|
landscape would erase it, so that `4` flag is **depth/stencil, not color**. So retail's color clear is NOT
|
||||||
|
gated here, and matching "don't color-clear a sealed interior" would be WRONG. (This is why I did the cdb
|
||||||
|
prep — it killed a wrong fix before shipping.)
|
||||||
|
- ✅ **It IS a real acdream bug** (retail seamless) in the **doorway-FLAP flood/pick family**: acdream drops
|
||||||
|
the room the camera looks back at (`0116`) from the connector root; **retail keeps it drawn.**
|
||||||
|
|
||||||
|
## 3. THE ONE OPEN FORK → the retail cdb trace (do this FIRST in the new session)
|
||||||
|
|
||||||
|
Retail is seamless, so retail either (A) **roots at the player's room `0116`, not the connector** (a
|
||||||
|
viewer-cell PICK divergence — acdream over-switches to `0118`), or (B) **roots at `0118` but its flood still
|
||||||
|
admits `0116`** (a FLOOD divergence — retail keeps the back-portal / uses an on-screen sliver acdream drops).
|
||||||
|
Static decomp cannot decide; observe retail.
|
||||||
|
|
||||||
|
**Toolchain (CLAUDE.md "Retail debugger toolchain"; binary CONFIRMED pairs with `refs/acclient.pdb` via
|
||||||
|
`py tools/pdb-extract/check_exe_pdb.py "C:/Turbine/Asheron's Call/acclient.exe"` → MATCH).**
|
||||||
|
- User: launch retail, connect to local ACE, walk to the SAME connecting room, and **HOLD the grey pose**
|
||||||
|
(so most captured frames are it).
|
||||||
|
- `bp acclient!SmartBox::update_viewer` (`0x00453ce0`) → dump `viewer_cell` (a `CObjCell*`, `acclient.h:35194`)
|
||||||
|
and its cell id. AND/OR `bp acclient!PView::DrawCells` (`0x005a4840`) → dump `cell_draw_num` +
|
||||||
|
`cell_draw_list.data[]` ids (`acclient.h:45939-45940`) — the drawn cell set.
|
||||||
|
- Decide: retail `viewer_cell == 0116` → **PICK fix** (acdream's viewer-cell pick/hysteresis over-retains
|
||||||
|
the connector — `CellTransit.cs:862-916 BuildCellSetAndPickContaining`, or the camera boom coasting the eye
|
||||||
|
into the connector at `pulledIn=-0.00` where retail's PathClipped `viewer_sphere` sweep hard-stops in
|
||||||
|
`0116`). retail `viewer_cell == 0118` but `cell_draw_list` ⊇ `0116` → **FLOOD fix** (retail admits the
|
||||||
|
back-portal from the connector; acdream's side-CULL / the deleted EyeInsidePortalOpening rescue is the site).
|
||||||
|
- cdb watchouts (CLAUDE.md): `qd` ONLY at a top-level threshold, never inside a conditional bp action (strands
|
||||||
|
cdb → kills retail); `dt acclient!Class @ecx field` for reads (`@@c++` is broken here); `.sympath <local>`
|
||||||
|
(no `srv*`, the `;` splits); spaced `.if ()` lets `qd` detach.
|
||||||
|
|
||||||
|
## 4. Apparatus (re-create in the new session — temp files won't persist)
|
||||||
|
|
||||||
|
- **Flap capture:** launch acdream with `ACDREAM_PROBE_FLAP=1` (canonical live cmd, CLAUDE.md). Emits per
|
||||||
|
frame: `[flap]` (root + per-portal proj/clip/side, `PortalVisibilityBuilder.cs:790`), `[flap-cam]`
|
||||||
|
(`root/viewerCell/playerCell/eyeInRoot/playerInRoot/terrain/outVisible`, `GameWindow.cs:11142-11152`),
|
||||||
|
`[flap-sweep]` (`ok/resolved/pulledIn/viewerCell`, `PhysicsCameraCollisionProbe.cs:74-87`), `[outdoor-node]`
|
||||||
|
(`root=IN/OUT`). ⚠️ heavy (per-frame); output goes to the raw background-task `.output`, not the Tee'd log.
|
||||||
|
Have the user HOLD the grey pose + turn to clear + turn back a couple times.
|
||||||
|
- **Offline cell geometry dump:** `tests/AcDream.Core.Tests/Physics/Issue186ConnectorCellGeometryInspectionTests.cs`
|
||||||
|
(`Dump_ConnectorCells_ShellAndCollision`) loads `0116/0117/0118` from the dat and prints render/physics poly
|
||||||
|
counts + normal-axis buckets. No live launch needed. (That's how the §1 table was produced.)
|
||||||
|
- **Decomp workflow:** `wf_362bbeda-933` (this session) — the 4-tracer + synthesis analysis; results in its
|
||||||
|
journal under the session's `subagents/workflows/` dir.
|
||||||
|
|
||||||
|
## 5. DO-NOT-RETRY (each already refuted or a known trap)
|
||||||
|
|
||||||
|
- Do NOT re-open the **null-root / AD-20 / AD-21** angle — root is valid, `eyeInRoot=Y`.
|
||||||
|
- Do NOT implement the **color-clear gating** — retail's gated `Clear` is depth/stencil (post-`LScape::draw`);
|
||||||
|
matching it would add non-retail stale-content.
|
||||||
|
- Do NOT re-add the deleted **`EyeInsidePortalOpening` rescue** (`PortalVisibilityBuilder.cs:314-319`) blindly
|
||||||
|
— it caused the `0171↔0173` flood-cycle churn (the ORIGINAL doorway flap).
|
||||||
|
- Do NOT widen **`PortalSideEpsilon`** (`:38-56`) — a documented root-lag band, NOT retail (F_EPSILON=0.0002);
|
||||||
|
BR-4 refuted tightening/widening without eye-exact tracking.
|
||||||
|
- Do NOT "fix" the flood by propagating an empty/edge-on portal — the empty-clip cull is retail-faithful.
|
||||||
|
- The sparse connector geometry (`0118` = 5 render / 3 physics polys) is FAITHFUL to the dat (retail draws the
|
||||||
|
same). It is context, not the bug — the bug is that `0116` isn't drawn, not that `0118` is thin.
|
||||||
|
|
||||||
|
## 6. Key code sites
|
||||||
|
|
||||||
|
- Frame-top clear (unconditional color+depth+stencil to fog): `GameWindow.cs:8968-8983`. **NOT the fix.**
|
||||||
|
- Viewer-cell resolution: `GameWindow.cs:9137-9148` (`viewerCellId = _retailChaseCamera.ViewerCellId`).
|
||||||
|
- Camera boom sweep: `PhysicsCameraCollisionProbe.cs` (`SweepEye`, PathClipped, `pulledIn`, `r.CellId=curr_cell`).
|
||||||
|
- Viewer-cell PICK: `CellTransit.cs:862-916` (`BuildCellSetAndPickContaining`, center-based `point_in_cell`,
|
||||||
|
current-cell-first hysteresis, port of `find_cell_list` pseudo_c:308788-308825).
|
||||||
|
- Portal FLOOD: `PortalVisibilityBuilder.cs` (`Build`; side-test `CameraOnInteriorSide :857-863`; empty-clip
|
||||||
|
cull `:320-324`; `PortalSideEpsilon :38-56`; the off-screen-vs-sliver note `:818-821`).
|
||||||
|
|
||||||
|
## 7. Retail decomp anchors
|
||||||
|
|
||||||
|
- `PView::DrawCells` `0x005a4840` (decomp line 432709): Clear gated on `outside_view.view_count>0` (432715),
|
||||||
|
Clear at 432732 (depth/stencil, post-`LScape::draw`); cells drawn unconditionally after (432815+).
|
||||||
|
- `SmartBox::update_viewer` `0x00453ce0`: `viewer_cell = sphere_path.curr_cell` (the render root = swept cell).
|
||||||
|
- `PView::ClipPortals` empty-clip gate `if(ecx_8!=0)` pc 005a5660; `AddViewToPortals` `view_count!=0` pc 005a5357.
|
||||||
|
- `PView::InitCell` side test decomp:432962. `RGBAColor_Black` @ `0x820fc0`.
|
||||||
|
|
||||||
|
## 8. Acceptance
|
||||||
|
- No grey flap passing through OR stopping in the connecting room at any camera angle; matches retail's seamless.
|
||||||
|
- Regression: the outside→inside doorway + other multi-room houses + the #177/#181 render-flap suites unchanged.
|
||||||
|
|
||||||
|
## 9. Context: this session also FIXED #185 (unrelated, DONE + gated)
|
||||||
|
The outdoor-stairs "invisible wall" (`07c5b832`) — a shadow part-id uint32 overflow dropping stair collision.
|
||||||
|
Unrelated to #186 (that was collision/registration; #186 is render/visibility). See
|
||||||
|
`docs/superpowers/specs/2026-07-08-185-outdoor-stairs-fix-design.md`.
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using DatReaderWriter;
|
||||||
|
using DatReaderWriter.DBObjs;
|
||||||
|
using DatReaderWriter.Options;
|
||||||
|
using Xunit;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
using Env = System.Environment;
|
||||||
|
|
||||||
|
namespace AcDream.Core.Tests.Physics;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// #186 diagnostic (report-only): dump the render-shell + collision geometry of the
|
||||||
|
/// connecting-room grey-flap cells (0xF6820116 player room, 0xF6820117 next room,
|
||||||
|
/// 0xF6820118 the connector root where the frame greys). Answers: is 0118 a CLOSED
|
||||||
|
/// shell (walls/floor/ceiling covering every direction bar the portal apertures) or a
|
||||||
|
/// bare connector? And does it carry collision geometry (so the camera sweep would
|
||||||
|
/// hard-stop) or none (so the boom coasts to a degenerate spot)? Skips without the dat.
|
||||||
|
/// </summary>
|
||||||
|
public class Issue186ConnectorCellGeometryInspectionTests
|
||||||
|
{
|
||||||
|
private readonly ITestOutputHelper _out;
|
||||||
|
public Issue186ConnectorCellGeometryInspectionTests(ITestOutputHelper output) => _out = output;
|
||||||
|
|
||||||
|
private static string? DatDir()
|
||||||
|
{
|
||||||
|
var d = Env.GetEnvironmentVariable("ACDREAM_DAT_DIR")
|
||||||
|
?? Path.Combine(Env.GetFolderPath(Env.SpecialFolder.UserProfile), "Documents", "Asheron's Call");
|
||||||
|
return Directory.Exists(d) ? d : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Dump_ConnectorCells_ShellAndCollision()
|
||||||
|
{
|
||||||
|
var datDir = DatDir();
|
||||||
|
if (datDir is null) { _out.WriteLine("SKIP: no dat dir"); return; }
|
||||||
|
using var dats = new DatCollection(datDir, DatAccessType.Read);
|
||||||
|
|
||||||
|
foreach (uint cellId in new[] { 0xF6820116u, 0xF6820117u, 0xF6820118u })
|
||||||
|
{
|
||||||
|
var env = dats.Get<EnvCell>(cellId);
|
||||||
|
if (env is null) { _out.WriteLine($"cell 0x{cellId:X8}: EnvCell NOT FOUND"); continue; }
|
||||||
|
|
||||||
|
var environment = dats.Get<DatReaderWriter.DBObjs.Environment>(0x0D000000u | env.EnvironmentId);
|
||||||
|
environment!.Cells.TryGetValue(env.CellStructure, out var cs);
|
||||||
|
|
||||||
|
var portals = env.CellPortals?.Select(p => $"0x{(0xF6820000u | p.OtherCellId):X8}") ?? Enumerable.Empty<string>();
|
||||||
|
_out.WriteLine($"=== cell 0x{cellId:X8} envId=0x{env.EnvironmentId:X4} struct={env.CellStructure} " +
|
||||||
|
$"pos=({env.Position.Origin.X:F2},{env.Position.Origin.Y:F2},{env.Position.Origin.Z:F2}) ===");
|
||||||
|
_out.WriteLine($" CellPortals -> [{string.Join(",", portals)}] VisibleCells={env.VisibleCells?.Count ?? 0}");
|
||||||
|
|
||||||
|
if (cs is null) { _out.WriteLine(" CellStruct: NULL"); continue; }
|
||||||
|
|
||||||
|
int renderPolys = cs.Polygons?.Count ?? 0;
|
||||||
|
int physPolys = cs.PhysicsPolygons?.Count ?? 0;
|
||||||
|
bool physBsp = cs.PhysicsBSP?.Root is not null;
|
||||||
|
bool cellBsp = cs.CellBSP?.Root is not null;
|
||||||
|
_out.WriteLine($" renderPolys={renderPolys} physicsPolys={physPolys} physicsBSP={(physBsp ? "YES" : "NO")} cellBSP={(cellBsp ? "YES" : "NO")}");
|
||||||
|
|
||||||
|
// Render-shell normal coverage: bucket each render poly's local normal by
|
||||||
|
// dominant axis to see whether the shell encloses (has +/-X,+/-Y,+/-Z faces).
|
||||||
|
if (cs.Polygons is not null && cs.VertexArray?.Vertices is not null)
|
||||||
|
{
|
||||||
|
var buckets = new Dictionary<string, int>();
|
||||||
|
foreach (var poly in cs.Polygons.Values)
|
||||||
|
{
|
||||||
|
var n = PolyNormal(poly, cs.VertexArray);
|
||||||
|
if (n is null) continue;
|
||||||
|
buckets.TryGetValue(DomAxis(n.Value), out int c);
|
||||||
|
buckets[DomAxis(n.Value)] = c + 1;
|
||||||
|
}
|
||||||
|
_out.WriteLine(" render-shell normal buckets: " +
|
||||||
|
string.Join(" ", buckets.OrderBy(k => k.Key).Select(k => $"{k.Key}={k.Value}")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector3? PolyNormal(DatReaderWriter.Types.Polygon poly, DatReaderWriter.Types.VertexArray va)
|
||||||
|
{
|
||||||
|
if (poly.VertexIds is null || poly.VertexIds.Count < 3) return null;
|
||||||
|
if (!va.Vertices.TryGetValue((ushort)poly.VertexIds[0], out var a)) return null;
|
||||||
|
if (!va.Vertices.TryGetValue((ushort)poly.VertexIds[1], out var b)) return null;
|
||||||
|
if (!va.Vertices.TryGetValue((ushort)poly.VertexIds[2], out var c)) return null;
|
||||||
|
var n = Vector3.Cross(
|
||||||
|
new Vector3(b.Origin.X, b.Origin.Y, b.Origin.Z) - new Vector3(a.Origin.X, a.Origin.Y, a.Origin.Z),
|
||||||
|
new Vector3(c.Origin.X, c.Origin.Y, c.Origin.Z) - new Vector3(a.Origin.X, a.Origin.Y, a.Origin.Z));
|
||||||
|
return n.LengthSquared() < 1e-9f ? (Vector3?)null : Vector3.Normalize(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string DomAxis(Vector3 n)
|
||||||
|
{
|
||||||
|
float ax = MathF.Abs(n.X), ay = MathF.Abs(n.Y), az = MathF.Abs(n.Z);
|
||||||
|
if (ax >= ay && ax >= az) return n.X >= 0 ? "+X" : "-X";
|
||||||
|
if (ay >= ax && ay >= az) return n.Y >= 0 ? "+Y" : "-Y";
|
||||||
|
return n.Z >= 0 ? "+Z" : "-Z";
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue