feat(diagnostics): retain bounded local crash reports (#477)

Project the original Run failure with loaded build, cached GPU and cell context before unwind. Preserve crash status and rethrow; contain report failures and omit arbitrary messages, source paths and session data.

Focused 19/19, narrow production/privacy review and default Release 17044/0/0 pass. Preserve the wrong-package smoke failure and real emitted report; corrected recipe10 smoke exits gracefully with two provisional PNG passes. AMD initiating cause and G4 remain open; FPS deferred. No new retail behavior deviation.
This commit is contained in:
Erik 2026-09-05 19:49:22 +02:00
parent 9baef7c507
commit f9bb47ec63
10 changed files with 927 additions and 2 deletions

View file

@ -0,0 +1,124 @@
# #477 — small automatic local crash report
2026-09-05. Base `9baef7c50`. Owner requested a simple report and inspection
of AMD logs so a future recurrence leaves actionable evidence. This chunk
adds diagnostics only; it does not repair or explain the AMD timeout.
FPS remains deferred, the known retail cathedral leak remains unchanged,
and G4 is not accepted.
## Verified boundary and existing owners
The original 16:41 Windows WER report is LiveKernelEvent 141 naming
`amdkmdag.sys`. The readable .NET event retained the old candidate's
`vkQueueSubmit2 (abandoned frame timeline signal)` / ErrorDeviceLost stack.
Two WER events share report ID `b3e69f70-71fc-418c-b9e8-686a6bdaf552`:
they are not proof of two separate resets. The detailed WATCHDOG dump and
the exact WER archive folder deny access, including elevated read attempts.
Scoped reads of the observed AMD CN/GameReport/Temp and Radeonsoftware
folders found no readable report explaining this crash. Do not change ACLs,
TDR/driver settings, install software, upload reports, or induce a GPU reset.
Microsoft describes 0x141 as a display-engine timeout live dump:
[0x141 documentation](https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/bug-check-0x141---video-engine-timeout-detected).
That is a symptom classification, not proof that AMD software rather than
client GPU work initiated it.
`GameWindow.Run` already catches the frame-loop failure, retains construction
cleanup, latches `_runFailure` and rethrows. Preserve that sequence and the
existing `ReportExited` crashed/graceful/shutdown-incomplete vocabulary. Add
one report attempt after the latch and before rethrow/teardown. Do not move
reporting into `ReportExited`, when world/graphics owners may be cleared.
The launcher already captures bounded stderr; leave that pipeline intact.
Use canonical `ApplicationPathSet.DiagnosticsDirectory` (Windows default
`%LOCALAPPDATA%/acdream/cache/diagnostics`) and cached Vulkan capabilities.
This is host-specific observability, not an AC algorithm or a retail port.
There is no retail decomp/binary parity claim or new gameplay divergence.
Lead records this register decision with verification in the landing commit.
## Allowed scope
Implementer owns only:
- `src/AcDream.App/Diagnostics/LocalCrashReportWriter.cs` (new focused owner
and bounded DTOs; no generic diagnostic framework).
- A narrow wiring hook in `src/AcDream.App/Rendering/GameWindow.cs`.
- `tests/AcDream.App.Tests/Diagnostics/LocalCrashReportWriterTests.cs` (new).
- `tests/AcDream.App.Tests/Rendering/GameWindowCrashStatusTests.cs`.
Lead owns this contract, architecture/issue/campaign ledgers, verification
and commit. OpenAI implementer and independent OpenAI production/privacy
review; no Claude CLI or Anthropic models. No commits or client launches
by implementer. No changes to Core, Runtime, shaders, startup options,
GPU recovery, exception types, status writers or launcher capture.
## Report contract
One automatic best-effort local JSON report per failing window run, with
unique UTC/PID/random identity, a unique same-directory temporary file and
atomic non-overwriting rename. No report on a successful run; no per-frame
work, background collector, network/upload, retry or retention deletion.
Existing reports must remain intact. Cap the serialized report at 256 KiB;
bound strings and exception traversal before serialization (at most eight
exception nodes, explicit truncation markers). Preserve valid JSON rather
than truncating raw bytes. Temporary-file cleanup is scoped to this writer's
own exact path and is also best effort.
Include only explicitly projected data:
- UTC time, PID, schema version; loaded App assembly version/informational
version and module MVID. Do not infer a clean source revision from git or
read the current checkout as runtime evidence.
- Original exception type, HResult, bounded stack without source file paths,
and bounded inner/aggregate structure. Exact `VulkanCallException.Operation`
plus numeric and named `Result` when present. Arbitrary exception messages,
`Data`, object serialization, raw environment/arguments/session config,
character/account names, credentials, chat and packet contents are omitted.
The original unhandled error/stderr behavior remains available unchanged.
- Cached GPU device name, driver/API information, actual width/height and
sample count from the existing Vulkan context; no new GPU/native queries.
- Capture the current controller reference once and project its existing
`CellPosition` cell/local XYZ and `State`; optional Runtime generation.
Unavailable values remain null, not invented cell/position zero. Non-finite
coordinates must not prevent the original exception from propagating.
Capture, serialization, file creation/write/rename, cleanup and any diagnostic
notification must never replace the original failure. Guard the entire hook,
including context/delegate construction. Keep bare `throw;` and crash status.
Use existing output only for a short saved-path notice or safe failure notice;
never log the secondary reporting exception or serialize its arbitrary data.
An unavailable context should still allow a minimal error report where possible.
Coverage is failures escaping the existing window loop. Do not claim this
catches native process termination, stack overflow, power loss, forced kill,
startup before that boundary, or an unrecovered GPU hang. Do not claim a
report identifies the driver's initiating fault or fixes a crash.
## Verification / stop rules
No build or test beside either graphical client. Lead rechecked both clients
and debugger absent and ACE UDP9000 PID13340 present before this contract.
Implementer must recheck before running Release build/focused tests.
Tests must exercise actual writer output: exact Vulkan fields and nested
aggregate handling; absent world/GPU; non-finite coordinates; Unicode/large
input/node/byte bounds; no message/Data/source-path leakage; unique reports
and no overwrite; no final partial JSON/temp leftovers on normal success;
failed context capture, inaccessible/invalid write destination and failing
notification do not throw or change the original exception. Use isolated
temporary directories, never the user's actual diagnostics directory.
Extend compiled-call coverage to prove the real `Run` catch latches, reports,
then rethrows, retaining existing crash-status tests. Lead independently checks
production inputs and failure safety, and runs focused and integrated Release
lanes. Independent review is limited to production wiring, correctness,
privacy and failure containment; no new full renderer review.
No deliberate graphical crash or driver reset. A short ordinary graphical
smoke may follow green Release/review to prove unchanged startup/world/close;
record exact binary and PNG verdict provisionally and close gracefully. Writer
behavior is proved by controlled exceptions in tests, not by harming the GPU.
Stop on a required extra production file, contract ambiguity, a second red
build, or a third fix round. Preserve raw failures and return bounded findings
to the lead; do not weaken assertions or widen scope to force a PASS.

View file

@ -0,0 +1,195 @@
# #477 — automatic local report / Windows evidence
2026-09-05. **Reporting PASS; focused19/19, narrow review and integrated17044/0/0
PASS. Corrected ordinary smoke PASS/provisional, exit0/graceful; clients closed.**
Bounded contract: `477-local-crash-report-contract.md`; source base `9baef7c50`.
This is an observability change, not a claim that AMD device loss is fixed.
## What the old logs establish
Lead re-read the exact local Application event window 16:4116:42 +02:
| Local time | Event / record | Observation |
|---|---|---|
| 16:41:20.1277754 | .NET Runtime 1026 / 713399 | AcDream.App unhandled VulkanCallException; `vkQueueSubmit2 (abandoned frame timeline signal)` / ErrorDeviceLost. |
| 16:41:28.4400641 | WER 1001 / 713400 | LiveKernelEvent141, initially empty bucket. |
| 16:41:57.2733346 | WER 1001 / 713401 | Same report, bucket `LKD_0x141_Tdr:C_AppFault_IMAGE_amdkmdag.sys-PF:1-HWS:1-SCH-PS:95d63b9d-CS:6708cc79-GS:62c333ab`. |
Both WER records name report `b3e69f70-71fc-418c-b9e8-686a6bdaf552` and the
same five problem-signature values; do not count them as two independent
resets. Exact candidate/process context remains in `477-cathedral-recurrence.md`.
This predates first-native-failure preservation at `f4da814af`.
0x141 identifies a display engine failing to respond in time, not a proven
client/driver initiating cause. [Microsoft reference](https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/bug-check-0x141---video-engine-timeout-detected).
The event names `C:/WINDOWS/LiveKernelReports/WATCHDOG/WATCHDOG-20260905-1641.dmp`.
Both that dump and exact WER archive folder
`C:/ProgramData/Microsoft/Windows/WER/ReportArchive/Kernel_141_475b65fe8bdf2cfdc1be595d354ddee35b4fd0dd_00000000_cab_b3e69f70-71fc-418c-b9e8-686a6bdaf552`
deny read access even through elevated tool calls. They were not analyzed;
no ACL, ownership, driver or TDR change was attempted.
Scoped AMD checks found `C:/Users/erikn/AppData/Local/AMD/CN` and
`Radeonsoftware`. CN's September4 RSX_Common log is empty; its other observed
log is from August. CN/GameReport has one observed file, none dated the crash
day; CN/Temp is empty; Radeonsoftware's observed cache/images are older.
No relevant readable AMD cause report was found in those bounded checks.
This is not a claim that no other report exists anywhere on the machine.
No settings/database contents, arbitrary shader caches, or remote submissions
were needed. Future app reporting remains independent of AMD tooling.
## Existing seams independently checked by the lead
- Run's existing exception catch latches before rethrow; ReportExited already
distinguishes crashed, graceful and shutdown-incomplete. Constructor/startup
before the Run boundary and native hard failures remain outside this chunk.
- Canonical ApplicationPathSet owns diagnostics paths; the launcher already
drains bounded stderr. No new directory resolver or log subsystem is needed.
- Vulkan Capabilities is a stored managed record; width/height read the stored
swapchain configuration and SampleCount is cached. No native call is required.
- PlayerMovementController.CellPosition borrows the physics body's current
cell/local origin. It is not the camera/render position; its orientation is
not a current body-facing witness and is deliberately not projected here.
- No AC-specific algorithm, rendering decision, membership, collision, wire,
teardown or exception-recovery policy is being changed. Register disposition:
no new retail deviation; no row/count churn for host-only local diagnostics.
## Frozen implementation and focused proof
Two production files only: the focused App writer and a 34-line Run hook/
projection in GameWindow. Report construction caps eight exception nodes,
bounded method metadata strings and a final256KiB serialized payload, with
explicit truncation/non-finite/unavailable markers. Bare original rethrow and
the existing crash-status vocabulary are unchanged. The root separately
checked the actual borrowed Vulkan context's publication/getters and controller
position source. No new native call or normal-frame work was introduced.
Final source SHA256:
| File | SHA256 |
|---|---|
| App/Diagnostics/LocalCrashReportWriter.cs | `5EF0E66C48D1376A50694278C792CB6587A94333233AFF9BA5BC8012E87F5440` |
| App/Rendering/GameWindow.cs | `2AA765C5F2566CC81423E56364DD95B0B8C8B705D8E7A4E26A1C07AC2133BB48` |
| App.Tests/Diagnostics/LocalCrashReportWriterTests.cs | `829D595DDEB9DAB6796C653367BF65AD12E4C801A6FD6FCFB4626C6D9775EC5A` |
| App.Tests/Rendering/GameWindowCrashStatusTests.cs | `45AF6B9CF2D6332B88A8909B5A8AA3A54A15B4D631C305BD0148FD67AC50F5A1` |
Evidence under `artifacts/477-local-crash-report/`, independently read/hashed
by the lead:
- `release-build-1.log`: FAIL, two CS0619 errors in new tests. Always-throwing
lambdas selected xUnit's obsolete Func<Task> overload. This was not a
production compile or runtime failure. SHA256
`FB134920D8D062F112766F9FD8F1ECEFF8B396F956150C97EBB0781EDA219CD5`.
- Fix round1 adds explicit synchronous Action casts at those two tests;
production and assertions unchanged. `release-build-2.log`: PASS,0W0E.
SHA256 `3436CC7D5F4461A8A5D3A5D6177EF9EC13233E2E1B86C1D352C51201D138025B`.
- `focused-1.trx`:19executed/19passed/0failed/0skipped. SHA256
`9883E8BE70121AAF189252105F64C6DEF937EE753AB4AD6DC2C36468B7FEAF54`.
Tests write/parse real isolated JSON files, retain exact Vulkan fields and
loaded assembly identity, and cover absent/failed context, non-finite positions,
nested/wide/deep exception trees, Unicode limits, non-overwriting output,
blocked/invalid paths, failed notification and original-exception identity.
Privacy cases include injected remote-stack text and poisoned virtual
Message/StackTrace/Data/ToString accessors. Production IL assertions prove the
real catch's latch/report/guard/rethrow ordering and single cached-owner reads.
Temporary test directories are removed by the tests; no fake report was put
in the user's diagnostics folder and no deliberate graphical crash was induced.
Independent OpenAI production/privacy review found no issues. Narrow closure
independently checked the two Action casts, original failure, green build,
19/19 TRX and final hashes; PASS, no further fix round. No production change
followed review. Lead separately parsed the same evidence and verified scope.
## Lead integrated Release
`artifacts/477-local-crash-integrated-20260905/`, exec86511 completed exit0.
Locked restore and solution Release build0W0E;14test assemblies,
17044executed/passed,0failed/0skipped. All19report/wiring tests are present
and PASS in the full App TRX. Lead rehashed32/32manifest entries.
Summary SHA256 `271261E59D04D53646CA4191DC0DFD2C22999C545D4D10ECA5FC5E673BC802EB`.
Exact source identity is parent9baef7c50782069f19d7a55d74712559979debc3,
WorktreeDirty=true with the frozen four-file patch and lead docs. This lane
uses the repository's documented DEFAULT filter, not the previous chunk's
broader custom filter: it also excludes Windows/Vulkan/SystemFont lanes,
Purpose=Diagnostic and Status=KnownFailure. Thus its17044count is not directly
comparable to the previous17051headline; tests were not removed or weakened.
The precise filter and all per-assembly totals remain in the summary.
Exact Release DLL SHA256 for both graphical attempts below:
- App `4CE893E4F69A4E67C2D77FA499D551B1603B94E8C8F5E766D66B87BBA149FC8D`.
- Core `4FA128BA1BF1366D3AF6F008289EDCFD07F1D0B94B855321BDA37FEC27C79338`.
## Ordinary smoke: retain the failed launch, not just the retry
First attempt `logs/selfgate-20260905-194419-477-local-crash-smoke/`, PID30360:
FAIL before login, zero PNGs, original unhandled InvalidDataException / exit
-532462766. Lead accidentally omitted the explicit recipe10 Session argument,
so the runner's older default selected acdream.pak recipe8; the current
client correctly requires10. This is a launch-configuration error, not an
AMD recurrence or a production reporting bug. No forced close was necessary;
lead confirmed the process absent before retry. Runner script exit0 is NOT
the client's success: the client's nonzero exit above is authoritative.
The new Run hook actually wrote this report before original-error propagation:
`C:/Users/erikn/AppData/Local/acdream/cache/diagnostics/crash-20260905T1744207523446Z-30360-29bc68a5d79b42f99f9bf651a7240df4.json`.
Lead opened and parsed it: original InvalidDataException type/HResult/method
stack, PID30360, loaded informational version1.0.0+9baef7c5..., MVID
cead7ceb-6a91-4b92-9199-e21d3ea354ea, cached RX9070XT/driver2.0.395/
Vulkan1.4.349/1280x720/4x; world null because login never occurred, Vulkan
failure fields null because this was not a Vulkan error. No arbitrary message,
package/source path or session data is serialized. Report SHA256
`B55868781EB8A19719EC3FD94177F21F7E7539521C55F83FEAF60EA5935FB3F0`.
The original detailed error remains in stderr. This supplies an unplanned
real Run-boundary reporting witness, not a GPU-crash reproduction.
Retry `logs/selfgate-20260905-194522-477-local-crash-smoke-v10/`, exec92566,
uses the exact prior successful `logs/s5-c5-session-v10.json` / existing
acdream-v10-s5-c5.pak. Source and binaries unchanged, no rebuild. Clients/
debugger absent and ACE9000 PID13340 up before each launch. Existing
`route-482-controls.txt` SHA256
`21506C621979A43A604F973C31149B9D192C66A7965782315ADD3B7011CA3AF5`;
two settled cathedral/Holtburg checkpoints/PNGs followed by graceful close.
Completed exit0/gracefully; script complete plus logout requested/confirmed.
At19:47+02 both clients/debugger absent; ACE9000 PID13340 remained up. Both
checkpoints are completed/visible, wait cue=false, invariant failures=0. No
device-loss/unhandled/script-failure/timeout/report notification matched the
corrected run's stdout/stderr. System/Application events19:45:2219:47:00
contain zero matching GPU/crash reports. This short smoke is not another
stability clearance or a full lifecycle/reconnect gate.
Lead opened both PNGs under the corrected run's `artifacts/screenshots/`:
| PNG | Narrow verdict / limitation | SHA256 |
|---|---|---|
| `482-control-cathedral-settled.png` | PASS/provisional: visible textured floor, arch/walls, player and retained UI; no tunnel/wait caption. Open exterior views do not establish a fix for the known retail cathedral leak. | `96F76C741FD576D6AA92C7705FF6FA5F9CA66E34D32F0AD222759D26328E6568` |
| `482-control-holtburg-settled.png` | PASS/provisional for ground, house and nearby actor/world reveal. Close-camera player fading is visible; not an avatar-opacity or full-scene acceptance claim. | `AA59410A0192F90C97C0092A7A5EE4C4F583A8B2A44E7C6189BAAE62A5E9B33E` |
Corrected terminal artifacts SHA256:
- client.log `42B2418028A9CD7E4EF5ABB335B0E2D76FE90AA3380F0D6AB95983B246880B1B`.
- client.err.log `DDEFD71E602AD4EE3AB410852A39C4140089377A0DD7BEA26E83F6BE13789FDF`.
- run.json `59B643AFAB36A716C2E171CFDC9FE511A29F0CA3A6A741A078DF69DE2276D146`.
- checkpoints JSONL `588EEEF8AB5CAEB85B5D8E5977A809E1E082C1014821B9A9C906956B3ED5823D`.
The real startup report is2072bytes and is preserved in the user's diagnostics
directory. Its production run includes the original exception's actual
method metadata and no world position fabricated before login. It demonstrates
real hook/file/error propagation in addition to the controlled tests, without
claiming that an actual Vulkan device-loss report has yet been captured by
this newly added reporter.
## Closeout and limitations
Controlled exceptions, actual compiled wiring and the retained real startup error establish reporting,
not an induced driver failure. Native hard hangs/termination and errors before Run
are outside coverage. G4, #477 initiating fault and AP159 stay open; FPS and
the known retail cathedral leak remain outside this chunk.
For a future recurrence, retain `crash-*.json` from the canonical diagnostics
directory and the existing launcher stderr, then correlate the JSON UTC/PID
with Windows Application/System events. The loaded MVID is the binary identity;
the informational-version commit alone is not proof of a clean source tree.
No automatic upload, report deletion, driver or Windows permission change is
part of this feature. Held C1a scratch diagnostic is unchanged/unbuilt/unrun.

View file

@ -5160,3 +5160,32 @@ paired indoor branch decoding proves both cheap-reject equalities admit in
retail, unlike our strict comparisons. AP-159 remainder3 records this existing
tie separately from remainder2; active row count unchanged. No scene occurrence
or production change inferred. No new graphical run belongs to C1a.
## 65. Owner-requested automatic local crash report — 2026-09-05
Implemented under `477-local-crash-report-contract.md`, base9baef7c50.
Four scoped App source/test files, OpenAI implementation and independent
review limited to production wiring, correctness, privacy and
failure containment. No new renderer review or AC-specific algorithm change.
One report after Run latches its original failure, before rethrow: loaded
build, original error/Vulkan result, cached GPU/driver and current cell/local
position. No per-frame work, fresh GPU query, upload or recovery. Existing
launcher stderr and status vocabulary stay unchanged. Reporting PASS:
focused19/19, narrow review closed, default Release0W0E/17044pass/0fail/0skip,
manifest32/32. One test-overload compilation correction, no production fixes
after review. This is not an AMD-cause repair claim.
Lead's exact old Windows checks and access limits are in
`477-local-crash-report-verification.md`: .NET1026/713399 and two WER1001
records713400/713401, the same report ID, LiveKernelEvent141/amdkmdag.sys.
Dump and WER archive deny read; scoped readable AMD folders do not explain
the cause. No settings, ACL, driver change or submission. Clients/debugger
absent, ACE9000 PID13340 up before implementation; recheck before execution.
The first smoke accidentally used the older default recipe8package and failed
before login; it saved a real2072-byte report then preserved the original
exception/nonzero exit. Evidence is retained, not counted as graphical PASS.
Corrected existing recipe10session smoke exit0/graceful, two PNGs opened narrow
PASS/provisional, no new GPU/crash events; clients/debugger closed, ACE up.
Frozen source/binary/report/PNG hashes and exact default-vs-prior custom test
filter distinction are in the verification note. C1a scratch diagnostic remains
held/unbuilt/unrun. Known retail leak, FPS deferral and G4 status unchanged.