From 3ebb120dd271c873476cd071b2a33f09d17ce710 Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 6 Sep 2026 10:22:51 +0200 Subject: [PATCH] fix #483: restore far terrain and close OVERHAUL with #484 deferred --- docs/ISSUES.md | 50 +++- docs/architecture/acdream-architecture.md | 14 ++ .../retail-divergence-register.md | 8 + docs/architecture/worldbuilder-inventory.md | 11 + docs/plans/2026-04-11-roadmap.md | 22 +- docs/plans/2026-05-12-milestones.md | 25 +- .../2026-08-30-campaign-fw-frame-walk.md | 5 +- ...-09-01-campaign-overhaul-world-solidity.md | 49 +++- .../2026-09-06-campaign-closeout.md | 89 +++++++ .../483-far-terrain-registration-contract.md | 102 +++++++- ...4-distant-terrain-flicker-investigation.md | 53 +++++ .../2026-09-01-overhaul/g4-owner-checklist.md | 35 +-- .../Rendering/Walk/WalkLandscapeAssembler.cs | 13 +- src/AcDream.App/Streaming/LandblockBuild.cs | 6 +- .../Streaming/LandblockBuildFactory.cs | 11 +- .../LandblockPresentationPipeline.cs | 15 +- .../Streaming/LandblockRenderPublisher.cs | 46 ++-- .../Streaming/LandblockStreamer.cs | 5 +- .../Streaming/LandblockBuildFactoryTests.cs | 23 +- ...dblockConcretePresentationPipelineTests.cs | 218 ++++++++++++++++++ 20 files changed, 700 insertions(+), 100 deletions(-) create mode 100644 docs/research/2026-09-01-overhaul/2026-09-06-campaign-closeout.md create mode 100644 docs/research/2026-09-01-overhaul/484-distant-terrain-flicker-investigation.md diff --git a/docs/ISSUES.md b/docs/ISSUES.md index 2b19ccf6..1c0ea87d 100644 --- a/docs/ISSUES.md +++ b/docs/ISSUES.md @@ -39,10 +39,56 @@ 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). +## #484 — Distant mountain silhouettes flicker and change with viewpoint + +**Status:** OPEN — OWNER-DEFERRED, 2026-09-06, after report on #483 candidate. +Flicker continues while still; camera movement reveals more mountain at some +spots. Exact pose/consecutive-frame reproduction still needed; no fix/PASS. +Read-only investigation found complete terrain index coverage and no obvious +stale-list accumulation. Finite GPU far clipping is a candidate explanation, +not a confirmed cause: the named and paired retail also use finite far clipping. +See `research/2026-09-01-overhaul/484-distant-terrain-flicker-investigation.md`. +The owner explicitly directed deferral and campaign closeout/merge. Exact +viewpoint and /loc remain future investigation work. Retail was untouched. +No speculative projection/fog or FPS change made; no fix/PASS claimed. + +## #483 — Far terrain loads but disappears at the near-tier boundary + +**Status:** DONE — repair accepted in owner-directed campaign closeout, +2026-09-06. Release build (0 warnings/errors), 106 narrow tests and the final +7,161-test App lane pass. Initial 105/1 narrow run selected the stale default +package and remains recorded. Both post-owner reviews PASS with no blocking +findings. The owner's subsequent silhouette-flicker report is separately +deferred as #484; no visual PASS for that symptom is claimed. + +Original owner G4 regression: owner reports a hard +terrain cutoff and reduced visible distance in `logs/owner-g4-20260906-r1/`. +The launch and later settings-reapply log both show NearRadius4/FarRadius25; +the saved landscape setting is25, so this is not a reduced preference. +No screenshot or exact cutoff-distance measurement was supplied for this row. + +Static cause: LoadFar has no EnvCells, while the sole landscape registration +in LandblockRenderPublisher.AdvanceCompleteOne is gated on EnvCells. Far GPU +terrain meshes consequently have no landscape slot/draw turn. Near-to-far +retirement independently removes the whole landscape slot with the building +registry, although it retains terrain. The production walk grid is51x51; +the standalone WalkLandscape11x11 default is not the production limit. + +Repair: terrain bounds/population now travel independently of buildings, +including the worker's Far stripping and both accepted-as-Far conversions. +Demotion retains terrain; full removal retires it. No larger near window, fog +change or second pass. Six production/two test files; AP-149 remains open. +The repaired build was launched for the owner in logs/owner-g4-20260906-r2/ +and r3. Retail was untouched; its presence no longer blocks builds by explicit +owner override. Exact hashes, review scope and proof limits: +`research/2026-09-01-overhaul/483-far-terrain-registration-contract.md` and +`research/2026-09-01-overhaul/2026-09-06-campaign-closeout.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], +**Status:** DONE — repair accepted in owner-directed campaign closeout, +2026-09-06. Automated gates and lead graphical wall gate PASS/provisional +remain the recorded proof, not newly executed tests. 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 diff --git a/docs/architecture/acdream-architecture.md b/docs/architecture/acdream-architecture.md index 0e810881..1670411f 100644 --- a/docs/architecture/acdream-architecture.md +++ b/docs/architecture/acdream-architecture.md @@ -1190,6 +1190,20 @@ cross-source tie ordinal), and AP-243 (retail's >50 m shared-cell-key optimization). They are bounded residuals, not a second visibility or ordering model. +**Terrain population ownership (#483 closed, 2026-09-06).** +`LandblockBuild.TerrainBounds` carries the existing authored height-table bounds +for both Near and Far loads, independently of EnvCells. The worker's Far payload +stripping and both accepted-as-Far conversions preserve this metadata. +`LandblockRenderPublisher` registers terrain in the sole `WalkLandscapeAssembler` +even when the build contains no buildings; terrain draws still come exclusively +from the retail walk's land-cell turns. Demotion clears the entry's building +references but retains terrain; full terrain removal retires the entry. This +restores missing population without changing the walk, view distance or Far-tier +content policy. AP-149's missing distant static/building closure remains open. +Release build, 106 narrow tests and the final 7,161-test App lane pass; both +post-owner review lenses PASS. Owner-directed campaign closeout accepts this +repair with separate silhouette-flicker issue #484 deferred. + The exact target and evidence are recorded in `docs/research/2026-09-01-overhaul/oh1-retail-world-contract.md`; OH5, OH6, and OH7 own these cutovers. diff --git a/docs/architecture/retail-divergence-register.md b/docs/architecture/retail-divergence-register.md index bc13c02a..2c7334d6 100644 --- a/docs/architecture/retail-divergence-register.md +++ b/docs/architecture/retail-divergence-register.md @@ -1,5 +1,13 @@ # Retail Divergence Register — current through 2026-08-28 +**OVERHAUL closeout, 2026-09-06:** owner-directed acceptance carries all +remaining register rows, including AP-149; it is not blanket retirement. +#483 restores missing terrain registration with the existing retail bounds +and walk, introducing no intentional deviation. #484 is an owner-deferred +symptom with unconfirmed cause, tracked in ISSUES rather than misclassified +as a proven retail deviation. See the +[closeout](../research/2026-09-01-overhaul/2026-09-06-campaign-closeout.md). + **What this is.** The single auditable register of every known place acdream's runtime behavior can deviate from the retail client (Sept 2013 EoR build, `docs/research/named-retail/`). It was triggered by a week of "small things" diff --git a/docs/architecture/worldbuilder-inventory.md b/docs/architecture/worldbuilder-inventory.md index a880bd0e..4322aa05 100644 --- a/docs/architecture/worldbuilder-inventory.md +++ b/docs/architecture/worldbuilder-inventory.md @@ -461,6 +461,17 @@ rendering and landblock mesh pins while retaining the terrain slot. Core's matching physics demotion preserves the terrain surface but removes indoor cells, portals, buildings, and static shadow registrations. +**Far-terrain draw registration (#483 closed, 2026-09-06).** +The existing `CLandBlock::get_land_limits` height-table bounds (+200/-1) are +carried independently of EnvCells by `LandblockBuild.TerrainBounds`, including +worker and accepted-as-Far conversions. Terrain publication registers both tiers +with the single walk landscape owner. Demotion retains that terrain entry while +clearing buildings; full terrain removal retires it. No new terrain algorithm, +second draw pass, larger Near window or Far static/building hydration is added. +AP-149 remains open. Release build, 106 narrow tests and the final 7,161-test +App lane pass; both post-owner review lenses PASS. Owner-directed campaign +closeout accepts the repair, with separate flicker issue #484 deferred. + **Cost-budgeted publication seam (2026-07-24).** WorldBuilder's editor path publishes a complete manager-owned scene; acdream's live streamer instead advances prepared render, physics, static, building, EnvCell, and spatial diff --git a/docs/plans/2026-04-11-roadmap.md b/docs/plans/2026-04-11-roadmap.md index b8f13659..d1517b23 100644 --- a/docs/plans/2026-04-11-roadmap.md +++ b/docs/plans/2026-04-11-roadmap.md @@ -518,19 +518,19 @@ Plus polish that doesn't get its own phase number: ## Phases ahead — agreed order -### Campaign OVERHAUL v2 — world solidity (ACTIVE, G4 unpassed, 2026-09-05) +### Campaign OVERHAUL v2 — world solidity (CLOSED, owner accepted with carried issues, 2026-09-06) -The five-slice successor owns geometry, cross-cell membership, retail walk, -depth/alpha ordering and consumer/material closeout. S1-S5 implementations and -the gate-found repairs are landed; final Release candidate `eea83ac88` has -17,094 hermetic passes and passing lifecycle/reconnect/R6 checks. Canonical -InstalledDat is not wholly green (eight unchanged failures/one skip), and one -final visual control failed at native shutdown (#422 symptom recurrence). -Owner G4, explicit carried-result disposition and final documentation closeout -remain; no main merge or CLOSED claim. FPS/C2 is owner-deferred and the known -retail cathedral slab leak is unchanged. The current ledger is +The five-slice successor shipped geometry, cross-cell membership, retail walk, +depth/alpha ordering and consumer/material closeout, plus gate-found repairs. +The final #483 Far-terrain repair passes Release, 106 narrow and 7,161 App tests; +both post-owner review lenses PASS. The owner directed closeout/integration +with #484 silhouette flicker deferred. Earlier integrated evidence retains +17,094 hermetic passes and lifecycle/reconnect/R6. InstalledDat's eight known +failures/one skip and the native shutdown failure remain failures; AMD cause +uncertainty, FPS/C2, AP-149 and the known retail cathedral slab leak are carried. +Remaining unreported owner matrix rows are not fabricated PASS rows. The ledger is [`2026-09-01-campaign-overhaul-world-solidity.md`](2026-09-01-campaign-overhaul-world-solidity.md), -with the [owner checklist](../research/2026-09-01-overhaul/g4-owner-checklist.md). +with the [owner-directed closeout](../research/2026-09-01-overhaul/2026-09-06-campaign-closeout.md). ### Campaign FW — the retail frame walk (execution superseded by OVERHAUL) diff --git a/docs/plans/2026-05-12-milestones.md b/docs/plans/2026-05-12-milestones.md index 2d4c401f..7538117a 100644 --- a/docs/plans/2026-05-12-milestones.md +++ b/docs/plans/2026-05-12-milestones.md @@ -12,20 +12,19 @@ world-lifecycle automation protects fresh login, outdoor/world-edge/dungeon travel, same-location revisit, exact graceful disconnect, and uncapped fresh-process reconnect with canonical JSON/PNG evidence. -**Active render prerequisite (2026-09-05): Campaign OVERHAUL v2 — G4 unpassed.** -The five-slice successor to FW has landed the geometry, membership, retail -walk, depth/alpha and consumer/material cutovers, plus the gate-found exterior, -portal-reveal, lighting, equipped-item and cathedral-wall repairs. The final -candidate is Release `eea83ac88`; hermetic17,094/17,094 and the final lifecycle, -reconnect and R6 checks pass. Canonical InstalledDat retains eight documented -failures/one skip; a final visual-control run also failed at native shutdown -despite graceful network logout. Neither is silently counted as PASS. -The final owner matrix and explicit carried-result disposition remain, followed -by documentation closeout; no main merge before G4. FPS/C2 is owner-deferred, -and retail's known cathedral slab leak is unchanged. FW's old ordinary-mesh -clip model is historical, not the current architecture. Plan/ledger: +**Render prerequisite CLOSED — owner accepted with carried issues, 2026-09-06.** +Campaign OVERHAUL v2 shipped the five geometry, membership, retail-walk, +depth/alpha and consumer/material slices, plus gate-found exterior, reveal, +lighting, held-item, cathedral-wall and Far-terrain registration repairs. +The owner directed closeout and integration with #484 mountain flicker deferred. +The last repair passes Release, 106 narrow tests and 7,161 App tests, with +both post-owner review lenses PASS. Earlier integrated evidence includes +17,094 hermetic passes and lifecycle/reconnect/R6. Known InstalledDat failures, +native shutdown failure, AMD cause uncertainty, FPS/C2 and the retail cathedral +slab leak remain carried; unreported matrix rows are not invented PASS rows. +M4 itself remains active. FW's old mesh clip model is historical. Plan/ledger: [`2026-09-01-campaign-overhaul-world-solidity.md`](2026-09-01-campaign-overhaul-world-solidity.md); -[remaining owner checklist](../research/2026-09-01-overhaul/g4-owner-checklist.md). +[owner-directed closeout](../research/2026-09-01-overhaul/2026-09-06-campaign-closeout.md). Before new M4 quest/emote/character-creation subsystem bodies enter the App layer, the active structural prerequisite is the behavior-preserving diff --git a/docs/plans/2026-08-30-campaign-fw-frame-walk.md b/docs/plans/2026-08-30-campaign-fw-frame-walk.md index 427db9c7..82c8409c 100644 --- a/docs/plans/2026-08-30-campaign-fw-frame-walk.md +++ b/docs/plans/2026-08-30-campaign-fw-frame-walk.md @@ -2,7 +2,10 @@ **Status:** EXECUTION SUPERSEDED by Campaign OVERHAUL (opened 2026-08-30; spec approved by owner 2026-08-30). Historical stages and evidence are retained; -this status is not a new user-acceptance or merge claim. +this status does not independently assert acceptance of the successor. +**Successor closeout, 2026-09-06:** OVERHAUL is owner-accepted with carried +issues (#484 deferred) and integration authorized. See its +[closeout report](../research/2026-09-01-overhaul/2026-09-06-campaign-closeout.md). **Successor execution ledger (owner-requested 2026-09-01):** [`docs/plans/2026-09-01-campaign-overhaul-world-solidity.md`](2026-09-01-campaign-overhaul-world-solidity.md). Campaign OVERHAUL preserves FW as evidence/history but owns all further 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 591984c2..64b670d4 100644 --- a/docs/plans/2026-09-01-campaign-overhaul-world-solidity.md +++ b/docs/plans/2026-09-01-campaign-overhaul-world-solidity.md @@ -1,8 +1,11 @@ # Campaign OVERHAUL — retail world construction and render solidity -**Status:** ACTIVE — **v2 rescope 2026-09-02.** Five production slices replace -the twelve-stage v1 plan. S1 is the current slice. See §13 for the ledger and -§15 for what v2 changed and why. +**Status:** CLOSED — OWNER ACCEPTED WITH CARRIED ISSUES, 2026-09-06. +The owner directed campaign closeout, commit, merge to main and push to Gitea, +with mountain flicker #484 deferred. This resolves the merge gate by owner +direction; it does not turn unreported matrix rows or known failures into PASS. +See the [closeout](../research/2026-09-01-overhaul/2026-09-06-campaign-closeout.md), +§13 for retained history and §15 for the v2 rescope. **Campaign code:** `OH`. @@ -11,9 +14,9 @@ the twelve-stage v1 plan. S1 is the current slice. See §13 for the ledger and **Branch (binding):** `claude/campaign-w-retail-frame-walk`. This is the long-lived renderer integration branch. It carries all of Campaign FW (101 -commits over `origin/main` at `e102fb36`). **Nothing merges to `main` until -the final gate passes**; `origin/main` is merged INTO this branch at every -slice boundary so it never drifts. +commits over `origin/main` at `e102fb36` at that checkpoint). The original +no-merge-before-final-gate rule held during implementation; the owner's +2026-09-06 closeout with carried issues now explicitly authorizes integration. **v2 base:** `5d907ae9` (`docs(render): define OH1 retail world contract`), tree clean. The uncommitted OH1/T3 evidence grammar (~32K lines) is parked @@ -602,6 +605,7 @@ code. | Cathedral stairwell, camera zoomed out (owner, 2026-09-03 night, #464) | `0xF4180113 [39.502899 19.589222 181.119247]` (also `0xF4180112 [39.070965 22.549135 178.781418]`, `0xF4180113 [39.364658 21.293159 179.773422]`), third-person camera zoomed fully out and tilted slightly up | Root `0xF4180114` with `0x113`/`0x112` flooded draws the stairwell solid, and from an eye beyond the open y=24 face (root `0xF4180109`, which retail's swept 0.3 m viewer sphere also reaches) the stairwell cells seen through the building's exterior portals draw with their walls — no vaulted hall, archway sky, balcony or floating stair slabs through the stairwell walls | S4 (owner gate G3 names zoom) | | Adjacent outdoor portal reveal (#474, owner 2026-09-05) | 3032FFFF → destination30310005, same-window hop184.5m; near4/far25, reveal6 in owner-regate-20260905-bf23673f3-r1 | Destination publication and exact placement acknowledgements converge; no circular wait, no forced/partial world reveal. Include repeated adjacent and long-distance portal controls; record latency rather than presuming all slow holds share this cause | S5 / G4 | +| Distant terrain cutoff (#483, owner 2026-09-06) | Owner G4 run owner-g4-20260906-r1, near4/far25; exact standing pose/cutoff distance not captured | Every resident far-terrain block has a landscape entry and an eligible terrain draw turn; near-to-far demotion retains terrain while removing buildings. Preserve the selected far distance and retail ordering; do not hide the missing population with fog or a second terrain pass | S5 / G4 | Append-only; a new symptom gets a row and an existing invariant before any fix. @@ -678,6 +682,13 @@ exact commit, recipe, and log path; the owner closes the client. ## 13. Execution ledger +**Final disposition — 2026-09-06:** S1-S5 and #483 are closed by owner-directed +acceptance with carried issues. #483 Release/106 narrow tests and the final +7,161-test App lane pass; both post-owner review lenses PASS. #484 is OPEN, +owner-deferred, not fixed. Earlier pending/FAIL snapshots below are historical; +recorded failures remain failures. The [closeout report](../research/2026-09-01-overhaul/2026-09-06-campaign-closeout.md) +owns current status, exact candidate hashes, review scope and remaining work. + Update immediately when a slice changes state. Chat is not the ledger. | Slice | Status | Spec | Implementation | Reviews | Automated gate | Owner gate | Notes | @@ -719,6 +730,32 @@ The remaining owner session and explicit carried-result boundaries are in `docs/research/2026-09-01-overhaul/g4-owner-checklist.md`; no row is pre-passed. Known retail leak unchanged, FPS/C2 deferred, no main merge. +**2026-09-06 owner G4 update:** maximum-distance terrain cutoff is a new FAIL, +with a fixed candidate now launched for owner re-test: PID17856, +logs/owner-g4-20260906-r2/,07:37:36UTC; entered world07:37:40UTC. Release build +0 warnings/errors; narrow tests106/106 with explicit current package (initial +105/1 stale-default-package failure retained). Owner explicitly removed the +retail-absence restriction for builds/tests/launch; retail remains untouched. +Owner test then review are pending, no visual/G4 PASS. The following handoff +status is historical and superseded by this execution update: +#483. Source confirms missing Far landscape registration plus demotion removing +terrain's walk entry with the building registry. Bounded contract +`483-far-terrain-registration-contract.md` at e0ba3e7f2 now has a written, +uncommitted candidate and regression tests (six production/two test files). +The worker Far-payload metadata forwarding is an explicitly authorized contract +amendment. Only whitespace validation ran: UNBUILT/UNTESTED/UNREVIEWED; +the previously tested eea83ac88 binary is unchanged. +Owner's explicit order is fix -> owner test -> review; no pre-owner review. +Retail PID25988 is owner-held: do not touch it or build/test/launch beside it. +No repaired-build or G4 PASS yet. This is correctness, not deferred FPS/C2 work. + +**Additional owner finding #484 (2026-09-06):** distant mountain silhouettes +flicker even while still and change coverage with viewpoint on the #483 candidate. +Read-only investigation is documented in `484-distant-terrain-flicker-investigation.md`. +No cause/fix/PASS established; exact affected viewpoint requested. The .1/5000 +projection versus radius25 square is not itself proof: paired retail also clips +terrain at finite far. No speculative projection/fog change. G4 remains unpassed. + ### Rollback ledger | Slice | Commits | Last accepted gate | Revert | diff --git a/docs/research/2026-09-01-overhaul/2026-09-06-campaign-closeout.md b/docs/research/2026-09-01-overhaul/2026-09-06-campaign-closeout.md new file mode 100644 index 00000000..7bc8f2ce --- /dev/null +++ b/docs/research/2026-09-01-overhaul/2026-09-06-campaign-closeout.md @@ -0,0 +1,89 @@ +# Campaign OVERHAUL v2 — owner-directed closeout, 2026-09-06 + +**CLOSED — OWNER ACCEPTED WITH CARRIED ISSUES.** After the connected testing +and the #483 distance repair, the owner directed: "Ok defer that as an issue. +I want to close out the campain, commit and merge this worktree with main and +then push main to remote gita". This is the final merge-gate disposition: +accept the campaign with #484 deferred, not a claim that every remaining G4 +checklist row was individually run or passed. Unreported rows remain +unreported; known failed evidence remains FAIL. + +## Shipped scope + +S1-S5 provide the geometry and cross-cell membership products, single retail +frame walk, depth/alpha ordering, and consumer/material cutovers. Gate-found +repairs cover outdoor buildings, portal reveal, dungeon lighting, held items, +cathedral exterior collision and finally missing Far-terrain registration. +FW's predecessor ledger remains historical; its old clip model is not the +shipping architecture. M4 as a whole is not declared complete. + +The last bounded fix, #483, carries the existing height-table terrain bounds +through Near/Far construction and all Far conversions, registers terrain +independently of buildings, retains it during demotion and removes it during +full retirement. It changes no radius, fog, LOD or retail walk algorithm. +The owner used the repaired build; the subsequently reported mountain flicker +is separately retained as #484, with no confirmed cause or speculative fix. + +## Final candidate verification + +- Release App/test build: PASS, zero warnings/errors. +- Narrow production-path regression suite: 106 passed / 0 failed / 0 skipped, + using the explicit recipe10 package. The initial 105/1 run selected the + obsolete default recipe8 package; both reports are retained under + `artifacts/483-far-terrain/`. No source change masked that mismatch. +- Final no-build App hermetic-filter regression: **7,161 passed / 0 failed / + 0 skipped**. Report: + `artifacts/483-closeout-app-hermetic/483-closeout-app-hermetic.trx`. + Filter excludes Lane InstalledDat, Linux, Manual, Timing, Live and + PreparedPackage. This is an App result, not a new complete-solution run. +- Post-owner independent retail review: **PASS**, no blocking finding. + Independent architecture/lifetime review: **PASS**, no blocking finding. + Both reviewed the six production/two test files; no further fix round. + Review scope and named/paired retail evidence are in + `483-far-terrain-registration-contract.md`; neither review certifies #484. +- App SHA256: + `869BA14CB2C610E006C80BDFFCBD75629339A3486B8B98D7708B23050700FF34`. + Core SHA256: + `B0E676482A45ED77C2AB050BBBC988139D1BF6D99308C42ABB0CFF440D39A0BC`. + Package `acdream-v10-s5-c5.pak` SHA256: + `4E0FA6C589ECE12E13099A56385077964961EC52F888BC9161C6F5D34829B59B`. +- Owner launches: `logs/owner-g4-20260906-r2/` (PID17856) and + `logs/owner-g4-20260906-r3/` (PID24476), same repaired build. + No new automated visual/PNG PASS is invented for this closeout. + +## Preserved evidence and carried work + +The previous integrated source `eea83ac884d0b0fd74d86a17a16d7d5d93f64d94` +passed Release, 17,094 complete-solution hermetic tests, repeated unchanged +geometry/membership and Signature8 checks, lifecycle/reconnect and R6. +Those results are historical integrated evidence, not a full-suite run on +the final #483 delta. + +Canonical InstalledDat remains **376 pass / 8 known failures / 1 known skip**. +The five final visual controls retain 51 inspected PNGs and their narrow, +provisional verdicts. Four controls exited0; the wall control exited +**0xC0000374 (process FAIL)** despite graceful network logout. No native stack +establishes its cause or equates it with the AMD notice. See +`c1c-integrated-verification.md`, +`s5-final-lifecycle-stability-verification.md` and +`s5-final-visual-controls.md` for exact limitations, paths and sealed manifests. + +Carried without claiming repair: **#484** distant silhouette flicker (explicit +owner deferral); **#422** native shutdown symptom; **#477** AMD crash notice / +unconfirmed initiating cause; the known InstalledDat failures/skip; the known +retail cathedral exterior-slab leak; owner-deferred FPS/C2; and the existing +divergence-register residuals including **AP-149** (Far static/building +closure). #483 adds no intentional retail deviation and retires none of +those residuals. Remaining unreported G4 matrix rows are not fabricated PASS +rows; the owner's closeout instruction supersedes waiting for that session. + +## Integration authorization + +Commit the owned candidate and documentation, merge the campaign branch into +main, then push **origin/main** to Gitea +`https://git.snakedesert.se/erik/acdream.git`. Do not push the GitHub remote. +Preserve main's unrelated untracked files and WorldBuilder submodule checkout; +no reset, clean, stash, history rewrite or force push. Actual Git completion +is reported after the operations succeed, not asserted by this pre-commit note. +Retail is untouched. Its presence does not block a build by the owner's +explicit override; a running ACDream still must not have its binaries replaced. diff --git a/docs/research/2026-09-01-overhaul/483-far-terrain-registration-contract.md b/docs/research/2026-09-01-overhaul/483-far-terrain-registration-contract.md index bcda7127..497cb6ce 100644 --- a/docs/research/2026-09-01-overhaul/483-far-terrain-registration-contract.md +++ b/docs/research/2026-09-01-overhaul/483-far-terrain-registration-contract.md @@ -5,6 +5,50 @@ Owner order: **implement, owner tests, then review**. This overrides the normal pre-owner review order for this bounded fix; no claim of review closure follows from implementation or successful compilation. +**Latest owner safety correction (2026-09-06):** retail being open does NOT +block building/testing/launching ACDream. The owner explicitly directed +"Just ignore if retail is up or not, does not affect our building. Now build +and launch." Leave retail untouched. Only a running ACDream blocks overwriting +its build or launching a duplicate. This supersedes all older both-client +absence requirements below; those describe the earlier handoff, not current +permission. The requested launch is manual, with review after the owner's test. + +## Final disposition — CLOSED, 2026-09-06 + +The owner directed campaign closeout and integration, with subsequent mountain +flicker deferred as #484. After the owner session, independent retail and +architecture/lifetime reviews both **PASS**, no blocking findings and no new +fix round. Retail review verified unchanged get_land_limits bounds, independent +Far terrain admission, all three metadata-preserving conversion paths and +unchanged walk/LOD policy against named retail and the paired evidence below. +Lifetime review verified the sole owner, retained publication receipt, +demotion/full-removal split, late-clear non-resurrection and production-route +regressions. Neither review certifies #484 or closes AP-149. + +Final no-build App hermetic-filter regression: **7,161/0/0**; report +`artifacts/483-closeout-app-hermetic/483-closeout-app-hermetic.trx`. +The Release build and 106 narrow passes below cover the same product candidate. +See `2026-09-06-campaign-closeout.md` for the owner's merge-gate disposition +and retained failures. No new individual visual PASS is asserted. + +## Historical execution — owner client launched + +After the explicit owner override, Release App/test build passed with zero +warnings/errors. Initial narrow run: 105 pass / 1 fail because it selected the +old default acdream.pak (recipe 8, expected 10). No product change was made for +that environment failure. Rerun with ACDREAM_PAK_PATH explicitly selecting the +same acdream-v10-s5-c5.pak as the client: **106 pass / 0 fail / 0 skip**. +Both TRX reports remain under artifacts/483-far-terrain/. + +The fixed uncommitted candidate launched once: PID17856 at +2026-09-06T07:37:36.5010165Z, enteredWorld07:37:40.5272305Z; logs are +logs/owner-g4-20260906-r2/. App SHA256 +869BA14CB2C610E006C80BDFFCBD75629339A3486B8B98D7708B23050700FF34; +Core B0E676482A45ED77C2AB050BBBC988139D1BF6D99308C42ABB0CFF440D39A0BC. +Retail untouched, no automated route or automatic close. Owner testing and +post-owner review were pending at this launch checkpoint; the final disposition +above supersedes it and the earlier unbuilt handoff statements below. + ## Confirmed cause Owner-g4-20260906-r1 logged NearRadius4/FarRadius25, unchanged maximum preference. @@ -29,7 +73,8 @@ preserves this known gap instead of asserting a usable far draw product. both tiers. Keep exactly one landscape owner and existing walk ordering/LOD. Near publication supplies buildings separately; far entries have no buildings. 3. Forward the terrain metadata through both PublishAsFar conversions and all - normal load/promotion routes. No extra DAT or collision/static read for Far. + normal load/promotion routes, including LandblockStreamer's unconditional + LoadFar near-payload stripping. No extra DAT or collision/static read for Far. Respect the retained publication receipt and existing retry/budget semantics. 4. Near-to-far retirement clears building references but retains the terrain entry/bounds. Full terrain removal removes its landscape entry. Repeated @@ -50,7 +95,8 @@ do not claim it retired because distant terrain now draws. - Far build keeps EnvCells null and performs only its existing heightmap read, but carries exact non-flat authored vertical bounds. -- Production publisher + real landscape/walk product produces a nonempty +- Actual LoadFar worker -> DrainCompletions -> production publisher + real + landscape/walk product preserves the bounds and produces a nonempty terrain turn for an in-view far-only block beyond near radius4, with no building/object population. The old missing-registration implementation must fail this assertion; a renderer-slot count alone is insufficient. @@ -64,11 +110,17 @@ do not claim it retired because distant terrain now draws. ## Ownership, safety and handoff One OpenAI implementer owns only LandblockBuild.cs, LandblockBuildFactory.cs, -LandblockRenderPublisher.cs, LandblockPresentationPipeline.cs, +LandblockRenderPublisher.cs, LandblockPresentationPipeline.cs, LandblockStreamer.cs, Rendering/Walk/WalkLandscapeAssembler.cs and their directly affected tests. Small related declaration/comment edits may be proposed, not broadened silently. Lead owns issue/ledger/architecture/register and this contract. No shader/PAK edit. +Scope amendment: the implementer found that the real LoadFar worker reconstructs +LandblockBuild while stripping near payload, losing separately carried terrain +bounds. The lead authorized only that metadata forwarding in the sixth production +file and an actual worker-to-walk regression. This implements requirement 3; +no scheduling, loading policy or new retail deviation is authorized. + **Retail PID25988 is owner-held: DO NOT TOUCH.** No close, input, debugger attach, build, tests or launch while either client is running. Code edits may proceed. Do not invoke Claude or start review agents. Return the uncommitted scoped diff @@ -76,3 +128,47 @@ and exact unrun test commands; implementation is NOT yet built or tested. After the owner closes retail, lead does the green Release build and narrow regression run, launches one manual client, and leaves review until after the owner's test. No main merge and no G4 acceptance are implied. + +## Lead static retail check — 2026-09-06 + +Named pseudo-C identifies the exact helper as +`CLandBlock::get_land_limits0052F1D0`, not calc_lighting. The existing local +Ghidra endpoint8081 is offline; no new tool or retail process was launched. +Instead the lead read the paired file `C:\Turbine\Asheron's Call\acclient.exe` +as data: timestamp2013-09-06, PDB GUID9e847e2f-777c-4bd9-886c-22256bb87f32, +age1, SHA256006ffeadc5d679c871497112a5bd1f87714d0e273e2166bae5052dde369297b1. +The separately named acclient_codex_oracle.exe is2015 and was excluded. + +Direct PE byte inspection agrees with the named source: + +- 0050634F onward loads mid_width from this+4, squares it, and walks the + complete block_draw_list backwards. Non-null and block+FC in-view tests + precede the virtual DrawBlock call. There is no building-presence gate. +- 0052F281 onward loads the maximum byte's height-table value, adds + float[007C8F1C]=200.0 (bytes00004843) and stores this+E0; the minimum-byte + value subtracts float[007928B0]=1.0 (bytes0000803F), stored at this+E4. + The existing compute helper's formula is therefore preserved, not replaced + by the GPU AABB's different margins. + +This is factual input verification for the repair, not the postponed code +review or a test PASS. Retail remained open and untouched throughout. + +## Candidate handoff — 2026-09-06 + +The implementer returned the six production files plus +LandblockBuildFactoryTests.cs and LandblockConcretePresentationPipelineTests.cs +uncommitted. The candidate and regression tests are written; ownership is back +with the lead. No implementation agent remains active on this repair. +Only whitespace validation (`git diff --check`) ran. The candidate is +**UNBUILT, UNTESTED and UNREVIEWED**; the previous eea83ac88 binary is unchanged. +Retail PID25988 remains owner-held, ACDream is absent, ACE UDP9000/PID2296 is up. +No build, test, client launch, debugger attach or client close was performed. + +When both clients are absent, build the App test project in Release (which +builds its App dependencies) and run the narrow lane below before owner launch. +The test run must not be reported as a visual gate or the deferred review. + +```powershell +dotnet build tests/AcDream.App.Tests/AcDream.App.Tests.csproj -c Release --no-restore -m:1 -p:UseSharedCompilation=false +dotnet test tests/AcDream.App.Tests/AcDream.App.Tests.csproj -c Release --no-build --no-restore --filter "FullyQualifiedName~LandblockBuildFactoryTests|FullyQualifiedName~LandblockConcretePresentationPipelineTests|FullyQualifiedName~LandblockPresentationPipelineTests|FullyQualifiedName~LandblockRenderPublisherTests|FullyQualifiedName~WalkLandscapeAssemblerTests|FullyQualifiedName~RetailFrameWalkTests|FullyQualifiedName~WalkLandCellOrderTests" --logger "trx;LogFileName=483-terrain-regression.trx" --results-directory artifacts/483-far-terrain +``` diff --git a/docs/research/2026-09-01-overhaul/484-distant-terrain-flicker-investigation.md b/docs/research/2026-09-01-overhaul/484-distant-terrain-flicker-investigation.md new file mode 100644 index 00000000..aa7ed3f4 --- /dev/null +++ b/docs/research/2026-09-01-overhaul/484-distant-terrain-flicker-investigation.md @@ -0,0 +1,53 @@ +# #484 — distant mountain silhouette flicker + +**OPEN — OWNER-DEFERRED, 2026-09-06.** The owner explicitly requested filing +this issue and closing/merging the campaign. Investigation stops here; exact +pose and consecutive-frame reproduction are future issue work, not a pending +campaign gate. Cause remains unconfirmed. No production fix or PASS claimed. + +2026-09-06; owner report on the uncommitted #483 candidate based on e0ba3e7f2. +Owner says flicker continues with character/camera still; turning reveals more +mountain at affected spots. User image: +`C:/Users/erikn/AppData/Local/Temp/codex-clipboard-d6976e08-527f-47b9-8f00-79429065ea13.png`. +A still does not establish the temporal mechanism. No fix or visual PASS yet. + +## Read-only findings + +- #483's launched App hash remains + 869BA14CB2C610E006C80BDFFCBD75629339A3486B8B98D7708B23050700FF34; + PID17856, logs/owner-g4-20260906-r2/. Retail was untouched. +- `TerrainModernRenderer.AppendCellIndexRuns` covers all 384 mesh indices for + side1/cell0; other LOD cell grids expand disjoint complete row ranges. This + is draw granularity, not a changing geometry LOD. No obvious stale terrain + list accumulation found in driver/renderer clear paths. +- Terrain indirect commands use a per-frame upload-ring allocation. Fog only + blends colour; the terrain shader does not discard fogged fragments. +- CPU walk uses CY/near and view-edge planes, while the world camera projection + has .1 m near / 5000 m far. A radius25 square can reach roughly 6.8 km + diagonally. This permits hardware clipping of CPU-admitted blocks, but that + is NOT sufficient evidence of this fault or justification for extending far. +- Named retail also uses finite far clipping: zfar0081EC88 defaults to4000; + PrimD3DRender::SetFOVInternal0059AB40 builds the projection from znear/zfar; + SetDefaultD3DStates sets clipping state0x88 to1 at005A3B30. Terrain submits + XYZ through DrawPrimitiveUP, not a projected-Z clamp/far bypass. Only sky + temporarily multiplies zfar by4, then restores it. +- Lead verified those three load-bearing facts from the matching 2013 PE as + file data (no attach): SHA256 + 006ffeadc5d679c871497112a5bd1f87714d0e273e2166bae5052dde369297b1. + VA0081EC88 bytes00007A45 encode4000; 0059AB40 body loads globals81EC88/81EC84 + before the perspective call; 005A3B32 has push1/push88 before SetRenderState. + The far-clipping hypothesis remains unproven; no projection change was made. + +## Next discriminating observation + +Owner was asked to leave the client at an affected viewpoint and provide /loc. +The one window observation instead caught arrival in Neftet, not the reported +mountain silhouette. No input was sent to either client. Need that pose before +capturing consecutive stable frames and camera/terrain admission/clip facts. +Distinguish disappearing block/triangle coverage from changing shaded pixels; +then write the bounded fix contract, add a discriminating regression, verify +and gate the actual repair. Do not turn an unproven far-plane theory into a fix. +No new code, build, test, client close/launch, review, or G4 acceptance this turn. + +Current safety: retail remaining open does not block build/test/launch (explicit +owner override). Leave retail untouched; do not overwrite a running ACDream. diff --git a/docs/research/2026-09-01-overhaul/g4-owner-checklist.md b/docs/research/2026-09-01-overhaul/g4-owner-checklist.md index 55e751ee..77969d39 100644 --- a/docs/research/2026-09-01-overhaul/g4-owner-checklist.md +++ b/docs/research/2026-09-01-overhaul/g4-owner-checklist.md @@ -1,11 +1,14 @@ -# G4 owner checklist — final candidate, 2026-09-05 +# G4 owner checklist — retained matrix; closeout 2026-09-06 -Status: **NOT RUN / NOT ACCEPTED**. This is the remaining owner session, -not another implementation or review round. Binding scope is the campaign -plan's S5 gate and section10 scene matrix. The automatic checks do not replace -the owner's judgment. Do not merge or mark the campaign closed from this card. +Status: **OWNER-DIRECTED CLOSEOUT ACCEPTED WITH CARRIED ISSUES.** On 2026-09-06 +the owner directed deferral of #484, campaign closeout, commit, merge to main +and push to Gitea. This supersedes waiting for the remaining matrix session; +it does not assert individual PASS results for unreported rows or erase FAILs. +See `2026-09-06-campaign-closeout.md` for the merge-gate disposition and exact +final #483 candidate. The earlier checklist and evidence below are retained +history, not a new launch request. -## Candidate and launch +## Historical candidate and launch (2026-09-05) Use the existing Release binaries built at `eea83ac884d0b0fd74d86a17a16d7d5d93f64d94`; documentation checkpoint @@ -17,7 +20,9 @@ Exact App/Core/package hashes and green build evidence are in Before launch, recheck binary/package hashes, both client processes and ACE UDP9000; record the launch commit, flags, profile, process and log directory. Keep the saved High/MSAA4, near4/far25 profile for the normal candidate run. -No rebuild beside either client; no code change after the accepted binary. +The original no-rebuild-beside-either-client rule was superseded on 2026-09-06: +retail stays untouched but does not block building. Do not overwrite running +ACDream binaries or launch duplicates. Owner session has no automatic route or automatic close. A retail comparator is needed for the two-client row and known cathedral slab comparison; launch each client in its own command. Close owned clients gracefully when agreed. @@ -42,7 +47,8 @@ each client in its own command. Close owned clients gracefully when agreed. ## Owner session For every row retain PASS/FAIL, actual `/loc`, symptom if any, and PNG/log -paths. All rows below remain **PENDING** on this final binary. The starting +paths. Rows without a reported individual verdict remain **UNREPORTED**, not +new PASS results; waiting for them was superseded by owner closeout. The starting locations are navigation aids, not a substitute for movement or camera work. | Row | Check | Start / navigation | @@ -60,13 +66,14 @@ network logout or a client-authored code0 does not override the OS exit. Do not retry just to replace a failed record. The lead reads local crash reports/Windows evidence without changing registry, driver or dump settings. -## Acceptance and remaining closeout +## Historical acceptance requirements and final disposition Owner PASS must identify the tested rows and any explicit carried exception. The historical acceptance of#422 in Campaign VM is not automatically a new OVERHAUL acceptance. A good visual session does not prove the original AMD reset's initiating cause, remove the installed-data failures, or convert -the recorded shutdown failure to PASS. Their disposition remains explicit. +the recorded shutdown failure to PASS. Their failures remain explicit in the +closeout report. FPS/C2 performance work is owner-deferred. Retail's known cathedral leak is unchanged; its proposed post-campaign improvement is not part of this gate. @@ -74,7 +81,7 @@ unchanged; its proposed post-campaign improvement is not part of this gate. After the owner results, reconcile the shipping documentation before closure: architecture, WorldBuilder inventory, divergence register, launch options, FW ledger (superseded, not deleted), roadmap/milestones and campaign memory. -Keep the existing failure records. Only when G4 and that closeout actually -agree with the code can the campaign be marked CLOSED — USER ACCEPTED and -the authorized merge considered. Do not rebuild the accepted product for -documentation-only changes. +Keep the existing failure records. The owner's 2026-09-06 closeout instruction +now authorizes integration with carried issues, rather than requiring another +matrix session. Documentation records that disposition without inventing +per-row passes. Do not rebuild the accepted product for documentation-only changes. diff --git a/src/AcDream.App/Rendering/Walk/WalkLandscapeAssembler.cs b/src/AcDream.App/Rendering/Walk/WalkLandscapeAssembler.cs index 45642eaa..5aaf92d7 100644 --- a/src/AcDream.App/Rendering/Walk/WalkLandscapeAssembler.cs +++ b/src/AcDream.App/Rendering/Walk/WalkLandscapeAssembler.cs @@ -75,8 +75,19 @@ public sealed class WalkLandscapeAssembler RefreshSlotIfWindowed(bx, by); } + /// Near-tier retirement retains terrain and its authored bounds. + /// A late building cleanup must not recreate a fully retired block. + public void ClearBuildings(uint landblockId) + { + (int bx, int by) = BlockCoords(landblockId); + if (!_blocks.TryGetValue((bx, by), out BlockData? data)) + return; + data.Buildings = Array.Empty(); + RefreshSlotIfWindowed(bx, by); + } + /// Landblock retirement (LandblockRenderPublisher - /// .RemoveBuildingRegistry): drop this landblock's data. Safe to call + /// .RemoveTerrain): drop this landblock's data. Safe to call /// on a landblock that never published (no-op). public void RetireLandblock(uint landblockId) { diff --git a/src/AcDream.App/Streaming/LandblockBuild.cs b/src/AcDream.App/Streaming/LandblockBuild.cs index 4a04929d..22e68913 100644 --- a/src/AcDream.App/Streaming/LandblockBuild.cs +++ b/src/AcDream.App/Streaming/LandblockBuild.cs @@ -3,6 +3,9 @@ using AcDream.Core.World; namespace AcDream.App.Streaming; +/// Authored terrain z-slab, independent of near-tier cell/building data. +public readonly record struct LandblockTerrainBounds(float MaxZ, float MinZ); + /// /// Complete CPU-side output of one streaming job. The worker owns this object /// until it posts the corresponding completion; the render thread then applies @@ -12,7 +15,8 @@ public sealed record LandblockBuild( LoadedLandblock Landblock, EnvCellLandblockBuild? EnvCells = null, LandblockBuildOrigin Origin = default, - LandblockCollisionBuild? Collisions = null) + LandblockCollisionBuild? Collisions = null, + LandblockTerrainBounds TerrainBounds = default) { public uint LandblockId => Landblock.LandblockId; } diff --git a/src/AcDream.App/Streaming/LandblockBuildFactory.cs b/src/AcDream.App/Streaming/LandblockBuildFactory.cs index 6333be3e..417e9e25 100644 --- a/src/AcDream.App/Streaming/LandblockBuildFactory.cs +++ b/src/AcDream.App/Streaming/LandblockBuildFactory.cs @@ -132,13 +132,15 @@ public sealed class LandblockBuildFactory { var heightmapOnly = _dats.Get(landblockId); if (heightmapOnly is null) return null; + (float maxZ, float minZ) = ComputeWalkZSlab(heightmapOnly.Height); return new AcDream.App.Streaming.LandblockBuild( new AcDream.Core.World.LoadedLandblock( landblockId, heightmapOnly, System.Array.Empty(), AcDream.Core.World.PhysicsDatBundle.Empty), - Origin: request.Origin); // far tier: no cells/buildings/entities + Origin: request.Origin, + TerrainBounds: new(maxZ, minZ)); // far tier: no cells/buildings/entities } var baseLoaded = AcDream.Core.World.LandblockLoader.Load(_dats, landblockId); @@ -179,7 +181,7 @@ public sealed class LandblockBuildFactory _heightTable)); var envCellBuild = new AcDream.App.Rendering.Wb.EnvCellLandblockBuildBuilder(landblockId); // Campaign FW3.1: the walk landscape's per-block z-slab - // (CLandBlock::calc_lighting-adjacent unpack @0x0052f1d0). Every + // (CLandBlock::get_land_limits @0x0052f1d0). Every // near-tier landblock has outdoor terrain heights regardless of // whether it has interior cells or buildings, so this is // unconditional — unlike WalkBuildings (added below, inside @@ -208,7 +210,8 @@ public sealed class LandblockBuildFactory merged, physicsDats), completedEnvCells, - request.Origin); + request.Origin, + TerrainBounds: new(walkMaxZ, walkMinZ)); } /// /// Phase A.1 Task 8: generate scenery (trees, rocks, bushes) for a single @@ -673,7 +676,7 @@ public sealed class LandblockBuildFactory /// Campaign FW3.1: retail's per-block z-slab /// (WalkLandscapeDatBuilder's port target — - /// CLandBlock::calc_lighting-adjacent unpack @0x0052f1d0): + /// CLandBlock::get_land_limits @0x0052f1d0): /// max_zval = heightTable[maxByte] + 200, /// min_zval = heightTable[minByte] - 1, over the landblock's own /// 81-byte heightmap. Uses — already a diff --git a/src/AcDream.App/Streaming/LandblockPresentationPipeline.cs b/src/AcDream.App/Streaming/LandblockPresentationPipeline.cs index 49b4bd6d..44f26131 100644 --- a/src/AcDream.App/Streaming/LandblockPresentationPipeline.cs +++ b/src/AcDream.App/Streaming/LandblockPresentationPipeline.cs @@ -479,18 +479,18 @@ public sealed class LandblockPresentationPipeline completed.Heightmap, Array.Empty(), PhysicsDatBundle.Empty); + LandblockBuild farBuild = new( + farLandblock, + Origin: completedBuild.Origin, + TerrainBounds: completedBuild.TerrainBounds); transaction = new PublicationTransaction { Kind = PublicationKind.Far, - Build = new LandblockBuild( - farLandblock, - Origin: completedBuild.Origin), + Build = farBuild, MeshData = meshData, LandblockId = farLandblock.LandblockId, Cost = LandblockStreamResultCost.Estimate( - new LandblockBuild( - farLandblock, - Origin: completedBuild.Origin), + farBuild, meshData), Tier = LandblockStreamTier.Far, Timing = PublicationTimingProbe.CreateTimings(), @@ -534,7 +534,8 @@ public sealed class LandblockPresentationPipeline PhysicsDatBundle.Empty); LandblockBuild farBuild = new( farLandblock, - Origin: completedBuild.Origin); + Origin: completedBuild.Origin, + TerrainBounds: completedBuild.TerrainBounds); transaction = new PublicationTransaction { Kind = PublicationKind.Far, diff --git a/src/AcDream.App/Streaming/LandblockRenderPublisher.cs b/src/AcDream.App/Streaming/LandblockRenderPublisher.cs index 8b5ff5c0..2c677129 100644 --- a/src/AcDream.App/Streaming/LandblockRenderPublisher.cs +++ b/src/AcDream.App/Streaming/LandblockRenderPublisher.cs @@ -103,15 +103,9 @@ public sealed class LandblockRenderPublisher private readonly Action? _prepareEnvCells; private readonly Action? _removeEnvCells; private readonly Dictionary _buildingRegistries = new(); - // Campaign FW3.1: the walk's production world-data siblings of - // _buildingRegistries. Owned here (not injected) exactly like - // _buildingRegistries — no caller constructs a LandblockRenderPublisher - // with pre-existing walk state. Committed/retired at the same points as - // the building registry (see AdvanceCompleteOne / RemoveBuildingRegistry - // below) because both come from the same landblock's BuildingInfo array - // at the same commit. No frame reads these yet (FW3.2 wires the walk - // into the render loop) — publication happens now so FW3.1's - // conformance gate exercises the production commit path. + // The frame walk's sole terrain/building owners. Both publish in the + // retained render suffix; near-layer retirement clears only buildings, + // while full terrain retirement removes the landscape entry as well. private readonly WalkBuildingRegistry _walkBuildingRegistry = new(); private readonly WalkLandscapeAssembler _walkLandscape = new(); @@ -360,21 +354,19 @@ public sealed class LandblockRenderPublisher _buildingRegistries[registryKey] = completedBuildings.Registry; } - // Campaign FW3.1: publish this landblock's walk world data in - // the same step — build.EnvCells carries WalkBuildings/WalkMaxZ - // /WalkMinZ (computed worker-side by LandblockBuildFactory) for - // every near-tier build, even one with zero interior cells - // (the z-slab is unconditional; WalkBuildings is naturally empty - // when LandBlockInfo has none). + // Terrain exists at both tiers; only near builds supply buildings. + // Keep both publications in this retained receipt's existing step. if (build.EnvCells is { } walkEnvCells) - { _walkBuildingRegistry.Publish(landblockId, walkEnvCells.WalkBuildings); - _walkLandscape.PublishLandblock( - landblockId, - walkEnvCells.WalkMaxZ, - walkEnvCells.WalkMinZ, - walkEnvCells.WalkBuildings); - } + else + _walkBuildingRegistry.Retire(landblockId); + _walkLandscape.PublishLandblock( + landblockId, + build.TerrainBounds.MaxZ, + build.TerrainBounds.MinZ, + build.EnvCells is { } cells + ? cells.WalkBuildings + : Array.Empty()); publication.BuildingRegistryCommitted = true; } else if (publication.EnvCellPublication is { } envCellPublication @@ -421,6 +413,7 @@ public sealed class LandblockRenderPublisher public void RemoveTerrain(uint landblockId) { _removeTerrain(landblockId); + _walkLandscape.RetireLandblock(landblockId); _terrainRemovalCount++; } @@ -433,14 +426,9 @@ public sealed class LandblockRenderPublisher public void RemoveBuildingRegistry(uint landblockId) { _buildingRegistries.Remove(landblockId & 0xFFFF0000u); - // Campaign FW3.1: retire the walk's siblings at the SAME retirement - // stage — they were committed together above, so they retire - // together (no new LandblockRetirementStage; folding into the - // existing BuildingRegistry stage keeps the retirement ticket state - // machine unchanged, which is the minimal/additive choice for a - // slice that does no frame wiring yet). + // Demotion retires buildings, not the retained far-tier terrain. _walkBuildingRegistry.Retire(landblockId); - _walkLandscape.RetireLandblock(landblockId); + _walkLandscape.ClearBuildings(landblockId); _buildingRegistryRemovalCount++; } diff --git a/src/AcDream.App/Streaming/LandblockStreamer.cs b/src/AcDream.App/Streaming/LandblockStreamer.cs index 600f7b07..34078912 100644 --- a/src/AcDream.App/Streaming/LandblockStreamer.cs +++ b/src/AcDream.App/Streaming/LandblockStreamer.cs @@ -615,7 +615,10 @@ public sealed class LandblockStreamer : IDisposable, ILandblockCompletionSource lb.Heightmap, System.Array.Empty(), PhysicsDatBundle.Empty); - build = new LandblockBuild(lb, Origin: build.Origin); + build = new LandblockBuild( + lb, + Origin: build.Origin, + TerrainBounds: build.TerrainBounds); } PublishResult(new LandblockStreamResult.Loaded( load.LandblockId, tier, build, mesh, load.Generation)); diff --git a/tests/AcDream.App.Tests/Streaming/LandblockBuildFactoryTests.cs b/tests/AcDream.App.Tests/Streaming/LandblockBuildFactoryTests.cs index 51008596..3b26b4e6 100644 --- a/tests/AcDream.App.Tests/Streaming/LandblockBuildFactoryTests.cs +++ b/tests/AcDream.App.Tests/Streaming/LandblockBuildFactoryTests.cs @@ -136,6 +136,7 @@ public sealed class LandblockBuildFactoryTests result.EnvCells); Assert.Equal(240f, envCells.WalkMaxZ); // heightTable[200] + 200 Assert.Equal(4f, envCells.WalkMinZ); // heightTable[10] - 1 + Assert.Equal(new LandblockTerrainBounds(240f, 4f), result.TerrainBounds); AcDream.App.Rendering.Walk.WalkBuildingFactory.Entry buildingEntry = Assert.Single(envCells.WalkBuildings); // origin (12,12) -> cellX=cellY=0 -> low word 0*8+0+1 = 1. @@ -189,20 +190,28 @@ public sealed class LandblockBuildFactoryTests } [Fact] - public void BuildFar_NeverPopulatesWalkDataBecauseEnvCellsIsNull() + public void BuildFar_CarriesNonFlatAuthoredBoundsWithoutNearDataOrExtraReads() { - // Documents the known FW3.1 scope gap: far-tier (LoadFar) landblocks - // carry no EnvCellLandblockBuild transaction at all (terrain-only), - // so their z-slab/buildings never reach WalkLandscapeAssembler until - // a follow-up wires the far-tier path too. var dat = CreateDat(out RecordingDatProxy proxy); - proxy.Add(LandblockId, new LandBlock { Id = LandblockId }); - var factory = Factory(dat, new object()); + var heights = Enumerable.Repeat((byte)10, 81).ToArray(); + heights[80] = 200; + proxy.Add(LandblockId, new LandBlock { Id = LandblockId, Height = heights }); + var heightTable = new float[256]; + heightTable[10] = 5.25f; + heightTable[200] = 40.5f; + var source = new TestPreparedCollisionSource(PreparedAssetReadStatus.Missing); + var factory = new LandblockBuildFactory(dat, source, new object(), heightTable); LandblockBuild? result = factory.Build(Request(LandblockStreamJobKind.LoadFar)); Assert.NotNull(result); Assert.Null(result.EnvCells); + Assert.Equal(new LandblockTerrainBounds(240.5f, 4.25f), result.TerrainBounds); + Assert.Empty(result.Landblock.Entities); + Assert.Same(PhysicsDatBundle.Empty, result.Landblock.PhysicsDats); + Assert.Null(result.Collisions); + Assert.Equal(0, source.Reads); + Assert.Equal([(typeof(LandBlock), LandblockId)], proxy.Reads); } [Fact] diff --git a/tests/AcDream.App.Tests/Streaming/LandblockConcretePresentationPipelineTests.cs b/tests/AcDream.App.Tests/Streaming/LandblockConcretePresentationPipelineTests.cs index 1dd18f36..dddc973d 100644 --- a/tests/AcDream.App.Tests/Streaming/LandblockConcretePresentationPipelineTests.cs +++ b/tests/AcDream.App.Tests/Streaming/LandblockConcretePresentationPipelineTests.cs @@ -1,6 +1,7 @@ using System.Collections.Immutable; using System.Numerics; using AcDream.App.Rendering; +using AcDream.App.Rendering.Walk; using AcDream.App.Rendering.Wb; using AcDream.App.Rendering.Vfx; using AcDream.App.Streaming; @@ -527,6 +528,223 @@ public sealed class LandblockConcretePresentationPipelineTests Assert.Equal(1, fixture.Render.Diagnostics.EnvCellRemovalCount); } + [Fact] + public async Task FarLoaded_WorkerAndRealWalkPreserveTerrainBeyondNearRadiusWithoutNearPopulation() + { + ConcreteFixture fixture = Fixture(new List()); + LandblockBuild far = Build() with + { + EnvCells = null, + Origin = new LandblockBuildOrigin(0xA3, 0xB4), + TerrainBounds = new(240.5f, 4.25f), + }; + + using var streamer = LandblockStreamer.CreateForRequests( + loadLandblock: _ => far, + buildMeshOrNull: (_, _) => EmptyMesh(), + workerCount: 1); + streamer.EnqueueLoad(new LandblockBuildRequest( + LandblockId, LandblockStreamJobKind.LoadFar, Generation: 483, far.Origin)); + streamer.Start(); + LandblockStreamResult? completion = null; + for (int attempt = 0; attempt < 200 && completion is null; attempt++) + { + IReadOnlyList drained = streamer.DrainCompletions(); + if (drained.Count > 0) + completion = Assert.Single(drained); + else + await Task.Delay(10); + } + var loaded = Assert.IsType(completion); + Assert.Equal(483ul, loaded.Generation); + Assert.Equal(far.Origin, loaded.Build.Origin); + Assert.Equal(far.TerrainBounds, loaded.Build.TerrainBounds); + Assert.Null(loaded.Build.EnvCells); + Assert.Same(PhysicsDatBundle.Empty, loaded.Landblock.PhysicsDats); + + Pipeline(fixture).PublishLoaded(loaded); + + AssertFarTerrain(fixture); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void AcceptedNearAsFar_BothOverloadsPreserveTerrainAndStripNearPopulation(bool metered) + { + var calls = new List(); + ConcreteFixture fixture = Fixture(calls); + var pipeline = Pipeline(fixture); + LandblockBuild near = BuildWithWalkBuilding() with + { + Origin = new LandblockBuildOrigin(0xA3, 0xB4), + }; + LandblockStreamResult.Loaded accepted = Result(near); + + if (metered) + { + // One mutation per frame forces the real retained publication path. + var budget = new StreamingWorkBudget( + TimeSpan.FromSeconds(1), + maxCompletionAdmissions: 64, + maxAdoptedCpuBytes: 1_000_000, + maxEntityOperations: 1, + maxGpuUploadBytes: 1_000_000, + maxGlRetireOperations: 64, + destinationReserveFraction: 0.75f); + LandblockPublicationAdvance advance = default; + for (int frame = 0; frame < 64 && !advance.Completed; frame++) + { + var meter = new StreamingWorkMeter(budget); + advance = pipeline.PublishAsFar( + accepted, near, accepted.MeshData, meter, ensureProgress: false); + meter.FinishFrame(); + Assert.True(meter.Snapshot.Used.EntityOperations <= 1); + } + Assert.True(advance.Completed); + } + else + { + pipeline.PublishAsFar(accepted, near, accepted.MeshData); + } + + Assert.False(pipeline.HasPendingPublication(accepted)); + Assert.Equal(1, calls.Count(call => call == "terrain")); + Assert.Equal(1, fixture.Render.Diagnostics.CompleteCount); + AssertFarTerrain(fixture); + } + + [Fact] + public void ConcreteRetirement_DemotionKeepsTerrain_PromotionAndRevisitRestoreBuildings() + { + ConcreteFixture fixture = Fixture(new List()); + var pipeline = Pipeline(fixture); + LandblockBuild near = BuildWithWalkBuilding(); + WalkBuilding building = Assert.Single(near.EnvCells!.WalkBuildings).Building; + + pipeline.PublishLoaded(Result(near)); + AssertNearTerrain(fixture, building); + + pipeline.BeginNearLayerRetirement(LandblockId); + pipeline.BeginNearLayerRetirement(LandblockId); + Assert.Equal(0, pipeline.PendingRetirementCount); + Assert.True(fixture.State.IsLoaded(LandblockId)); + Assert.False(fixture.State.IsNearTier(LandblockId)); + Assert.Equal(0, fixture.Render.Diagnostics.TerrainRemovalCount); + fixture.Render.RemoveBuildingRegistry(LandblockId); // repeated near cleanup + AssertTerrainBounds(fixture.Render.WalkLandscape); + Assert.NotEmpty(DrawTerrain(fixture, farViewer: false).TerrainTurns); + Assert.Empty(fixture.Render.WalkBuildings.GetBuildings(LandblockId)); + Assert.False(fixture.Render.WalkBuildings.TryGetEntry(building, out _)); + Assert.All(PublishedBlock(fixture.Render.WalkLandscape).CellBuildings, + value => Assert.Null(value)); + + pipeline.PublishPromoted( + new LandblockStreamResult.Promoted(LandblockId, near, EmptyMesh()), + mergeIntoExistingLandblock: true); + AssertNearTerrain(fixture, building); + + pipeline.BeginFullRetirement(LandblockId); + pipeline.BeginFullRetirement(LandblockId); + Assert.Equal(0, pipeline.PendingRetirementCount); + Assert.False(fixture.State.IsLoaded(LandblockId)); + fixture.Render.RemoveTerrain(LandblockId); // duplicate full cleanup + fixture.Render.RemoveBuildingRegistry(LandblockId); // late stage after terrain removal + Assert.Empty(DrawTerrain(fixture, farViewer: false).TerrainTurns); + Assert.All(fixture.Render.WalkLandscape.Landscape.Blocks, value => Assert.Null(value)); + Assert.Empty(fixture.Render.WalkBuildings.GetBuildings(LandblockId)); + + pipeline.PublishLoaded(Result(near)); + AssertNearTerrain(fixture, building); + } + + private static LandblockBuild BuildWithWalkBuilding() + { + var building = new WalkBuilding { PositionCellId = 0xA9B40009u }; + var entry = new WalkBuildingFactory.Entry( + building, Matrix4x4.Identity, Matrix4x4.Identity); + LandblockBuild build = Build(Entity(0x80A9B401u)); + return build with + { + EnvCells = new EnvCellLandblockBuild( + LandblockId, [], [], [entry], walkMaxZ: 240.5f, walkMinZ: 4.25f), + TerrainBounds = new(240.5f, 4.25f), + }; + } + + private static void AssertFarTerrain(ConcreteFixture fixture) + { + TerrainTurnRecorder turns = DrawTerrain(fixture, farViewer: true); + // Ring six uses one coarse terrain cell. This fails if the far block + // has a mesh/state entry but is missing from the sole landscape walk. + Assert.Equal((LandblockId & 0xFFFF0000u, 1, 0), Assert.Single(turns.TerrainTurns)); + AssertTerrainBounds(fixture.Render.WalkLandscape); + Assert.Empty(turns.Buildings); + Assert.Empty(fixture.Render.WalkBuildings.GetBuildings(LandblockId)); + Assert.All(PublishedBlock(fixture.Render.WalkLandscape).CellBuildings, + value => Assert.Null(value)); + Assert.True(fixture.State.IsLoaded(LandblockId)); + Assert.False(fixture.State.IsNearTier(LandblockId)); + Assert.Empty(fixture.State.Entities); + Assert.Empty(fixture.World.Entities); + } + + private static void AssertNearTerrain(ConcreteFixture fixture, WalkBuilding building) + { + TerrainTurnRecorder turns = DrawTerrain(fixture, farViewer: false); + Assert.NotEmpty(turns.TerrainTurns); + Assert.Contains(building.PositionCellId, turns.Buildings); + AssertTerrainBounds(fixture.Render.WalkLandscape); + Assert.Same(building, PublishedBlock(fixture.Render.WalkLandscape).CellBuildings[8]); + Assert.Same(building, Assert.Single( + fixture.Render.WalkBuildings.GetBuildings(LandblockId)).Building); + Assert.True(fixture.State.IsNearTier(LandblockId)); + } + + private static void AssertTerrainBounds(WalkLandscapeAssembler assembler) + { + WalkLandBlock block = PublishedBlock(assembler); + Assert.Equal(240.5f, block.MaxZ); + Assert.Equal(4.25f, block.MinZ); + } + + private static WalkLandBlock PublishedBlock(WalkLandscapeAssembler assembler) => + Assert.Single(assembler.Landscape.Blocks.OfType()); + + private static TerrainTurnRecorder DrawTerrain(ConcreteFixture fixture, bool farViewer) + { + // The far-only block is six blocks east of the viewer, beyond NearRadius4. + uint cameraCell = farViewer ? 0xA3B40001u : 0xA9B40001u; + var eye = new Vector3(12f, 12f, 100f); + var target = new Vector3(farViewer ? 6 * 192f + 96f : 144f, 12f, 20f); + Vector3 forward = Vector3.Normalize(target - eye); + Matrix4x4 viewProjection = Matrix4x4.CreateLookAt(eye, target, Vector3.UnitZ) + * Matrix4x4.CreatePerspectiveFieldOfView(MathF.PI / 2f, 16f / 9f, 0.1f, 10_000f); + fixture.Render.WalkLandscape.SetViewer(cameraCell, eye); + var context = new WalkProductionFrameContext( + new CellVisibility(), fixture.Render.WalkBuildings, + eye, forward, viewProjection, 1600f, 900f, cameraCell); + var recorder = new TerrainTurnRecorder(); + new RetailFrameWalk().WalkFrame( + cameraCell, null, fixture.Render.WalkLandscape.Landscape, context, recorder); + return recorder; + } + + private sealed class TerrainTurnRecorder : IWalkEventSink + { + public List<(uint LandblockId, int SideCellCount, int CellIndex)> TerrainTurns { get; } = []; + public List Buildings { get; } = []; + + public void Emit(in WalkEvent walkEvent) + { + if (walkEvent.Kind == WalkEventKind.Building) + Buildings.Add(walkEvent.CellId); + } + + public void OnLandCellTurn(uint landblockId, int sideCellCount, int cellIndex) => + TerrainTurns.Add((landblockId, sideCellCount, cellIndex)); + } + private static LandblockPresentationPipeline Pipeline( ConcreteFixture fixture) => new( fixture.Render,