diff --git a/docs/ISSUES.md b/docs/ISSUES.md index 835c623f4..29bb9f5ad 100644 --- a/docs/ISSUES.md +++ b/docs/ISSUES.md @@ -39,6 +39,27 @@ confirmed closed by the owner, 11 need a focused live gate, and 43 are safe to remain closed. See [`docs/research/2026-08-28-owner-closed-issue-validity-audit.md`](research/2026-08-28-owner-closed-issue-validity-audit.md). +## #482 — Some exterior cathedral walls do not stop the player + +**Status:** IN-PROGRESS — scoped repair, automated gates and lead graphical +wall gate PASS/provisional; owner acceptance pending. At F4180012 [48.002960,39.257545,160.004990], +AcDream can remain halfway through the wall. Retail teleported to that position +pushes out to x48.480000. Read-only retail capture identifies the neighboring +F418000A building Gfx01001FB3;0012 has no building pointer. Paired native code +confirms that a missing terrain polygon does not suppress the building/object +channels. The repaired CheckOtherCells no longer skips both on a terrain-cell +mismatch. Full installed-scene replay proves the failure, fixed25focused tests +pass and restoring the old skip fails5regressions. Release0W0E/full17036/0/0, +lead installed4/4. Owner accepts working collision without exact final-position +equality; original unmatched placement-vs-retail-settle comparison is preserved. +Owner authorized closing both clients; both closed gracefully before testing. +Lead graphical30724 proves outside placement, wall stopping and movement away; +unchanged33696 checks settled cathedral/Holtburg geometry. Both exited0 with +graceful logout, nine PNGs inspected with narrow verdicts/limitations in report. +Scope/evidence: `research/2026-09-01-overhaul/482-cathedral-exterior-collision-contract.md` +and `482-cathedral-exterior-collision-verification.md` alongside it. +Separate from AP159 ramp membership and #477 AMD device loss; FPS remains deferred. + ## #481 — Low initial FPS after entering the world **Status:** OPEN, owner-reported during the `de427d2c0` gate on2026-09-05. diff --git a/docs/architecture/retail-divergence-register.md b/docs/architecture/retail-divergence-register.md index be2530e0b..758be2cbd 100644 --- a/docs/architecture/retail-divergence-register.md +++ b/docs/architecture/retail-divergence-register.md @@ -560,6 +560,7 @@ equivalence argument (promote to AD/AP) or a fix. | # | Divergence | Where (file:line) | Recorded justification (deficient) | Risk if assumption breaks | Retail oracle | |---|---|---|---|---|---| +| ~~UN-10~~ | **Filed and RETIRED2026-09-05 (#482).** `CheckOtherCells` skipped an entire outdoor candidate when its terrain sample was absent/belonged to the center's other cell, suppressing that available cell's building and object collision. Availability now comes from `CellGraph.GetVisible`; terrain is optional and building→objects still execute on OK. | `src/AcDream.Core/Physics/TransitionTypes.cs` (`CheckOtherCells`) | No retail basis for the old whole-cell continue. Paired native return/dispatch proof and full prepared cathedral scene reproduce the omission; restoring it fails the building/object and approach/placement regressions. | Some exterior walls can be entered while the sphere center is in an adjacent landcell; repaired by restoring collision dispatch, not moving geometry or clamping coordinates. Owner accepts functional collision, not exact post-teleport settle coordinates; graphical acceptance remains separately recorded. | `CTransition::check_other_cells`0050AE50; `CLandCell::find_env_collisions`00532F20/0053305E; `CLandCell::find_collisions`00532D60; `CSortCell::find_collisions`005340A0; `docs/research/2026-09-01-overhaul/482-cathedral-exterior-collision-verification.md` | | UN-1 | `CheckOtherCells` iterates the overlap set SORTED by cell id; retail walks the CELLARRAY in build order — and the loop halts on the first non-OK result, so order is behavior-bearing | `src/AcDream.Core/Physics/CellTransit.cs:1718` | Justified only as "deterministic order for greppable probe logs" — no equivalence argument vs retail's array order recorded | A sphere straddling two cells that would each return a different non-OK result halts on a different cell than retail — different collision normal / slide direction at multi-cell straddles | `CTransition::check_other_cells` pc:272717-272798 | | UN-4 | GfxObj double-sided/negative-surface handling keeps WB's legacy logic (cull-mode double-siding, no reversed-winding duplicate, different neg-surface predicate) while the CellStruct path follows the retail-cited `ConstructMesh` reading | `src/AcDream.App/Rendering/Wb/ObjectMeshManager.cs:1059` (CellStruct contrast :1396-1410) | No recorded justification on the GfxObj side — it is the unmodified WB extraction; the retail citation was added only to the CellStruct path | GfxObj models retail draws via duplicated-reversed-winding get wrong back-face lighting (normals not inverted) or missing/extra negative faces — dark or absent faces from behind | `D3DPolyRender::ConstructMesh` 0x0059dfa0 | | UN-6 | Fixed 200 ms sleep between ConnectRequest and ConnectResponse; retail inserts no delay. Annotated only as "with 200ms race delay"; the 2026-06-04 audit flagged it, the follow-up refuted "forbidden workaround" but wrote no fuller rationale back | `src/AcDream.Core.Net/WorldSession.cs:484` | Presumed ACE port+1 listener race guard — four words, no citation | Every login eats a flat 200 ms; if the race needs longer on a loaded server, the handshake fails intermittently (ConnectResponse ignored → CharacterList never arrives, exit-29 shape) with no retry — a timing constant masking an unconfirmed root cause | (none recorded) | diff --git a/docs/plans/2026-09-01-campaign-overhaul-world-solidity.md b/docs/plans/2026-09-01-campaign-overhaul-world-solidity.md index 778df3e31..a80fca40e 100644 --- a/docs/plans/2026-09-01-campaign-overhaul-world-solidity.md +++ b/docs/plans/2026-09-01-campaign-overhaul-world-solidity.md @@ -872,6 +872,25 @@ its bounded scope/input/containment proof; no production change or new intentional deviation, no AMD-cause inference. Debugger detached, both owner-held clients remain open. FPS deferred; C1a/#477/G4 remain unclosed. +**Exterior wall #482,2026-09-05:** owner reports missing collision at +F4180012 [48.002960,39.257545,160.004990], with retail correcting to x48.48. +Paired capture identifies neighbor000A's direct Gfx01001FB3 shell; native +dispatch preserves building/object checks when terrain has no contribution. +Full production prepared/flat scene replay reproduces both approach and +initial-overlap failures, clear-space control passes. Bounded scoped repair +underway, not yet accepted; contract/verification482-cathedral-exterior- +collision-{contract,verification}.md, packet§62. Owner authorized closure; +both exact clients gracefully closed, no debugger remains, ACE stays up. +Owner clarified functional collision rather than exact final coordinate; +prepared human/direct flat-shell oracle replaces the unmatched settle +comparison, which is preserved. Frozen review PASS,25focused tests PASS, +5discriminating sabotage failures. Lead Release0W0E/full17036/0/0 and +installed4/4 PASS; UN-10 retired with repair. Lead graphical wall30724 and +settled-controls33696 both exit0/gracefully: wall blocking/correction/escape +PASS provisional, nine PNGs inspected with control limits in verification +report. No running clients/debugger; ACE up. Owner wall acceptance pending. +No FPS work; AP159, AMD#477 and final G4 remain separate open tails. + ## 14. Risk register (condensed) - **R1 second scene graph.** Graph records own only topology/membership facts; diff --git a/docs/research/2026-09-01-overhaul/482-cathedral-exterior-collision-contract.md b/docs/research/2026-09-01-overhaul/482-cathedral-exterior-collision-contract.md new file mode 100644 index 000000000..219a9f91f --- /dev/null +++ b/docs/research/2026-09-01-overhaul/482-cathedral-exterior-collision-contract.md @@ -0,0 +1,114 @@ +# #482 — cathedral exterior neighboring-cell collision + +Bounded chunk, 2026-09-05; base508c6014e. Owner's continued goal explicitly +requires resolving cathedral issues. Owner authorized graceful closure of +both clients before implementation/testing; PID25724 logout confirmed and +PID35048 absent. ACE UDP9000/PID13340 remains running. FPS is excluded. + +**Owner acceptance clarification during implementation:** "might not be exact +as retail does it. As long as it is fixed." Functional acceptance is preventing +entry through the wall, moving an initially overlapping character outside, +and allowing movement away. Exact equality with retail's final x48.48 is NOT +required. This changes the acceptance oracle, not the authorized production +algorithm: retain the named/paired retail dispatch fix, no coordinate clamp, +scene special case or placement-algorithm rewrite. A bare SetPosition request +is not established as the same phase/state as the owner's later-settled retail +observation; preserve that failed comparison as an unmatched diagnostic, +never relabel it retail-exact. Actual graphical confirmation remains owed. + +## Reproduction and independently checked retail facts + +Owner walks into some exterior wall sections at F4180012, +[48.002960,39.257545,160.004990], orientation[1,0,0,0]. Retail teleported to +the same spot initially overlaps, then moves to[48.480000,39.257545,160.004990] +with orientation[-0.992006,0,0,-0.126192]. Screenshots are the supplied +codex-clipboard-f29dec37-5578-4de6-a046-a6db75e11b97.png and +codex-clipboard-57dcb1d8-6638-4fc7-856c-7fc0b7681b06.png in Local/Temp. +This is collision failure, not just different projected character positions. + +Root's paired2013 retail snapshot identifies F418000A's sole building: +owner1A4A0F20, part0 at113E6EC8, GfxObj01001FB3 (449 physics polygons and a +non-null physics BSP), root origin[36,36,160],180-degree Z rotation. Root and +part0 Position bytes are identical. F4180012 has no building pointer. The +reported center is east of x48; a radius0.48 human sphere overlaps000A. +No Setup-part placement repair is indicated by this direct-Gfx shell. + +Named+paired facts: CTransition::check_other_cells0050AE50 dispatches every +non-null, non-primary candidate via vtable+88 (0050AE98–0050AEAF). +CLandCell::find_env_collisions00532F20 retains entry-restriction OK in EBX; +no terrain polygon takes00532F96→0053305E and returns OK via00533061. +CLandCell::find_collisions00532D60 then calls CSortCell005340A0 at00532D79, +and objects0052B750 at00532D86 if still OK. CSortCell invokes the building's +006B5300 channel. Thus no terrain contribution must NOT suppress that cell's +building/object checks. Root verified named bodies; independent OpenAI audit +checked paired native spans. Root independently verified the raw spans; +`482-cathedral-exterior-collision-verification.md` records their hashes. + +Production Transition.CheckOtherCells currently continues the entire outdoor +cell at the SampleTerrainWalkable cell-id mismatch, before building/object +dispatch. This is a confirmed dispatch divergence; pre-fix installed repro +must establish that it actually explains this reported wall failure. + +## Allowed implementation + +One OpenAI implementer, shared campaign worktree; lead owns documentation, +register, integration and graphical runs. Implementer may edit only: + +- src/AcDream.Core/Physics/TransitionTypes.cs, CheckOtherCells outdoor branch. +- tests/AcDream.Core.Tests/Physics/TransitionCheckOtherCellsTests.cs (or one + focused sibling test file if cleaner). +- one new tests/AcDream.App.Tests/Physics/CathedralExteriorWallCollisionInstalledDatTests.cs. + +First write/run a discriminating pre-fix regression and exact installed-scene +replay. Use current full F418 building/terrain/EnvCell publication or an +existing production factory/publisher fixture; do not omit CacheBuildings. +Inspect the preserved s5-c1a-geometry fixture as a recipe, not a golden source; +do not modify that scratch or import its unpinned membership hash assertion. +Use prepared/flat collision in the installed witness. Assert actual shell +identity/anchor and absence of an0012 building, overlapping000A candidate, +then actual transition response. No silent DAT skips or new production probe. + +If the reproduced failure is this dispatch gate, fix only its scope: retain +the existing loaded-cell/null-owner semantics (CellGraph.GetVisible is the +explicit availability source), evaluate terrain only where applicable, and +always continue through building then objects for an available other cell +after terrain OK. Reuse SampleTerrainWalkableInCell if appropriate. A missing +terrain polygon is not an unavailable cell. Do not permit stale cached +buildings/objects under an unavailable candidate. Preserve non-OK stopping, +live sphere refresh, water/contact semantics, primary exclusion and existing +iteration order. UN-1 sorting, AP159 ramp bridge, transforms, package/shaders, +placement shortcuts and FPS remain out of scope for this chunk. + +Tests must demonstrate adjacent building dispatch, neighboring object dispatch +after terrain miss, nonintersecting neighbor control, unavailable-cell inertness, +and existing terrain-only behavior. Exercise prepared/flat production traversal, +not only a mocked return. Exact installed wall witness must assert actual +blocked/corrected position and preserve a clear-position control. Use actual +prepared standard-human Setup collision properties, explicitly a scale1 +control unless the current avatar's inputs were captured. Run both +approach from outside and owner-reported initial overlap if the production +placement/resolve seam supports it; report differences, never weaken an +expectation merely because approach passes. Per the owner's clarification, +the initial-overlap assertion is functional nonpenetration plus a real +movement-away control, not the unmatched exact post-settle coordinate oracle. +Mutation/sabotage must make the +new regression fail when the old whole-cell skip is reinstated. + +## Gate / stop / integration + +Implementer alone owns build/test execution until handback (both client names +must remain absent). Release build and focused whole test classes; keep +pre-fix failed TRX, fixed TRX, sabotage failed TRX, restore the fixed source +using apply_patch, rerun green and report exact diff/hash freeze. No commits +or graphical launches by implementer. Root independently verifies claims +against named retail/paired binary and actual artifacts, then runs retail and +production review lenses. Maximum two correction rounds; third required fix +round stops with write-up. If installed replay points to another mechanism, +STOP and return evidence before changing additional production scope. + +Root files #482 and register UN-10 (never reused), records retirement only on +verified repair, and commits code/evidence/ledger together. No new intentional +deviation is authorized. Root rebuilds green, runs exact exterior/doorway +graphical checks with PNG PASS/FAIL (provisional until owner), closes own +client gracefully. Overall G4 remains FAIL until cathedral tails and final +lifecycle/stability/owner sign-off are genuinely complete. No main merge. diff --git a/docs/research/2026-09-01-overhaul/482-cathedral-exterior-collision-verification.md b/docs/research/2026-09-01-overhaul/482-cathedral-exterior-collision-verification.md new file mode 100644 index 000000000..48c83fe7f --- /dev/null +++ b/docs/research/2026-09-01-overhaul/482-cathedral-exterior-collision-verification.md @@ -0,0 +1,223 @@ +# #482 — exterior wall evidence and verification + +2026-09-05, base508c6014e. Implementation and gates are not yet accepted. +Bounded scope: `482-cathedral-exterior-collision-contract.md`. + +## Owner failure and read-only retail capture + +Owner FAIL: AcDream remains embedded at F4180012 +[48.002960,39.257545,160.004990]. Retail initially overlaps when teleported +there, then moves outward to x48.480000 with unchanged y/z. Different heading +does not explain missing physical collision. PNGs below are owner evidence, +not a lead self-gate. + +Non-invasive cdb snapshots of paired2013 retail PID35048 identify the loaded +F418 landblock and all nine building anchors. F418000A's building is +owner1A4A0F20, part0 at113E6EC8, direct Gfx01001FB3,449 physics polygons and +non-null physics BSP. Origin[36,36,160], rotation approximately180 degrees +around Z; all18 Position dwords of root/part0 agree. F4180012 has no building. +The radius0.48 human sphere at the owner center overlaps000A. This rules out +a Setup-part transform explanation for this particular direct-Gfx shell, +but actual AcDream candidate inclusion and collision response need testing. + +Captures used read-only inspection, no breakpoints/calls/memory writes, and +ended in qd. After owner authorization, both exact clients were closed via +CloseMainWindow; AcDream log confirms graceful logout requested/confirmed. +No forced kill, no remaining client/debugger; ACE UDP9000/PID13340 remained up. + +Archive: `artifacts/482-cathedral-wall-retail-20260905/`. Copies hash-checked +against their original Temp files; names below share that directory. + +| File | SHA-256 | +|---|---| +| cathedral-wall-retail-roots-20260905.log | 8319545BE95A7C5A9E3287265482A03AA157CECCEB4BD5F797BFE8F8D04A8F17 | +| cathedral-wall-retail-roster-20260905.cdb | 399D2EB9E78E4621A68C9F53F61270E993471B93718DE546AA1FA9B4452041FC | +| cathedral-wall-retail-roster-20260905.log | 16815962339A8B31E5236617C0D8E48E09BBE453767F5A933357BE013B924767 | +| cathedral-wall-retail-geometry-20260905.log | DCD2D6E9990B6EE495D538D96C00AED0D7561A9EE49648F95E490247318FAE32 | +| codex-clipboard-f29dec37-5578-4de6-a046-a6db75e11b97.png | 255FE90F98C8C6517969DC58FB38859BF34DB4861E77A84F04A7AD167FCA3270 | +| codex-clipboard-57dcb1d8-6638-4fc7-856c-7fc0b7681b06.png | CAB385A1EF032CC108FA1413DFD5AF22F8F67D31D5BF69515516EDDC119BAB00 | + +## Named and paired native dispatch + +Oracle: named-retail/acclient_2013_pseudo_c.txt, paired executable +C:/Turbine/Asheron's Call/acclient.exe, SHA-256 +006FFEADC5D679C871497112A5BD1F87714D0E273E2166BAE5052DDE369297B1, +PDB GUID9e847e2f-777c-4bd9-886c-22256bb87f32 age1. Root and independent retail +audit checked the dispatch/return branches; no new decompilation required. +For these .text addresses, file offset = VA minus00400000. These are span +hashes, not whole-function hashes. + +| VA, byte count | Meaning | SHA-256 | +|---|---|---| +| 0050AE98,28 | Non-null/non-primary candidate virtual find_collisions | FE5504232907D37E9FC16252C9E80250C16A5880FCE115D9ED21F963A0601A75 | +| 00532D60,47 | LandCell env, then sort/building, then object on OK | 91989F38F67E681C677420E46992F0AE89F3DBA3CB12D8D158B33AD345C477F4 | +| 00532F20,36 | Entry-restriction result retained in EBX | AF70C2C2267E51532F3606C53A21782AD70CE86282CA012F6639F64820732E5F | +| 00532F8C,18 | No terrain polygon branches to return | 73D9BFCBBE689BEB742237FA31929263BE0D9B4A7F7DB1738D4CB039EEF6408C | +| 0053305E,12 | Return EAX=EBX, including OK=1 | DA523DD257160ECF834F1006428D192E671AF37ADE7559B8B49AC3F60DD2759C | +| 005340A0,23 | Non-null building dispatch to006B5300 | 4F4A92F32C3C71BD771E21B0A2411FDB9CFD571CD8DA3C4DA28F4D11E1AE87A7 | +| 006B5300,72 | Building part0 collision with BldgCheck bracket | D3C1035DB94955DBDFD50AB6119C3A5DED0187D39511C66896B5AAAC4A29438D | + +Faithful dispatch pseudocode (existing candidate-order deviation UN-1 remains +outside this repair): for each available non-primary candidate, check its +environment contribution; no terrain polygon means OK, not skip the cell. +On OK check its building; on OK check its objects; propagate a non-OK result +without evaluating later channels. Refresh the live sphere for each candidate. +Secondary ACE LandCell and ACViewer SortCell ports agree; paired retail wins. + +Current AcDream whole-cell continue after terrain sample mismatch suppresses +both building and object channels. This dispatch divergence is confirmed; +the complete owner repro cause still requires the full installed-scene test. + +## Initial replay and owner acceptance clarification + +Pre-fix full near-build/publication + v10 prepared/flat replay confirms real +candidates[F4180012,F418000A], shell01001FB3/449 polygons and no0012 building. +Both outside approach and generic initial-overlap placement wrongly commit +x48.002960; clear-space control passes. Exact artifact: +`artifacts/482-wall/482-prefixed-installed-replay.trx` (2failed/1passed/0skipped). + +With the scoped dispatch repair, approach stops at x48.6 and the clear control +passes. Generic nonethereal Placement|Slide placement finds +[48.772762,39.70199,160.00499], failing the initial strict x48.48 assertion. +`482-fixed-installed.trx` preserves this. Production review correctly rejects +that exact oracle: the request had hand-authored human geometry/default state, +different flags and no later settle simulation; it was not matched to the +owner's post-teleport observation. Compass-search placement can produce a +diagonal correction. No production placement defect or retail equality follows. + +Owner then explicitly clarified: "might not be exact as retail does it. As +long as it is fixed." The acceptance criterion is functional wall blocking, +nonpenetrating overlap correction and free movement away, not bit/exact final +position equality. The corrected fixture must use prepared standard-human +geometry as an explicitly labeled control, verify actual collision clearance, +and preserve the original unmatched failure evidence. No extra production +algorithm change is authorized or made by this criterion amendment. + +## Frozen implementation and discriminating tests + +Production change is only the available outdoor candidate dispatch in +TransitionTypes.CheckOtherCells. It introduces no new owner, scene ID, clamp, +physics model, placement shortcut, shader/package or FPS change. UN-10 is +filed/retired with the repair; existing UN-1 and AP159 are unchanged. + +Final source SHA-256 (root independently read/hash-checked): + +- TransitionTypes.cs: C83818C07137EE32214FB32996BA67C42D2ECAB319C8CB7D7ADFC8CF0A4156BE. +- TransitionCheckOtherCellsTests.cs: 636A67E192960B7C4EA852B0E8D781BBA6AF7E30E45B7D35E290C58A4A8E35BC. +- CathedralExteriorWallCollisionInstalledDatTests.cs: E3A420FCE7D86B3C0E5942CE95BBD1301015FD135409B26F505526AB45737A77. + +Prepared Setup02000001 confirms spheres z0.475/1.35,r0.48,height1.835, +step-up0.6/step-down1.5. Scale1 is an explicit standard-human control, not a +captured current-avatar state. Full production publication includes terrain, +buildings, prepared objects and EnvCells. Direct flat-shell queries prove +the reported point collides and the corrected placement does not. Approach +blocks, clear-position motion and movement away remain unobstructed. + +Root read the actual TRXs, results and outputs, not just reported counts. +Artifacts below are under `artifacts/482-wall/`: + +| Artifact | Outcome | SHA-256 | +|---|---|---| +| 482-restored-core.trx | 21PASS/0FAIL/0skip | F28DFFA37997BCF09A63908A759650A3F09C5CC84C4AD6702D7EBFE220341C3F | +| 482-restored-installed.trx | 4PASS/0FAIL/0skip | 31EDDFCF3CCEA007BD6230E4C7108C0F8C80B5E3271EA835DBF08202FF58233F | +| 482-sabotage-core.trx | 18PASS/3expectedFAIL/0skip | 88A9D96C1E8BA1C418D15EC13045EDCE43F5596758D64254C00DD3071DFAB0C6 | +| 482-sabotage-installed.trx | 2PASS/2expectedFAIL/0skip | F3E5D4AC89ED62053B0050559A11EFC4424CCAACB78C4A2147C06DEA4D1D74E0 | +| unmatched-strict-placement-fixture.cs.txt | Preserved unmatched oracle, not accepted parity test | 042426F8EB81DEEAD0B91CE030169C0AE172CD20CF92798A8359C88C2DC3AE90 | +| 482-prefixed-installed-replay.trx | 1PASS/2expectedFAIL/0skip | 0872F992C4A4E0C1C17DF785A2FBB37D088111CF4BBA6CA741B7A2914A7E880C | +| 482-fixed-installed.trx | Initial unmatched comparison,1FAIL | C7BD387185B1DD09E38E3C8A2520475F0606E039F116F5931BB6905EE908299B | +| 482-final-implementation.diff | Archived implementation snapshot | 3707EDC9B64079AE28482970D53987EDDB69B6E849F08730E4F148F6B814AA04 | + +Restoring the old whole-cell skip fails the real building/object/stop-order +regressions and both installed approach/placement cases. Clear/escape controls +stay green. Restored final code passes all25 focused tests. Implementer's +Release solution build succeeds0warnings/0errors; lead integrated gate follows. + +## Review closure + +One fixture-proof correction round: independent review found the unmatched +exact-coordinate oracle, not a production dispatch defect. Owner explicitly +changed acceptance to working collision. Final combined retail/production +review PASS on the three frozen hashes above, root independently checked +named bodies/seven native byte spans and actual TRXs. No further production +correction, no third fix round. Review does not claim exact teleport parity +or movement away specifically from the returned placement pose; the graphical +route checks that actual post-placement escape as an additional observation. + +## Lead integration gate + +Fresh locked restore and full Release build0W0E pass, followed by the literal +hermetic gate17036PASS/0FAIL/0skip across14assemblies. Filter unchanged from +the prior full gate: Lane!=InstalledDat&Lane!=Linux&Lane!=Manual&Lane!=Timing& +Lane!=Live&Lane!=PreparedPackage. Evidence: +`artifacts/482-integrated-hermetic/release-gate-summary.json`; root independently +verified all32 SHA256SUMS entries. This ran on base508c6014e plus the frozen +delta, honestly recorded dirty, before the integration commit. The lead's +fresh no-build InstalledDat rerun passes4/4/0skip: +`artifacts/482-wall/482-lead-installed.trx`. + +Exact graphical candidate DLL SHA-256: + +- App: FEF0E3717F5B0A526A1F40A2645F61BCC31AF316A2E4F456D4CD3BD04FC131D8. +- Core: F20220D51B6BFFBEDED128061D9D1662EC492D5D3C55D197648CD7BA3D9BB04F. + +Graphical route `tools/overhaul-selfgate/route-482-cathedral-wall.txt` preflight +checks all78verbs/actions, balanced held input and terminal close-client. +SHA-256734B3F9E659B159A65E75DBD12FF33699B6AB51D630F5A89790BF02422999FAA. +Both client names/debugger absent and ACE UDP9000/PID13340 verified before +launch. One controlled Release process, no concurrent builds/tests. +High/MSAA4,1280x720 and the existing v10 session/package; automation adds +checkpoint/referee and borderless exact-framebuffer instrumentation, so this +is not an ordinary-profile stability or FPS measurement. + +## Graphical check — PASS for wall functionality, provisional until owner + +Run `logs/selfgate-20260905-183651-482-cathedral-wall-functional/`,PID30724, +started18:36:51+02, unchanged DLL hashes above. All78commands completed; +exit0 and graceful logout requested/confirmed. All seven checkpoints report +completed/visible, waitCueShown=false, invariant failures0 and idle transit. +No logged ErrorDeviceLost/unhandled exception; this short, unvalidated run +does not clear AMD#477. Existing missing PhysicsScriptTable notices remain. + +All PNGs below were opened. Paths share that run's `artifacts/screenshots/`. +Positions are read directly from the visible /loc text, not inferred from +camera perspective. Results are narrow/provisional, not final G4. + +| PNG | Observation / verdict | +|---|---| +| 482-01-overlap.png | PASS: requested embedded point places visibly outside, x48.891850,y39.257545,z160.004990. | +| 482-01b-overlap-escape.png | PASS: movement away from that actual returned placement reaches x51.650181, same y/z. | +| 482-02-approach.png | PASS: westward input from x49 stops outside at x48.497448, same y/z. | +| 482-03-escape.png | PASS: backing away reaches x53.011257, same y/z. | +| 482-04-adjacent-wall.png | PASS: second wall point at y41 stops outside at x48.829643,z160.004990. | +| 482-05-cathedral-interior.png | Geometry visible; arrival caption still present. Settled control below supersedes presentation acceptance. | +| 482-06-holtburg-doorway.png | Ground/building visible, close-camera avatar translucent. Not proof of avatar-opacity parity; settled control below. | + +Log SHA-256A860FC230DF4311E6764A73DF00D0C395C51A7B3A9FBF370A9C6D5304BA2C37B; +checkpoint streamBF2CDA69B818A26FB174B38B94F3DB8F849D4B2AC611A4A45D00E8C682D7E32F. + +Separate unchanged-binary controls: +`logs/selfgate-20260905-183912-482-settled-controls/`,PID33696, +started18:39:12+02,20seconds after each world-visible edge. Script +`tools/overhaul-selfgate/route-482-controls.txt`; exit0/graceful logout, both +checkpoints completed/visible, cuefalse/invariants0/transit idle. PNGs opened: + +- `artifacts/screenshots/482-control-cathedral-settled.png`: narrow PASS for + stable cathedral floor/geometry; caption cleared, /loc F4180104 + [36.900002,48.700001,169.804993]. SHA-256 + A0EC44D1D42303B09F1AB5E79CDF89DA2A30A0E70695F8CAB40563E8F1E0F648. +- `artifacts/screenshots/482-control-holtburg-settled.png`: narrow PASS for + stable outdoor ground/doorway geometry, /loc A9B40029 + [133.988495,17.614059,94.005005], not an ongoing fall. Camera remains close + with translucent avatar; no avatar-opacity/retail-camera parity claim. + SHA-256567C92060A6255550ED9EDA68F050EFBC5239BFEFEA755D413CB0AE0FD4BCDD0. + +Second log SHA-2561F9076DEF0F5DAAD3DDB5DF6674261F8633E2097DC5BC7773BD8BBCE0571AA72; +checkpoint streamB0D004349487174BC008073249215D262D2EDF8C2A6262D78EDBAD1F2F7A58EA. +Both own clients closed; no AcDream.App/acclient/cdb remains. ACE stays up. + +## Acceptance remaining + +Owner visual acceptance is still pending. The older owner screenshots remain +FAIL evidence for the pre-fix build, never relabeled as passing. +No link to AP159 ramp membership or AMD#477 is established. FPS is deferred, +G4 remains FAIL, and nothing merges to main. diff --git a/docs/research/2026-09-01-overhaul/s5-consumers-material-closeout-packet.md b/docs/research/2026-09-01-overhaul/s5-consumers-material-closeout-packet.md index 0f0569dfd..f775101f6 100644 --- a/docs/research/2026-09-01-overhaul/s5-consumers-material-closeout-packet.md +++ b/docs/research/2026-09-01-overhaul/s5-consumers-material-closeout-packet.md @@ -5074,3 +5074,35 @@ a production AP159 repair. Neither a visual-cause nor AMD-cause claim follows. No source/package/FPS change or new intentional deviation. Debugger detached; AcDream25724 and retail35048 remain open for owner, ACE13340 stays up. No builds/tests/closure while owner checks. C1a/AP159 and #477/G4 remain open. + +## 62. Exterior cathedral wall collision #482 — 2026-09-05 + +Owner reports physical wall penetration at F4180012 x48.002960,y39.257545, +z160.004990; retail corrects x to48.480000. Snapshot identifies000A's single +direct-Gfx01001FB3 shell, part0/root frames identical,449 physics polygons, +and no0012 building. Named/paired native dispatch confirms no terrain polygon +does not suppress the later building/object channels. AcDream's whole-cell +continue does. Full production near-build/publication with prepared flat +collision reproduces both walking-in and initially-overlapping failures, +clear-space control passes. artifacts/482-wall/482-prefixed-installed-replay.trx: +2failed/1passed/0skipped, actual candidates0012/000A and embedded x48.002960. + +Owner explicitly authorized closing both and continuing. Both exact clients +closed gracefully; AcDream logout confirmed, no debugger, ACE9000 still up. +Bounded OpenAI implementation owns builds/tests; lead owns integration and +graphical acceptance. `482-cathedral-exterior-collision-contract.md` and +`482-cathedral-exterior-collision-verification.md` record scope/native evidence. +Owner subsequently clarified functional collision, not exact final retail +coordinates. Original unmatched strict-placement comparison preserved; +prepared human control/direct flat-shell clearance supplies the functional +oracle. Frozen retail/production review PASS after one fixture correction, +25focused PASS, old-skip sabotage5expectedFAIL, restored green. Lead full +Release0W0E/hermetic17036/0/0 and installed4/4 pass. UN-10 filed/retired with +the scoped repair, no new intentional deviation. Exact graphical candidate +and artifacts are in the verification report. Lead wall30724 PASS provisional +for actual outside placement, stopped approach and escape; separate settled +controls33696 narrow PASS for ground/geometry with camera-opacity limits. +Nine PNGs opened, both clients exit0/gracefully, checkpoints completed/visible/ +cuefalse/invariants0/transit idle. No clients/debugger remain, ACE up. Owner +wall acceptance remains pending; the short runs do not clear stability. +AP159 ramp, AMD#477 and G4 stay open, FPS remains deferred. diff --git a/src/AcDream.Core/Physics/TransitionTypes.cs b/src/AcDream.Core/Physics/TransitionTypes.cs index c6a801646..d45df607f 100644 --- a/src/AcDream.Core/Physics/TransitionTypes.cs +++ b/src/AcDream.Core/Physics/TransitionTypes.cs @@ -2878,37 +2878,46 @@ public sealed class Transition if ((cellId & 0xFFFFu) < 0x0100u) { - var terrainWalkable = engine.SampleTerrainWalkable(footCenter.X, footCenter.Y); - if (terrainWalkable is null || terrainWalkable.Value.CellId != cellId) + // CTransition::check_other_cells (0x0050ae98): a null cell + // is unavailable. CLandCell::find_env_collisions + // (0x00532f96 -> 0x0053305e) instead returns OK when its + // terrain polygon misses; the available cell must still + // run its building and object channels (0x00532d79/86). + if (engine.DataCache.CellGraph.GetVisible(cellId) is null) continue; - var terrainState = ValidateWalkable( - footCenter, - sphereRadius, - terrainWalkable.Value.Plane, - terrainWalkable.Value.IsWater, - terrainWalkable.Value.WaterDepth, - cellId: terrainWalkable.Value.CellId, - walkableVertices: terrainWalkable.Value.Vertices); - - if (PhysicsDiagnostics.ProbeIndoorBspEnabled) + var terrainWalkable = engine.SampleTerrainWalkableInCell( + cellId, footCenter.X, footCenter.Y); + if (terrainWalkable is { } terrain) { - var plane = terrainWalkable.Value.Plane; - Console.WriteLine(System.FormattableString.Invariant( - $"[other-cells] primary=0x{sp.CheckCellId:X8} iter=0x{cellId:X8} terrain wpos=({footCenter.X:F3},{footCenter.Y:F3},{footCenter.Z:F3}) r={sphereRadius:F3} result={terrainState} n=({plane.Normal.X:F3},{plane.Normal.Y:F3},{plane.Normal.Z:F3}) d={plane.D:F3}")); - } + var terrainState = ValidateWalkable( + footCenter, + sphereRadius, + terrain.Plane, + terrain.IsWater, + terrain.WaterDepth, + cellId: terrain.CellId, + walkableVertices: terrain.Vertices); - if (PhysicsDiagnostics.ProbePushBackEnabled) - { - PhysicsDiagnostics.LogPushBackCellTransit( - primaryCellId: sp.CheckCellId, - otherCellId: cellId, - bspResult: (int)terrainState, - halted: false); - } + if (PhysicsDiagnostics.ProbeIndoorBspEnabled) + { + var plane = terrain.Plane; + Console.WriteLine(System.FormattableString.Invariant( + $"[other-cells] primary=0x{sp.CheckCellId:X8} iter=0x{cellId:X8} terrain wpos=({footCenter.X:F3},{footCenter.Y:F3},{footCenter.Z:F3}) r={sphereRadius:F3} result={terrainState} n=({plane.Normal.X:F3},{plane.Normal.Y:F3},{plane.Normal.Z:F3}) d={plane.D:F3}")); + } - if (ApplyOtherCellResult(terrainState, out var terrainHalted)) - return terrainHalted; + if (PhysicsDiagnostics.ProbePushBackEnabled) + { + PhysicsDiagnostics.LogPushBackCellTransit( + primaryCellId: sp.CheckCellId, + otherCellId: cellId, + bspResult: (int)terrainState, + halted: false); + } + + if (ApplyOtherCellResult(terrainState, out var terrainHalted)) + return terrainHalted; + } // BR-7 / A6.P4 (2026-06-11): retail's per-other-cell // find_collisions on a LandCell is env → building → objects diff --git a/tests/AcDream.App.Tests/Physics/CathedralExteriorWallCollisionInstalledDatTests.cs b/tests/AcDream.App.Tests/Physics/CathedralExteriorWallCollisionInstalledDatTests.cs new file mode 100644 index 000000000..561cf95db --- /dev/null +++ b/tests/AcDream.App.Tests/Physics/CathedralExteriorWallCollisionInstalledDatTests.cs @@ -0,0 +1,217 @@ +using System.Collections.Immutable; +using System.Numerics; +using AcDream.App.Streaming; +using AcDream.Content; +using AcDream.Content.Pak; +using AcDream.Core.Physics; +using AcDream.Core.World; +using DatReaderWriter.DBObjs; +using Xunit.Abstractions; + +namespace AcDream.App.Tests.Physics; + +/// +/// #482: the F418000A cathedral shell must still collide when the mover's +/// center belongs to F4180012 and only its sphere overlaps the shell's cell. +/// Uses the complete production near-build/publication and prepared package. +/// +[Trait("Lane", "InstalledDat")] +public sealed class CathedralExteriorWallCollisionInstalledDatTests + : IClassFixture +{ + private const uint PrimaryCell = 0xF4180012u; + private const uint BuildingCell = 0xF418000Au; + private const uint ShellGfx = 0x01001FB3u; + // Prepared standard-human control, not an uncaptured current-avatar + // snapshot. RuntimeSetPositionMoverPreparer uses these authored fields + // multiplied by the object's scale; this witness explicitly uses scale 1. + private const uint HumanSetup = 0x02000001u; + private const float MoverScale = 1f; + private static readonly Vector3 Reported = new(48.002960f, 39.257545f, 160.004990f); + private ImmutableArray HumanSpheres => _scene.Mover.Spheres; + private float Radius => HumanSpheres[0].Radius * MoverScale; + private float Height => _scene.Mover.Height * MoverScale; + private float StepUp => _scene.Mover.StepUpHeight * MoverScale; + private float StepDown => _scene.Mover.StepDownHeight * MoverScale; + private readonly Scene _scene; + private readonly ITestOutputHelper _output; + + public CathedralExteriorWallCollisionInstalledDatTests(Scene scene, ITestOutputHelper output) + => (_scene, _output) = (scene, output); + + [Fact] + public void OutsideApproach_IsBlockedByNeighboringPreparedShell() + { + AssertSceneAndCandidate(); + Vector3 from = Reported with { X = 48.6f }; + ResolveResult result = _scene.Engine.ResolveWithTransition( + from, Reported, PrimaryCell, Radius, Height, StepUp, StepDown, + isOnGround: true, body: GroundedBody(from), + moverFlags: ObjectInfoState.IsPlayer | ObjectInfoState.EdgeSlide, + movingEntityId: 0x000F4243u, sphereList: HumanSpheres); + _output.WriteLine($"approach from={from} requested={Reported} actual={result.Position} ok={result.Ok}"); + Assert.True(result.Ok); + Assert.InRange(result.Position.X, 48.479f, 48.601f); + Assert.InRange(MathF.Abs(result.Position.Y - Reported.Y), 0f, 0.001f); + Assert.InRange(MathF.Abs(result.Position.Z - Reported.Z), 0f, 0.01f); + } + + [Fact] + public void ReportedInitialOverlap_PlacementFindsClearPositionOutsideWall() + { + AssertSceneAndCandidate(); + PhysicsSetPositionResult result = _scene.Engine.SetPosition(new PhysicsSetPositionRequest( + Reported, Quaternion.Identity, PrimaryCell, Reported, HumanSpheres, + Scale: MoverScale, StepUpHeight: StepUp, StepDownHeight: StepDown, + MoverFlags: ObjectInfoState.IsPlayer | ObjectInfoState.EdgeSlide, + MovingEntityId: 0x000F4243u, + Flags: PhysicsSetPositionFlags.Placement | PhysicsSetPositionFlags.Slide + | PhysicsSetPositionFlags.DoNotCreateCells)); + _output.WriteLine($"overlap requested={Reported} actual={result.Position} error={result.Error} residence={result.Residence}"); + Assert.True(result.IsCommitted); + // Owner's acceptance is functional wall collision, not equality to + // retail's separately observed later settle position. The original + // strict/unmatched placement fixture and failed TRX are preserved. + Assert.True(result.Position.X >= 48f + Radius - 0.001f); + Assert.Equal(TransitionState.OK, QueryShellPlacement(result.Position)); + } + + [Fact] + public void ClearExteriorPosition_PreservesFreeMovement() + { + AssertSceneAndCandidate(); + Vector3 from = Reported with { X = 49f }; + Vector3 target = from + new Vector3(0f, 0.1f, 0f); + ResolveResult result = _scene.Engine.ResolveWithTransition( + from, target, PrimaryCell, Radius, Height, StepUp, StepDown, + isOnGround: true, body: GroundedBody(from), + moverFlags: ObjectInfoState.IsPlayer | ObjectInfoState.EdgeSlide, + movingEntityId: 0x000F4243u, sphereList: HumanSpheres); + _output.WriteLine($"clear requested={target} actual={result.Position} ok={result.Ok}"); + Assert.True(result.Ok); + Assert.InRange(Vector3.Distance(target, result.Position), 0f, 0.001f); + } + + [Fact] + public void MovementAwayFromExteriorWall_IsUnobstructed() + { + AssertSceneAndCandidate(); + Vector3 from = Reported with { X = 48.6f }; + Vector3 target = from + new Vector3(0.2f, 0f, 0f); + ResolveResult result = _scene.Engine.ResolveWithTransition( + from, target, PrimaryCell, Radius, Height, StepUp, StepDown, + isOnGround: true, body: GroundedBody(from), + moverFlags: ObjectInfoState.IsPlayer | ObjectInfoState.EdgeSlide, + movingEntityId: 0x000F4243u, sphereList: HumanSpheres); + _output.WriteLine($"away requested={target} actual={result.Position} ok={result.Ok}"); + Assert.True(result.Ok); + Assert.InRange(Vector3.Distance(target, result.Position), 0f, 0.001f); + Assert.Equal(TransitionState.OK, QueryShellPlacement(result.Position)); + } + + private void AssertSceneAndCandidate() + { + PhysicsDataCache cache = _scene.Cache; + Assert.Equal(CollisionTraversalMode.Flat, cache.CollisionTraversalMode); + BuildingPhysics building = Assert.IsType(cache.GetBuilding(BuildingCell)); + Assert.Equal(ShellGfx, building.ModelId); + Assert.Equal(new Vector3(36f, 36f, 160f), building.WorldTransform.Translation); + Assert.InRange(Vector3.Distance(-Vector3.UnitX, + Vector3.TransformNormal(Vector3.UnitX, building.WorldTransform)), 0f, 0.000001f); + Assert.InRange(Vector3.Distance(-Vector3.UnitY, + Vector3.TransformNormal(Vector3.UnitY, building.WorldTransform)), 0f, 0.000001f); + Assert.Null(cache.GetBuilding(PrimaryCell)); + GfxObjPhysics physics = Assert.IsType(cache.GetGfxObj(ShellGfx)); + Assert.Null(physics.BSP); + Assert.NotNull(physics.FlatPhysicsBsp); + Assert.True(physics.FlatPhysicsBsp.RootIndex >= 0); + Assert.Equal(449, physics.FlatPhysicsBsp.PolygonTable.Polygons.Length); + Assert.NotNull(cache.CellGraph.GetVisible(BuildingCell)); + _output.WriteLine($"mover standard-human={HumanSetup:X8} scale={MoverScale} height={Height} steps={StepUp}/{StepDown} spheres={string.Join(';', HumanSpheres)}"); + Vector3 center = Reported + HumanSpheres[0].Origin * MoverScale; + uint containing = CellTransit.FindCellSet(cache, center, Radius, PrimaryCell, out var cells); + _output.WriteLine($"candidate containing={containing:X8} cells={string.Join(',', cells.Select(id => id.ToString("X8")))} shell={building.ModelId:X8} anchor={building.WorldTransform.Translation}"); + Assert.Equal(PrimaryCell, containing); + Assert.Contains(BuildingCell, cells); + Assert.Equal(PrimaryCell, _scene.Engine.SampleTerrainWalkable(center.X, center.Y)!.Value.CellId); + Assert.Null(_scene.Engine.SampleTerrainWalkableInCell(BuildingCell, center.X, center.Y)); + Assert.Equal(TransitionState.Collided, QueryShellPlacement(Reported)); + } + + private TransitionState QueryShellPlacement(Vector3 position) + { + var transition = new Transition(); + transition.SpherePath.InitPath(position, position, PrimaryCell, HumanSpheres, MoverScale); + transition.SpherePath.InsertType = InsertType.Placement; + transition.SpherePath.BldgCheck = true; + BuildingPhysics building = _scene.Cache.GetBuilding(BuildingCell)!; + Assert.True(Matrix4x4.Decompose(building.WorldTransform, out _, out var rotation, out var origin)); + var inverse = Quaternion.Inverse(rotation); + Vector3 local0 = Vector3.Transform(transition.SpherePath.GlobalSphere[0].Origin - origin, inverse); + Vector3 local1 = Vector3.Transform(transition.SpherePath.GlobalSphere[1].Origin - origin, inverse); + return CollisionTraversal.FindCollisions(_scene.Cache, _scene.Cache.GetGfxObj(ShellGfx)!, + transition, local0, Radius, true, local1, HumanSpheres[1].Radius * MoverScale, + local0, Vector3.Transform(Vector3.UnitZ, inverse), 1f, rotation, _scene.Engine, origin); + } + + private static PhysicsBody GroundedBody(Vector3 position) => new() + { + Position = position, + Orientation = Quaternion.Identity, + ContactPlaneValid = true, + ContactPlane = new Plane(Vector3.UnitZ, -160f), + ContactPlaneCellId = PrimaryCell, + TransientState = TransientStateFlags.Contact | TransientStateFlags.OnWalkable, + }; + + public sealed class Scene : IDisposable + { + private readonly BoundedTestDatCollection _dat; + private readonly PakPreparedAssetSource _prepared; + public PhysicsDataCache Cache { get; } + public PhysicsEngine Engine { get; } + public FlatSetupCollision Mover { get; } + + public Scene() + { + Assert.Equal("1", System.Environment.GetEnvironmentVariable("ACDREAM_RUN_INSTALLED_DAT_TESTS")); + string? directory = System.Environment.GetEnvironmentVariable("ACDREAM_DAT_DIR"); + string? package = System.Environment.GetEnvironmentVariable("ACDREAM_PAK_PATH"); + Assert.True(Directory.Exists(directory), "An explicit installed ACDREAM_DAT_DIR is required."); + Assert.True(File.Exists(package), "An explicit validated ACDREAM_PAK_PATH is required."); + _dat = new BoundedTestDatCollection(directory!); + var bounded = (IDatReaderWriter)_dat; + _prepared = new PakPreparedAssetSource(package!, bounded); + PreparedCollisionReadResult mover = _prepared.ReadSetupCollision(HumanSetup); + Assert.Equal(PreparedAssetReadStatus.Loaded, mover.Status); + Mover = Assert.IsType(mover.Data); + Assert.Equal(2, Mover.Spheres.Length); + float[] heights = Assert.IsType(bounded.Get(0x13000000u)).LandDefs.LandHeightTable; + var factory = new LandblockBuildFactory(bounded, _prepared, new object(), heights); + LandblockBuild build = Assert.IsType(factory.Build(new LandblockBuildRequest( + 0xF418FFFFu, LandblockStreamJobKind.LoadNear, Generation: 1, + new LandblockBuildOrigin(0xF4, 0x18)))); + Assert.NotNull(build.EnvCells); + LandblockCollisionBuild collisions = Assert.IsType(build.Collisions); + Assert.NotEmpty(collisions.CellStructures); + Cache = PhysicsDataCache.CreateProduction(); + Engine = new PhysicsEngine { DataCache = Cache }; + TerrainSurface terrain = LandblockPhysicsContentBuilder.BuildTerrainSurface(build.Landblock, heights); + var surfaces = new List(); + var portals = new List(); + LandblockPhysicsContentBuilder.PublishPreparedCells(Cache, build.Landblock, collisions, + Vector3.Zero, surfaces, portals); + LandblockPhysicsContentBuilder.CacheBuildings(Cache, build.Landblock, terrain, Vector3.Zero); + LandblockPhysicsContentBuilder.CachePreparedObjects(Cache, collisions); + Engine.AddLandblock(build.LandblockId, terrain, surfaces, portals, 0f, 0f); + _ = LandblockPhysicsContentBuilder.PublishStaticCollision(Engine, Cache, + build.Landblock, collisions, Vector3.Zero); + } + + public void Dispose() + { + _prepared.Dispose(); + _dat.Dispose(); + } + } +} diff --git a/tests/AcDream.Core.Tests/Physics/TransitionCheckOtherCellsTests.cs b/tests/AcDream.Core.Tests/Physics/TransitionCheckOtherCellsTests.cs index 644d5f04f..e852acbba 100644 --- a/tests/AcDream.Core.Tests/Physics/TransitionCheckOtherCellsTests.cs +++ b/tests/AcDream.Core.Tests/Physics/TransitionCheckOtherCellsTests.cs @@ -9,12 +9,10 @@ using Xunit; namespace AcDream.Core.Tests.Physics; /// -/// Unit tests for the result-combine helper used by -/// . The iteration / per-cell -/// BSP-query parts are covered end-to-end by -/// ; this file pins the -/// retail-faithful halt semantics that -/// acclient_2013_pseudo_c.txt:272739-272752 spells out. +/// Pins the retail halt semantics of +/// (0x0050ae50), plus the available outdoor cell's terrain → building → +/// objects dispatch (CLandCell::find_collisions 0x00532d60). Neighbor fixtures +/// use prepared-flat production traversal with no replacement result hooks. /// public class TransitionCheckOtherCellsTests { @@ -168,4 +166,165 @@ public class TransitionCheckOtherCellsTests Assert.Equal(0xA9B40001u, t.CollisionInfo.ContactPlaneCellId); Assert.True(t.SpherePath.CheckPos.Z > -0.28f); } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void OutdoorTerrainMiss_RetainsPreparedBuildingAndObjectCollision(bool objectChannel) + { + PhysicsEngine engine = NeighborWall(objectChannel); + Transition transition = NeighborTransition(); + Assert.NotNull(engine.DataCache!.CellGraph.GetVisible(NeighborCell)); + Assert.Null(engine.SampleTerrainWalkableInCell(NeighborCell, 23.8f, 12f)); + + TransitionState result = transition.CheckOtherCells(engine, + transition.SpherePath.GlobalSphere[0].Origin, 0.48f, new[] { NeighborCell }); + + // Actual flat BSP Path 6 calls SetCollide and returns Adjusted for + // an unclipped airborne sphere; its normal belongs to StepUpNormal. + Assert.Equal(TransitionState.Adjusted, result); + Assert.True(transition.SpherePath.Collide); + Assert.Equal(-Vector3.UnitX, transition.SpherePath.StepUpNormal); + if (objectChannel) + Assert.Equal(NeighborObject, transition.CollisionInfo.LastCollidedObjectGuid); + else + Assert.True(transition.CollisionInfo.CollidedWithEnvironment); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void OutdoorTerrainMiss_NonintersectingPreparedNeighborRemainsClear(bool objectChannel) + { + PhysicsEngine engine = NeighborWall(objectChannel, wallOriginX: 30f); + Transition transition = NeighborTransition(); + Vector3 target = transition.SpherePath.CheckPos; + + TransitionState result = transition.CheckOtherCells(engine, + transition.SpherePath.GlobalSphere[0].Origin, 0.48f, new[] { NeighborCell }); + + Assert.Equal(TransitionState.OK, result); + Assert.Equal(target, transition.SpherePath.CheckPos); + Assert.False(transition.CollisionInfo.CollisionNormalValid); + Assert.Null(transition.CollisionInfo.LastCollidedObjectGuid); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void UnavailableOutdoorCell_DoesNotDispatchRetainedBuildingOrObjects(bool objectChannel) + { + PhysicsEngine engine = NeighborWall(objectChannel, wallOriginX: 24f); + Transition available = NeighborTransition(targetX: 24.3f); + Assert.Equal(NeighborCell, engine.SampleTerrainWalkable(24.3f, 12f)!.Value.CellId); + Assert.Equal(TransitionState.Adjusted, available.CheckOtherCells(engine, + available.SpherePath.GlobalSphere[0].Origin, 0.48f, new[] { NeighborCell })); + // Deliberately leave stale cached geometry and engine terrain behind: + // CellGraph availability, not a terrain sample, governs dispatch. + engine.DataCache!.CellGraph.RemoveLandblock(NeighborCell); + Assert.Null(engine.DataCache.CellGraph.GetVisible(NeighborCell)); + Assert.NotNull(engine.DataCache.GetGfxObj(NeighborGfx)); + if (objectChannel) + Assert.NotEmpty(engine.ShadowObjects.GetObjectsInCell(NeighborCell)); + else + Assert.NotNull(engine.DataCache.GetBuilding(NeighborCell)); + Transition transition = NeighborTransition(targetX: 24.3f); + Vector3 target = transition.SpherePath.CheckPos; + + TransitionState result = transition.CheckOtherCells(engine, + transition.SpherePath.GlobalSphere[0].Origin, 0.48f, new[] { NeighborCell }); + + Assert.Equal(TransitionState.OK, result); + Assert.Equal(target, transition.SpherePath.CheckPos); + Assert.False(transition.CollisionInfo.CollisionNormalValid); + Assert.Null(transition.CollisionInfo.LastCollidedObjectGuid); + } + + [Fact] + public void OutdoorPrimaryCell_IsNotDispatchedAgain() + { + PhysicsEngine engine = NeighborWall(objectChannel: false); + Transition transition = NeighborTransition(); + transition.SpherePath.SetCheckPos(transition.SpherePath.CheckPos, NeighborCell); + + Assert.Equal(TransitionState.OK, transition.CheckOtherCells(engine, + transition.SpherePath.GlobalSphere[0].Origin, 0.48f, new[] { NeighborCell })); + Assert.False(transition.CollisionInfo.CollisionNormalValid); + } + + [Fact] + public void NeighborBuildingNonOk_StopsBeforeNeighborObjects() + { + PhysicsEngine engine = NeighborWall(objectChannel: false); + engine.ShadowObjects.Register(NeighborObject, NeighborGfx, new Vector3(23.5f, 12f, 0f), + Quaternion.Identity, 10f, 0f, 0f, 0xA9B40000u, + collisionType: ShadowCollisionType.BSP, state: 0x00010000u, + seedCellId: NeighborCell, isStatic: true); + Assert.NotEmpty(engine.ShadowObjects.GetObjectsInCell(NeighborCell)); + Transition transition = NeighborTransition(); + + Assert.Equal(TransitionState.Adjusted, transition.CheckOtherCells(engine, + transition.SpherePath.GlobalSphere[0].Origin, 0.48f, new[] { NeighborCell })); + Assert.True(transition.CollisionInfo.CollidedWithEnvironment); + Assert.Null(transition.CollisionInfo.LastCollidedObjectGuid); + } + + private const uint NeighborCell = 0xA9B40009u; + private const uint NeighborGfx = 0x0100F482u; + private const uint NeighborObject = 0xCA9B4482u; + + private static Transition NeighborTransition(float targetX = 23.8f) + { + Vector3 target = new(targetX, 12f, 1f); + var transition = new Transition(); + transition.SpherePath.InitPath(new Vector3(targetX - 0.4f, 12f, 1f), target, + 0xA9B40001u, 0.48f); + transition.SpherePath.SetCheckPos(target, 0xA9B40001u); + return transition; + } + + private static PhysicsEngine NeighborWall(bool objectChannel, float wallOriginX = 23.5f) + { + var (root, polygons) = BSPStepUpFixtures.TallWall(); + var normalized = new Dictionary(); + foreach ((ushort id, ResolvedPolygon polygon) in polygons) + normalized.Add(id, new ResolvedPolygon + { + Id = id, Vertices = polygon.Vertices, Plane = polygon.Plane, + NumPoints = polygon.NumPoints, SidesType = polygon.SidesType, + }); + var graph = new GfxObjPhysics + { + SourceId = NeighborGfx, + BSP = new PhysicsBSPTree { Root = root }, + Resolved = normalized, + BoundingSphere = root.BoundingSphere, + }; + var cache = PhysicsDataCache.CreateProduction(); + cache.CacheGfxObj(NeighborGfx, FlatCollisionAssetBuilder.FlattenGfxObj(graph)); + Assert.Equal(CollisionTraversalMode.Flat, cache.CollisionTraversalMode); + Assert.Null(cache.GetGfxObj(NeighborGfx)!.BSP); + Assert.NotNull(cache.GetGfxObj(NeighborGfx)!.FlatPhysicsBsp); + var engine = new PhysicsEngine { DataCache = cache }; + var heightTable = new float[256]; + Array.Fill(heightTable, -1000f); + engine.AddLandblock(0xA9B4FFFFu, new TerrainSurface(new byte[81], heightTable), + Array.Empty(), Array.Empty(), 0f, 0f); + Vector3 origin = new(wallOriginX, 12f, 0f); + if (objectChannel) + { + engine.ShadowObjects.Register(NeighborObject, NeighborGfx, origin, + Quaternion.Identity, 10f, 0f, 0f, 0xA9B40000u, + collisionType: ShadowCollisionType.BSP, state: 0x00010000u, + seedCellId: NeighborCell, isStatic: true); + Assert.Contains(engine.ShadowObjects.GetObjectsInCell(NeighborCell), + entry => entry.EntityId == NeighborObject); + } + else + { + cache.CacheBuilding(NeighborCell, Array.Empty(), + Matrix4x4.CreateTranslation(origin), NeighborGfx); + } + return engine; + } } diff --git a/tools/overhaul-selfgate/route-482-cathedral-wall.txt b/tools/overhaul-selfgate/route-482-cathedral-wall.txt new file mode 100644 index 000000000..dcb105907 --- /dev/null +++ b/tools/overhaul-selfgate/route-482-cathedral-wall.txt @@ -0,0 +1,95 @@ +# #482: exact owner overlap, outside approach, escape and doorway controls. +# Release green first; no other client; normal High/MSAA4 profile, not FPS work. +# /loc output and PNGs are observations; inspect them before assigning PASS. +wait world-ready 180000 +wait world-visible 90000 +resize 1280 720 +wait framebuffer 1280 720 30000 +renderpack select high +wait render-pack high 90000 +sleep 4000 + +# Owner-reported overlap: a successful placement should push east to x48.48. +command /teleloc F4180012 48.002960 39.257545 160.004990 1 0 0 0 +sleep 2000 +wait materialized 1 180000 +wait world-visible 90000 +sleep 4000 +input press CameraViewDefault +command /loc +sleep 500 +checkpoint 482_01_overlap +screenshot 482-01-overlap 15000 + +# From the actual returned placement, step east away from the wall. +input down MovementStrafeRight +sleep 750 +input up MovementStrafeRight +sleep 1000 +command /loc +sleep 500 +checkpoint 482_01b_overlap_escape +screenshot 482-01b-overlap-escape 15000 + +# Face west and walk into the same wall from clear exterior space. +command /teleloc F4180012 49 39.257545 160.004990 0.707107 0 0 0.707107 +sleep 2000 +wait materialized 2 180000 +wait world-visible 90000 +sleep 3000 +input press CameraViewDefault +input down MovementForward +sleep 750 +input up MovementForward +sleep 1000 +command /loc +sleep 500 +checkpoint 482_02_approach +screenshot 482-02-approach 15000 + +# Backing away must remain possible; no wall-sticking workaround. +input down MovementBackup +sleep 750 +input up MovementBackup +sleep 1000 +command /loc +sleep 500 +checkpoint 482_03_escape +screenshot 482-03-escape 15000 + +# A second point along the same exterior wall, with a clear exterior start. +command /teleloc F4180012 49 41 160.004990 0.707107 0 0 0.707107 +sleep 2000 +wait materialized 3 180000 +wait world-visible 90000 +sleep 3000 +input press CameraViewDefault +input down MovementForward +sleep 750 +input up MovementForward +sleep 1000 +command /loc +sleep 500 +checkpoint 482_04_adjacent_wall +screenshot 482-04-adjacent-wall 15000 + +# Known valid cathedral interior arrival; no AP159 membership claim. +command /teleloc F4180104 36.9 48.7 169.805 1 0 0 0 +sleep 2000 +wait materialized 4 180000 +wait world-visible 90000 +sleep 4000 +input press CameraViewDefault +checkpoint 482_05_cathedral_interior +screenshot 482-05-cathedral-interior 15000 + +# Previously gated valid standing point outside the Holtburg doorway. +command /teleloc A9B40029 133.603592 17.391838 96.330009 0.977940 -0.143531 -0.022038 0.150157 +sleep 2000 +wait materialized 5 180000 +wait world-visible 90000 +sleep 4000 +input press CameraViewDefault +checkpoint 482_06_holtburg_doorway +screenshot 482-06-holtburg-doorway 15000 +close-client diff --git a/tools/overhaul-selfgate/route-482-controls.txt b/tools/overhaul-selfgate/route-482-controls.txt new file mode 100644 index 000000000..2696d4499 --- /dev/null +++ b/tools/overhaul-selfgate/route-482-controls.txt @@ -0,0 +1,29 @@ +# #482 unchanged-binary controls after the portal-out presentation has settled. +wait world-ready 180000 +wait world-visible 90000 +resize 1280 720 +wait framebuffer 1280 720 30000 +renderpack select high +wait render-pack high 90000 +sleep 4000 +command /teleloc F4180104 36.9 48.7 169.805 1 0 0 0 +sleep 2000 +wait materialized 1 180000 +wait world-visible 90000 +sleep 20000 +input press CameraViewDefault +command /loc +sleep 500 +checkpoint 482_control_cathedral_settled +screenshot 482-control-cathedral-settled 15000 +command /teleloc A9B40029 133.603592 17.391838 96.330009 0.977940 -0.143531 -0.022038 0.150157 +sleep 2000 +wait materialized 2 180000 +wait world-visible 90000 +sleep 20000 +input press CameraViewDefault +command /loc +sleep 500 +checkpoint 482_control_holtburg_settled +screenshot 482-control-holtburg-settled 15000 +close-client