fix #483: restore far terrain and close OVERHAUL with #484 deferred
Some checks failed
CI / linux-portable (push) Successful in 3m36s
CI / windows-gate (push) Failing after 6m52s
CI / release (push) Has been skipped

This commit is contained in:
Erik 2026-09-06 10:22:51 +02:00
parent e0ba3e7f2c
commit 3ebb120dd2
20 changed files with 700 additions and 100 deletions

View file

@ -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

View file

@ -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.

View file

@ -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"

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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 |

View file

@ -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.

View file

@ -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
```

View file

@ -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.

View file

@ -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.

View file

@ -75,8 +75,19 @@ public sealed class WalkLandscapeAssembler
RefreshSlotIfWindowed(bx, by);
}
/// <summary>Near-tier retirement retains terrain and its authored bounds.
/// A late building cleanup must not recreate a fully retired block.</summary>
public void ClearBuildings(uint landblockId)
{
(int bx, int by) = BlockCoords(landblockId);
if (!_blocks.TryGetValue((bx, by), out BlockData? data))
return;
data.Buildings = Array.Empty<WalkBuildingFactory.Entry>();
RefreshSlotIfWindowed(bx, by);
}
/// <summary>Landblock retirement (<c>LandblockRenderPublisher
/// .RemoveBuildingRegistry</c>): drop this landblock's data. Safe to call
/// .RemoveTerrain</c>): drop this landblock's data. Safe to call
/// on a landblock that never published (no-op).</summary>
public void RetireLandblock(uint landblockId)
{

View file

@ -3,6 +3,9 @@ using AcDream.Core.World;
namespace AcDream.App.Streaming;
/// <summary>Authored terrain z-slab, independent of near-tier cell/building data.</summary>
public readonly record struct LandblockTerrainBounds(float MaxZ, float MinZ);
/// <summary>
/// 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;
}

View file

@ -132,13 +132,15 @@ public sealed class LandblockBuildFactory
{
var heightmapOnly = _dats.Get<DatReaderWriter.DBObjs.LandBlock>(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.WorldEntity>(),
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));
}
/// <summary>
/// Phase A.1 Task 8: generate scenery (trees, rocks, bushes) for a single
@ -673,7 +676,7 @@ public sealed class LandblockBuildFactory
/// <summary>Campaign FW3.1: retail's per-block z-slab
/// (<c>WalkLandscapeDatBuilder</c>'s port target —
/// <c>CLandBlock::calc_lighting</c>-adjacent unpack @0x0052f1d0):
/// <c>CLandBlock::get_land_limits</c> @0x0052f1d0):
/// <c>max_zval = heightTable[maxByte] + 200</c>,
/// <c>min_zval = heightTable[minByte] - 1</c>, over the landblock's own
/// 81-byte heightmap. Uses <see cref="_heightTable"/> — already a

View file

@ -479,18 +479,18 @@ public sealed class LandblockPresentationPipeline
completed.Heightmap,
Array.Empty<WorldEntity>(),
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,

View file

@ -103,15 +103,9 @@ public sealed class LandblockRenderPublisher
private readonly Action<EnvCellLandblockBuild>? _prepareEnvCells;
private readonly Action<uint>? _removeEnvCells;
private readonly Dictionary<uint, BuildingRegistry> _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<WalkBuildingFactory.Entry>());
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++;
}

View file

@ -615,7 +615,10 @@ public sealed class LandblockStreamer : IDisposable, ILandblockCompletionSource
lb.Heightmap,
System.Array.Empty<AcDream.Core.World.WorldEntity>(),
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));

View file

@ -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]

View file

@ -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<string>());
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<LandblockStreamResult> drained = streamer.DrainCompletions();
if (drained.Count > 0)
completion = Assert.Single(drained);
else
await Task.Delay(10);
}
var loaded = Assert.IsType<LandblockStreamResult.Loaded>(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<string>();
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<string>());
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<WalkLandBlock>());
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<uint> 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,