handoff: VSync/event-wedge fixes + iterate 2.A–2.BC research notes

Source changes (dormant parity infra, retained from iterate 2.AI/2.AO):
- xenia-kernel/exports.rs: nt_create_event manual_reset polarity +
  related event wiring
- xenia-gpu/mmio_region.rs: D1MODE_VBLANK_VLINE_STATUS hardcode parity

Also lands the audit-runs/ analysis notes (.md/.txt/.json digests) for the
iterate 2.x VSync/0x10e8/0x1004 wedge investigation. Raw trace dumps
(.jsonl/.gz/.csv/.stdout) and agent worktrees (.claude/) are gitignored as
regenerable local artifacts — see memory + HANDOFF for the running findings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-05 07:19:08 +02:00
parent acd1656753
commit ef93a4fa14
620 changed files with 108303 additions and 1 deletions

View File

@@ -0,0 +1,110 @@
# Phase C+13 investigation — NtCreateFile `game:\dat\files.tbl`
## Predecessor state (from Phase C+12)
Cold-vs-cold first divergence at `tid_event_idx=103862` on the
canary tid=6 → ours tid=1 main chain:
```
canary: NtCreateFile(path="game:\\dat\\files.tbl") -> 0xc0000034 (STATUS_OBJECT_NAME_NOT_FOUND)
ours: NtCreateFile(path="game:\\dat\\files.tbl") -> 0x00000000 (STATUS_SUCCESS, synth-empty stub)
```
Verified from the C+12 archived JSONLs:
- `xenia-rs/audit-runs/phase-c12-NtQueryFullAttributesFile/ours-cold.jsonl`
events 103860-103862: import.call → kernel.call → kernel.return = 0.
- `xenia-rs/audit-runs/phase-c12-NtQueryFullAttributesFile/canary-cold-tid6-250k.jsonl`
events 103860-103862: same import.call → kernel.call → kernel.return = `0xc0000034`.
Canary's next event (idx 103863): `RtlNtStatusToDosError(0xc0000034) -> 2`
(ERROR_FILE_NOT_FOUND). Sylpheed has proper NOT_FOUND handling and
continues; the synth-empty was masking the correct branch.
## Canary semantics (xboxkrnl_io.cc:39-111)
`NtCreateFile_entry` is a thin wrapper:
1. Translate the `OBJECT_ATTRIBUTES.name` ANSI_STRING to a path.
2. Path validation: `IsValidPath``STATUS_OBJECT_NAME_INVALID` on
reject.
3. Resolve the `root_directory` handle if non-zero.
4. Call `kernel_state()->file_system()->OpenFile(...)`.
5. **Return the `OpenFile` status verbatim.** If the file isn't
present in any mounted device, that's `STATUS_OBJECT_NAME_NOT_FOUND`
(`VirtualFileSystem::ResolvePath` returns nullptr for unresolved
paths, which `OpenFile` translates to NOT_FOUND).
6. On success: alloc handle and write to `handle_out`.
For `game:\dat\files.tbl` specifically: the `game:` symlink points
to the disc device. The disc image ISO doesn't contain `dat/files.tbl`
(disc dump omission — likely a region-specific or build-time-only
file). `OpenFile` returns NOT_FOUND, the function returns NOT_FOUND.
## Ours pre-fix (exports.rs `open_vfs_file`)
`open_vfs_file` had three paths:
1. **Empty path** (root-of-device) → synth-empty handle (special-cased
for `NtCreateFile("game:\")` disc-validation probe).
2. **`cache:` prefix** → host-FS-backed via `open_cache_file` (AUDIT-038).
3. **Otherwise**: call `vfs.read_file(&path)`. On `Ok(bytes)` return
handle. **On `Err(e)`: synth a zero-byte file and return SUCCESS.**
The (3) `Err` branch was the synth-empty fallback. Its docstring
acknowledged two rationales:
> 1. Writable system partitions (`cache:`, `partition0:`, `partition1:`)
> aren't backed by the disc — we synth so opens succeed.
> 2. Disc files that didn't make it into the ISO rip — returning
> NOT_FOUND would make Sylpheed's boot validator call
> `XamShowDirtyDiscErrorUI` → dashboard exit.
But rationale (1) was already covered by the AUDIT-038 `cache:`
short-circuit, and rationale (2) is empirically WRONG: canary returns
NOT_FOUND for `game:\dat\files.tbl` and Sylpheed continues — there
is no `XamShowDirtyDiscErrorUI` event in canary's trace anywhere
near idx 103862. The synth-empty was misleading the boot trajectory
in a non-canary direction.
## Fix shape
Two surgical changes:
1. New helper `is_disc_prefix(raw_path: &str) -> bool` that
recognises `game:\`, `d:\`/`D:\`, and `\Device\Cdrom0\` (the
subset of `path::DEVICE_PREFIXES` that resolves to the read-only
disc).
2. `open_vfs_file` captures the raw path via
`crate::path::object_attributes_raw_name` (alongside the existing
normalised `path`). In the `Err(e)` arm, if `is_disc_prefix(&raw_path)`,
return `STATUS_OBJECT_NAME_NOT_FOUND` (mirroring canary). Otherwise
keep the legacy synth-empty.
The `cache:` short-circuit is upstream of this branch and unchanged;
its existing NOT_FOUND-on-FILE_OPEN-miss behaviour is preserved.
## Tests
- `is_disc_prefix_recognises_disc_aliases` (12 assertions, mix of
disc and non-disc prefixes including case variants).
- `nt_create_file_game_prefix_missing_returns_not_found` — primary.
- `nt_create_file_cdrom_prefix_missing_returns_not_found`
`\Device\Cdrom0\` alias.
- `nt_create_file_non_disc_prefix_missing_still_synthesizes`
regression guard for `\Device\Harddisk0\Partition1\sys.bin`.
## Risk analysis (pre-fix)
- **Sylpheed might crash on NOT_FOUND**: REFUTED. Canary returns
NOT_FOUND, then Sylpheed calls `RtlNtStatusToDosError` and
continues; +712 events of further progress observed in cold-vs-cold
(no fault, no `XamShowDirtyDiscErrorUI`).
- **Reading-error #23 regression**: matched-prefix MUST grow, not
shrink. Cold-vs-cold confirms 103862 → 104574 (+712); no regression.
- **Sister-chain regression**: confirmed unchanged for all 5 sister
chains (see `cold-vs-cold-result.md`).
- **Stable digest shift**: expected; the synth-empty was emitting
one extra kernel.call/return pair (synth-success path); removing
it for disc paths drops `imports` 40447 → 40390 (-57). Phase B
`image_loaded_sha256` is unchanged.