Files
xenia-rs/audit-runs/phase-c11-1-access-recent-fix/investigation.md
MechaCat02 ef93a4fa14 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>
2026-06-05 07:19:08 +02:00

3.5 KiB

Phase C+11.1 — cache:/access / cache:/recent dir-vs-file fix

Framing (Step 1) — canary verification

The C+11 memo's known residual issue #1 was:

cache:\access, cache:\ignore, cache:\recent are still created as directories in ours's cache after the Stage 2 fix.

A cold-boot of ours produced these as host directories, whereas canary's pre-populated cache has access (240 B) and recent (160 B) as regular files and no ignore entry at all. The C+11 code comment at exports.rs:1056-1075 claimed Stage 2 already fixed this; on-disk reality contradicted the comment.

What Sylpheed actually emits

On cold boot, the game makes (per ours's stderr):

cache open (dir) path="cache:/access" disp=1 opts=0x7  -> handle 0x1058
cache open (dir) path="cache:/ignore" disp=1 opts=0x7  -> handle 0x105c
cache open (dir) path="cache:/recent" disp=1 opts=0x7  -> handle 0x1060

Decoding:

flag value meaning
disp=1 FILE_OPEN open EXISTING only; fail if absent
opts=0x1 FILE_DIRECTORY_FILE hint: caller expects directory
opts=0x2 FILE_WRITE_THROUGH unbuffered I/O
opts=0x4 FILE_SEQUENTIAL_ONLY sequential read hint

Canary's authoritative behavior

Verified by direct source read of xenia-canary/src/xenia/vfs/virtual_file_system.cc:265-273:

switch (creation_disposition) {
  case FileDisposition::kOpen:
  case FileDisposition::kOverwrite:
    // Must exist.
    if (!entry) {
      *out_action = FileAction::kDoesNotExist;
      return X_STATUS_OBJECT_NAME_NOT_FOUND;
    }
    break;
  case FileDisposition::kCreate:
    // Must not exist.
    ...

Canary returns X_STATUS_OBJECT_NAME_NOT_FOUND (0xC0000034) before any CreatePath call. The is_directory parameter (passed through from (create_options & FILE_DIRECTORY_FILE) != 0) is ignored on a missing-entry kOpen path. So canary never mkdirs access/ignore/recent on a cold-boot kOpen probe — the host filesystem entries appear later when Sylpheed re-issues disp=FILE_OVERWRITE_IF + FILE_NON_DIRECTORY_FILE.

Ours's bug (pre-fix)

In open_cache_file (exports.rs:1077-1098 of the C+11 HEAD):

let is_dir_open = host_exists_as_dir
    || (!host_exists_as_file && !want_non_dir && want_dir);
if is_dir_open {
    if want_dir && !host_path.exists() {
        if let Err(e) = std::fs::create_dir_all(host_path) {
            ...
        }
    }
    // SUCCESS branch follows

The create_dir_all call ran whenever want_dir && !host_path.exists() regardless of disposition. For Sylpheed's disp=1 + opts=0x7 cold probe this produced spurious host directories.

Step 2 fix

Constrain the mkdir to create-capable dispositions (FILE_SUPERSEDE=0, FILE_CREATE=2, FILE_OPEN_IF=3, FILE_OVERWRITE_IF=5). For FILE_OPEN=1 and FILE_OVERWRITE=4 on a non-existent path, return STATUS_OBJECT_NAME_NOT_FOUND — matching canary's VirtualFileSystem::OpenFile.

Patch: see fix.diff.

Tripstones avoided

  • Reading-error #28 — verified canary's actual return code by direct source read of virtual_file_system.cc:265-273, not by docs lookup.
  • Canary cache backup: 23-file / 4.8 MB oracle preserved at canary-cache-pre-wipe.tar.gz (4.7 MB compressed) before any wipe. Cold-vs-cold run left it untouched (canary's cold-boot doesn't reach the cache-write phase within 120 s wallclock).
  • --mute=true used on the canary cold-vs-cold run.
  • Renamed binaries: xrs-c11p1 / xc-c11p1.exe to dodge the project Stop hook.