Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 26 additions & 19 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- **A headless ares host: builds and links, does not yet run — and that changes the status of the
project's most-cited blocker.** Several findings are stuck at 2-versus-1 with no tiebreaker,
because this project's provenance rule counts ares and bsnes as *one* reference: `A2.10`, and the
OBJ-interlace field parity where RustySNES's `row + field` and `$213F` bit 7 are both ares'. Both
name the same missing thing — ares actually running the cart — and the recorded reason it had not
happened was that ares would have to be built and had no headless mode.

The first half of that is now known to be cheap. `-DARES_CORES=sfc` configures and builds the SFC
core standalone in a couple of minutes; `ares::Platform` is a small interface whose methods all
have no-op defaults, so a headless host is ~120 lines; it links; and the cart's results block is
reachable by construction at `ares::SuperFamicom::cpu.wram[0xF000 + n]`. What remains is a crash
during setup — a bounded debugging job, not a feasibility question.

Committed under `scripts/accuracysnes/ares_host/` **labelled as incomplete**, with the build
recipe and the two traps that each cost a round: `hiro` is not optional for a headless host
(`mia/mia.hpp` includes it and its generated resource header does not exist until hiro is built
once), and `nall/main.hpp` must be included by the host translation unit or the link fails with a
bare `undefined reference to 'main'` out of `crt1.o`. The README names the three likely causes of
the crash in the order worth trying.
- **A headless ares host — and `A2.10` is settled.** Several findings sat at 2-versus-1 with no
tiebreaker, because this project's provenance rule counts ares and bsnes as *one* reference, so
"RustySNES and snes9x against Mesen2" is only 2-vs-1 if ares is not already on RustySNES's side —
and nobody could check. `scripts/accuracysnes/ares_host/` now can:

```
magic ACSN / done a5 / count 338 / passed 299 / failed 5 / skipped 1 / golden 33
```

Reproducible across runs. **ares passes `A2.10`** ("PEI does not page-wrap", catalogue index 11,
status `$01`), so with RustySNES and snes9x it is **3 against 1 with Mesen2 the outlier**, and the
row comes off the "unexplained, needs a fourth opinion" list it has been on since the oracle fix.

**Five rows where ares disagrees with the cart** — `C7.05`, `C7.10`, `E8.02`, `E3.06`, `F1.10` —
are recorded and **not** adjudicated. `F1.10` is suspect-of-the-host first: it is a
`PAD2_CONTRACT` row and this host's port detection is assumed rather than verified. Not wired into
`crossval.sh` for that reason — an `ARES_KNOWN_FAILURES` constant encoding unexamined
disagreements would be worse than no third reference.

Three setup steps turned out to be mandatory and each cost a round, because all three fail as the
*same* segfault inside `System::load` with a backtrace pointing at memory setup rather than at
what is missing: `ares::Memory::FixedAllocator::get()` before anything touches a core (`Bus::reset`
allocates from it); `ares::SuperFamicom::option("Pixel Accuracy", "true")` before `load`
(`PPUBase::implementation` is null until `setAccurate` picks a PPU, and `Bus::reset` calls
`ppu.map()`); and `nall/main.hpp` included by the host translation unit, without which the *link*
fails with a bare `undefined reference to 'main'` from `crt1.o`.

- **The H-IRQ comparator moves into the clock domain (`T-06-A`), and nothing below the long dots
moves with it.** `HIRQ_TRIGGER_DELAY = 4` was a *dot-domain rounding* of ares'
Expand Down
28 changes: 17 additions & 11 deletions docs/accuracysnes-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -843,7 +843,7 @@ before writing the row.
disagreement in the battery, and a row batch authored while one row is unexplained risks attributing
a new failure to the wrong cause.

#### First arbitrated finding: `A2.10` — Mesen2 is the outlier, 2-vs-1
#### First arbitrated finding: `A2.10` — Mesen2 is the outlier, and ares makes it 3-vs-1

The moment the oracle started working it produced a result the project has never been able to see.
`crossval.sh` reports `Mesen2: 1 failing test(s)`, catalogue index **11 = cart `A2.10`, "PEI does not
Expand All @@ -855,16 +855,22 @@ divergences. So this is **2-vs-1 with Mesen2 as the outlier**, which inverts the
standing heuristic warns that *RustySNES failing alone* means a real bug, and that is not what this
is.

**Do not yet record it as a known-good Mesen2 divergence.** That would need a third opinion — ares or
bsnes on the same row — and the heuristic's own caveat applies with force here: a harness bug
upstream of an implementation produces exactly this signature, and one already did once in this
project (the `$F8`/`$F9` retraction). The cheap next step is to run the row against ares or bsnes. If
they agree with RustySNES and snes9x, add a `MESEN2_KNOWN_FAILURES` entry mirroring the snes9x one,
with the rationale, so `crossval.sh` reports AGREEMENT instead of `DISAGREEMENT` and stops masking
future real divergences behind a known one.

Until then `crossval.sh`'s `DISAGREEMENT` verdict is **correct and should stay** — one genuinely
unexplained row is exactly what it exists to report.
**SETTLED 2026-08-01 — ares passes it, so this is 3-vs-1.** The third opinion the paragraph below
asked for now exists: `scripts/accuracysnes/ares_host/` runs the battery under ares
(`magic ACSN`, `done a5`, 338 tests, reproducible across runs) and reports catalogue index 11 as
status `$01` — **pass**. With RustySNES and snes9x that is three implementations against Mesen2, and
the caveat that held this open — a harness bug upstream of an implementation produces the same
signature, as the `$F8`/`$F9` retraction proved — is answered: the fourth host is independent of all
three and agrees.

`A2.10` can therefore become a `MESEN2_KNOWN_FAILURES` entry whenever the count is next revised. It
is not being added in the same change that established it, because `MESEN2_KNOWN_FAILURES` currently
reads 2 and matches; changing the constant and the reasoning at once would leave nothing to check
the change against.

*The original reasoning, kept because it is why the row waited:* recording it as a known-good Mesen2
divergence without a third opinion would have been exactly the mistake the `$F8`/`$F9` retraction
already cost this project once.

#### The Mesen oracle — the "14 of 335" reading does not reproduce

Expand Down
118 changes: 68 additions & 50 deletions scripts/accuracysnes/ares_host/README.md
Original file line number Diff line number Diff line change
@@ -1,66 +1,84 @@
# A headless ares host for AccuracySNES — builds and links, does **not** yet run
# A headless ares host for AccuracySNES — the third opinion

**Status: incomplete infrastructure, committed deliberately.** `build.sh` produces a linked binary;
running it against the cart dumps core during setup. What is finished is the part that was recorded
as the blocker, and what remains is a bounded debugging job rather than a feasibility question.
**Working.** It runs the battery and reports the results block:

## Why this exists
```
$ REF_PROJ=$PWD/ref-proj bash scripts/accuracysnes/ares_host/build.sh
$ /tmp/ares_host tests/roms/AccuracySNES/build/accuracysnes.sfc 900
ACCURACYSNES-BEGIN
magic ACSN
done a5
count 338
passed 299
failed 5
skipped 1
golden 33
status 0 01
...
```
Comment on lines +5 to +18

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add language identifiers to both changed Markdown fences.

Both fences omit a language identifier and can fail the active MD040 check.

  • scripts/accuracysnes/ares_host/README.md#L5-L18: change the opening fence to ```text or ```console.
  • CHANGELOG.md#L19-L21: change the opening fence to ```text or ```console.

As per path instructions, these Markdown files must follow the pinned markdownlint rules.

🧰 Tools
🪛 markdownlint-cli2 (0.23.1)

[warning] 5-5: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

📍 Affects 2 files
  • scripts/accuracysnes/ares_host/README.md#L5-L18 (this comment)
  • CHANGELOG.md#L19-L21
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/accuracysnes/ares_host/README.md` around lines 5 - 18, Add language
identifiers to both changed Markdown fences: update the fence in
scripts/accuracysnes/ares_host/README.md lines 5-18 and the fence in
CHANGELOG.md lines 19-21 to use text or console, preserving their existing
contents.

Sources: Path instructions, Linters/SAST tools


Several findings are stuck at **2 versus 1** with no way to break the tie, because this project's
own provenance rule counts ares and bsnes as **one** reference:
Reproducible: two runs give the identical tally.

- **`A2.10`** ("PEI does not page-wrap") — Mesen2 fails it, RustySNES and snes9x pass. Recorded as
*not* settled precisely because a harness bug upstream of every implementation produces the same
signature, and one did once (the `$F8`/`$F9` retraction).
- **OBJ/screen interlace field parity** — RustySNES draws one field's source rows, snes9x and
Mesen2 the other. RustySNES's `row + field` and its `$213F` bit 7 are both *ares'*, so this is the
bsnes/ares lineage against the other two rather than RustySNES alone.
## Why it exists

Both name the same missing thing: **ares actually running the cart.** The recorded reason it had not
happened was that ares would have to be built and had no headless mode. The first half of that is
now known to be cheap; only the second half is real.
Several findings sat at **2 versus 1** with no way to break the tie, because this project's
provenance rule counts ares and bsnes as **one** reference — so "RustySNES and snes9x against
Mesen2" is only 2-vs-1 if ares is not already on RustySNES's side, and nobody could check.

## What is established
**The first thing it settled: `A2.10` ("PEI does not page-wrap").** ares **passes** it (catalogue
index 11, status `$01`). With RustySNES and snes9x also passing, that is **3 against 1 with Mesen2
the outlier**, and the row comes off the "unexplained, needs a fourth opinion" list.

| | |
|---|---|
| ares' SFC core builds standalone | **yes** — `-DARES_CORES=sfc`, ~76 targets, a couple of minutes |
| a headless host compiles against it | **yes** — `ares::Platform` is a small interface whose methods all have no-op defaults |
| it links | **yes** — see `build.sh` for the library set and the two non-obvious traps |
| the results block is reachable | **yes, by construction** — `ares::SuperFamicom::cpu.wram[0xF000 + n]` is the cart's `$7E:F000` |
| it runs the cart | **no** — dumps core during setup |
## The five rows ares disagrees with the cart about

Two traps `build.sh` records because each cost a round:
New information, and **not yet adjudicated** — the cart, snes9x and Mesen2 all pass these:

- **`hiro` is not optional for a headless host.** `mia/mia.hpp` includes it, and its generated
`resource/resource.hpp` does not exist until hiro has been built once.
- **`nall/main.hpp` must be included by the host translation unit.** It emits `::main` only when
`NALL_MAIN_IMPL` is undefined, and nall's own `main.cpp.o` defines that. Omit the include and the
link fails with a bare `undefined reference to 'main'` out of `crt1.o`, which reads like a missing
object file rather than a missing shim.
| idx | row | code |
|---:|---|---:|
| 116 | `C7.05` | 1 |
| 120 | `C7.10` | 1 |
| 265 | `E8.02` | 3 |
| 276 | `E3.06` | 2 |
| 288 | `F1.10` | 2 |

## What is left
**Treat `F1.10` as suspect-of-this-host first.** It is a `PAD2_CONTRACT` row, and this host's port
detection (`port->name().find("2")` on the button's grandparent) is *assumed* to work, not verified.
Check that before concluding anything about ares.
Comment on lines +44 to +46

The crash is in setup, before any frame runs. The likely candidates, in order:
The standing heuristic — three implementations agreeing usually means a broken test, one disagreeing
usually means a real bug — cuts an unfamiliar way here: it is **ares** alone, on rows the other three
pass.

1. `mia::System::create("Super Famicom")->load()` needs a system pak that `mia` looks for under the
home location — `setHomeLocation` here points at `~/.local/share/ares/`, which may not exist.
desktop-ui populates it on first run.
2. The `Cartridge Slot` port is allocated with no argument; desktop-ui passes the medium and checks
the returned node.
3. Controller ports are allocated by name `"Gamepad"`; the actual node name should be confirmed
against `ares/sfc/controller/controller.cpp` rather than assumed.
## Three setup steps that are not optional, each of which cost a debugging round

Run it under a debugger and start at (1) — a null pak is the failure that would reach furthest
before dying.
All three fail as a **segfault inside `System::load`**, with a backtrace pointing at memory setup
rather than at what is actually missing.

## Usage, once it works
1. **`ares::Memory::FixedAllocator::get()` before anything touches a core.** `Bus::reset()`
allocates its page tables from that bump allocator. desktop-ui does this on its first line.
2. **`ares::SuperFamicom::option("Pixel Accuracy", "true")` before `load`.** `PPUBase::implementation`
is null until `setAccurate` picks one of the two PPUs, and `Bus::reset()` calls `ppu.map()` →
`implementation->map()`. `"true"` selects the **accurate** PPU, the only one worth
cross-validating against.
3. **`nall/main.hpp` included by this translation unit.** It emits `::main` only when
`NALL_MAIN_IMPL` is undefined, and nall's own `main.cpp.o` defines that. Omit it and the *link*
fails with a bare `undefined reference to 'main'` from `crt1.o`, which reads like a missing object
file rather than a missing shim.
Comment on lines +52 to +66

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Correct the repeated setup-failure description.

Both documents group the allocator, PPU, and nall/main.hpp omissions under the System::load segmentation-fault description. The third omission actually causes a link-time undefined reference to 'main'.

  • scripts/accuracysnes/ares_host/README.md#L52-L66: separate the allocator and PPU segmentation faults from the nall/main.hpp link failure.
  • CHANGELOG.md#L33-L39: apply the same separation to the changelog entry.

As per path instructions, documentation is the specification and must match the implementation and build recipe.

📍 Affects 2 files
  • scripts/accuracysnes/ares_host/README.md#L52-L66 (this comment)
  • CHANGELOG.md#L33-L39
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/accuracysnes/ares_host/README.md` around lines 52 - 66, Update
scripts/accuracysnes/ares_host/README.md lines 52-66 to separate the allocator
and PPU omissions, which cause System::load segmentation faults, from the
nall/main.hpp omission, which causes a link-time undefined reference to main.
Apply the same corrected separation to CHANGELOG.md lines 33-39; both
documentation sites must describe the implementation and build behavior
accurately.

Source: Path instructions


```bash
REF_PROJ=$PWD/ref-proj bash scripts/accuracysnes/ares_host/build.sh
/tmp/ares_host tests/roms/AccuracySNES/build/accuracysnes.sfc 900
```
Plus one build fact: **`hiro` is not optional even headless** — `mia/mia.hpp` includes it, and its
generated `resource/resource.hpp` does not exist until `ninja hiro` has run once.

`ptrace` is denied in this sandbox, so gdb cannot attach. The host installs its own `SIGSEGV`
handler and prints a backtrace; resolve the frames with `addr2line -Cfe /tmp/ares_host 0x…`.

## Results-block offsets

From `asm/runtime.inc`, and easy to get wrong by one field: `R_COUNT` is `+$06`, `R_PASSED` is
`+$0A`. Reading the latter as the former reported "count 299" for a 338-test battery, which looks
like a truncated run rather than a misread field.

## Not yet wired into `crossval.sh`

Output is the same `magic` / `done` / `count` / `status N XX` shape the snes9x libretro host emits,
so `crossval.sh` can consume it as a third reference with a `ARES_KNOWN_FAILURES` constant beside
the existing two.
Deliberately. Adding a third reference means an `ARES_KNOWN_FAILURES` constant, and that constant
must not be written until the five rows above are adjudicated — a known-failure count that encodes
unexamined disagreements is worse than no third reference at all.
49 changes: 48 additions & 1 deletion scripts/accuracysnes/ares_host/ares_host.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,25 @@
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <csignal>
#include <execinfo.h>
#include <unistd.h>

namespace {

// A crash here is a setup mistake in this file, not an ares bug, and this sandbox cannot run a
// debugger (`ptrace` is denied), so the backtrace has to come from inside the process. Build with
// `-rdynamic` — `build.sh` does — or the frames come back as bare addresses that `addr2line -Cfe`
// still resolves.
auto crashHandler(int sig) -> void {
void* frames[40];
int n = backtrace(frames, 40);
fprintf(stderr, "\nares_host: signal %d — backtrace follows\n", sig);
backtrace_symbols_fd(frames, n, 2);
_exit(9);
Comment on lines +22 to +27

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔵 Trivial | 🏗️ Heavy lift

Keep crashHandler safe in signal context.

crashHandler calls fprintf, backtrace, and backtrace_symbols_fd after SIGSEGV or SIGABRT. These calls are not guaranteed to be async-signal-safe. If the fault interrupts allocator, loader, or stdio code while a lock is held, the handler can hang or fault again instead of exiting with status 9.

Keep the handler limited to a fixed write and _exit path, or move symbolization to a parent process or post-mortem tool. Verify the design against the target POSIX and libc signal-safety contract.

🧰 Tools
🪛 Clang (14.0.6)

[warning] 23-23: do not declare C-style arrays, use std::array<> instead

(modernize-avoid-c-arrays)


[warning] 24-24: variable name 'n' is too short, expected at least 3 characters

(readability-identifier-length)


[warning] 25-25: do not call c-style vararg functions

(cppcoreguidelines-pro-type-vararg)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/accuracysnes/ares_host/ares_host.cpp` around lines 22 - 27, Update
crashHandler to use only async-signal-safe operations in the signal path: remove
fprintf, backtrace, and backtrace_symbols_fd, and emit only a fixed diagnostic
via write before preserving _exit(9). If backtrace symbolization is required,
move it to a parent process or post-mortem tool and verify the implementation
against the target POSIX/libc signal-safety contract.

}

} // namespace

namespace {

Expand Down Expand Up @@ -78,9 +97,17 @@ auto main(Arguments arguments) -> void {
printf("usage: ares_host <rom.sfc> <frames>\n");
exit(2);
}
bool verbose = (bool)arguments.take("--verbose");
string rom = arguments[0];
u32 budget = (u32)toNatural(arguments[1]);
Comment on lines +100 to 102

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- candidate files ---'
git ls-files 'scripts/accuracysnes/ares_host/*' | sed -n '1,120p'

printf '%s\n' '--- host source context ---'
host=$(git ls-files | grep -E '(^|/)scripts/accuracysnes/ares_host/ares_host\.cpp$' | head -n 1 || true)
if [ -n "$host" ]; then
  cat -n "$host" | sed -n '80,125p'
fi

printf '%s\n' '--- Arguments definitions and uses ---'
rg -n --glob '!snesdev_wiki/**' \
  'class Arguments|struct Arguments|Arguments::|arguments\.take|take\([^)]*--verbose|arguments\.size\(\)|operator\[\]' \
  . | sed -n '1,240p'

Repository: doublegate/RustySNES

Length of output: 2447


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- build script ---'
cat -n scripts/accuracysnes/ares_host/build.sh

printf '%s\n' '--- source header and dependency references ---'
cat -n scripts/accuracysnes/ares_host/ares_host.cpp | sed -n '1,45p'
rg -n --glob '!snesdev_wiki/**' \
  'ares|nall|Arguments|github.com|commit|checkout|submodule' \
  scripts/accuracysnes/ares_host README.md .github 2>/dev/null | sed -n '1,240p'

Repository: doublegate/RustySNES

Length of output: 22865


🌐 Web query:

Search the ares-emulator/ares GitHub source for the nall Arguments implementation, specifically Arguments::take, Arguments::size, and Arguments::operator[]semantics innall/string/arguments.hpp or equivalent.

💡 Result:

In the ares-emulator/ares source code, the nall::Arguments class (found in nall/nall/arguments.hpp) provides a wrapper around a std::vector to manage command-line arguments [1]. The semantics for the requested methods are as follows: size The size method returns the number of arguments currently held in the internal collection as a u32 type [1]: auto size const -> u32 { return arguments.size; } [1] operator[] The subscript operator provides access to the individual argument strings at a given index. It includes both mutable and const overloads, returning a reference to the string [1]: auto operator[](u32 index) -> string& { return arguments[index]; } [1] auto operator[](u32 index) const -> const string& { return arguments[index]; } [1] take The take method is used to consume and remove specific arguments from the internal collection, which is useful for parsing command-line flags and their values [1][2]. It is overloaded to support different search patterns: 1. take -> string: Removes and returns the first element in the arguments list. 2. take(string_view name) -> bool: Searches for an argument matching the given name; if found, it removes it from the list and returns true [1]. 3. take(string_view name, bool& argument) -> bool: Searches for a flag. If found, it removes it, sets the referenced bool to true, and returns true [1]. 4. take(string_view name, string& argument) -> bool: Searches for an argument by name. If found, it removes both the name and the subsequent value from the list, assigns the value to the referenced string, and returns true [1]. These methods facilitate typical CLI patterns, such as checking for flags (e.g., arguments.take("--fullscreen")) or extracting values associated with flags (e.g., arguments.take("--system", system)) [2].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu

url='https://raw.githubusercontent.com/ares-emulator/ares/6f6786e0/nall/nall/arguments.hpp'
src=$(curl --fail --silent --show-error --location "$url")

printf '%s\n' '--- relevant declarations and methods ---'
printf '%s\n' "$src" | rg -n -A14 -B5 \
  'class Arguments|auto size|auto take|operator\[\]|arguments\.remove|arguments\.size' | sed -n '1,260p'

printf '%s\n' '--- source length and commit URL ---'
printf 'lines=%s\n' "$(printf '%s\n' "$src" | wc -l)"
printf '%s\n' "$url"

Repository: doublegate/RustySNES

Length of output: 4256


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
args = ["--verbose", "rom.sfc"]
initial_size = len(args)

try:
    verbose = "--verbose" in args
    if verbose:
        args.remove("--verbose")
    final_size = len(args)
    print(f"input=ares_host --verbose rom.sfc")
    print(f"initial_size={initial_size}")
    print(f"take_verbose={verbose}")
    print(f"remaining={args}")
    print(f"final_size={final_size}")
    print(f"arity_check_before_take={'passes' if initial_size >= 2 else 'fails'}")
    print(f"arity_check_after_take={'passes' if final_size >= 2 else 'fails'}")
    if final_size < 2:
        raise IndexError("arguments[1] is out of bounds")
except IndexError as error:
    print(f"access_result=out_of_bounds: {error}")
PY

Repository: doublegate/RustySNES

Length of output: 379


Move the arity check after option removal. With ares_host --verbose <rom>, take("--verbose") reduces the argument count from two to one, so arguments[1] performs an out-of-bounds access.

🧰 Tools
🪛 Clang (14.0.6)

[warning] 101-101: variable 'rom' is not initialized

(cppcoreguidelines-init-variables)


[warning] 102-102: variable 'budget' is not initialized

(cppcoreguidelines-init-variables)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/accuracysnes/ares_host/ares_host.cpp` around lines 100 - 102, Move
the positional-argument arity validation to after `arguments.take("--verbose")`
in the argument parsing flow, before accessing `arguments[0]` or `arguments[1]`.
Ensure the check uses the post-option-removal count and rejects invocations
missing either the ROM or budget argument.


// MUST come before anything touches a core. `Bus::reset()` allocates its page tables from this
// bump allocator, and without the first `get()` to construct it the very first load segfaults
// inside `System::load` with no hint that memory setup was the problem. desktop-ui does the same
// thing on its first line for the same reason.
signal(SIGSEGV, crashHandler);
signal(SIGABRT, crashHandler);
ares::Memory::FixedAllocator::get();
ares::platform = &platform_;
mia::setHomeLocation([]() -> string { return {Path::userData(), "ares/"}; });

Expand All @@ -95,11 +122,21 @@ auto main(Arguments arguments) -> void {
exit(3);
}

if(verbose) fprintf(stderr, "ares_host: loaded the game and system paks\n");

// MUST come before `load`. `PPUBase::implementation` is null until `setAccurate` picks one of the
// two PPU implementations, and `Bus::reset()` calls `ppu.map()` -> `implementation->map()` during
// `System::load`. Without this the first load segfaults in `Bus::reset` with a backtrace that
// points at memory setup rather than at an unselected PPU. desktop-ui passes its own setting here.
// "true" selects the ACCURATE PPU, which is the only one worth cross-validating against.
ares::SuperFamicom::option("Pixel Accuracy", "true");

ares::Node::System root;
if(!ares::SuperFamicom::load(root, "[Nintendo] Super Famicom (NTSC)")) {
fprintf(stderr, "ares_host: ares::SuperFamicom::load failed\n");
exit(3);
}
if(verbose) fprintf(stderr, "ares_host: ares::SuperFamicom::load returned\n");
if(auto port = root->find<ares::Node::Port>("Cartridge Slot")) {
port->allocate();
port->connect();
Expand All @@ -110,18 +147,28 @@ auto main(Arguments arguments) -> void {
port->connect();
}
}
if(verbose) fprintf(stderr, "ares_host: ports connected\n");
root->power();

if(verbose) fprintf(stderr, "ares_host: powered on\n");
while(platform_.frames < budget) root->run();

// The results block, straight out of WRAM. $7E:F000 is WRAM offset $F000.
const auto& wram = ares::SuperFamicom::cpu.wram;
const u32 RESULTS = 0xF000;
// Offsets are `asm/runtime.inc`'s, and they are easy to get wrong by one field: R_COUNT is +$06
// and R_PASSED is +$0A. Reading the latter as the former reported "count 299" for a 338-test
// battery, which looks like a truncated run rather than a misread field.
auto rd16 = [&](u32 off) -> u32 { return wram[RESULTS + off] | (wram[RESULTS + off + 1] << 8); };
u32 count = rd16(0x06);
printf("ACCURACYSNES-BEGIN\n");
printf("magic %c%c%c%c\n", wram[RESULTS], wram[RESULTS + 1], wram[RESULTS + 2], wram[RESULTS + 3]);
printf("done %02x\n", wram[RESULTS + 0x08]);
u32 count = wram[RESULTS + 0x0A] | (wram[RESULTS + 0x0B] << 8);
printf("count %u\n", count);
printf("passed %u\n", rd16(0x0A));
printf("failed %u\n", rd16(0x0C));
printf("skipped %u\n", rd16(0x0E));
printf("golden %u\n", rd16(0x10));
Comment on lines 156 to +171

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- target outline ---'
ast-grep outline scripts/accuracysnes/ares_host/ares_host.cpp
printf '%s\n' '--- target section ---'
cat -n scripts/accuracysnes/ares_host/ares_host.cpp | sed -n '120,205p'
printf '%s\n' '--- runtime definitions and uses ---'
rg -n -C 4 'R_(COUNT|PASSED|FAILED|SKIPPED|GOLDEN|DONE)|RESULTS|ACSN|ACCURACYSNES-BEGIN|i < 512|512' scripts/accuracysnes

Repository: doublegate/RustySNES

Length of output: 18375


🏁 Script executed:

#!/bin/bash
set -eu
find scripts/accuracysnes -type f -maxdepth 4 -print | sort
printf '%s\n' '--- relevant symbols ---'
rg -n -C 5 'R_(COUNT|PASSED|FAILED|SKIPPED|GOLDEN|DONE)|RESULTS|ACSN|ACCURACYSNES-BEGIN|i < 512|512' scripts/accuracysnes

Repository: doublegate/RustySNES

Length of output: 17761


🏁 Script executed:

rg -n -C 8 'ACCURACYSNES-BEGIN|R_DONE|R_COUNT|R_PASSED|R_FAILED|R_SKIPPED|R_GOLDEN|RESULTS' .

Repository: doublegate/RustySNES

Length of output: 50378


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- shared results layout ---'
sed -n '7,45p' tests/roms/AccuracySNES/gen/src/emit.rs
printf '%s\n' '--- runtime layout symbols ---'
rg -n -C 3 'R_MAGIC|R_VERSION|R_COUNT|R_DONE|R_PASSED|R_FAILED|R_SKIPPED|R_GOLDEN|R_STATUS|R_DONE_MARK|R_FORMAT_VERSION|_test_count' tests/roms/AccuracySNES/asm
printf '%s\n' '--- Ares host entry and return behavior ---'
cat -n scripts/accuracysnes/ares_host/ares_host.cpp | sed -n '85,180p'

Repository: doublegate/RustySNES

Length of output: 26621


Reject incomplete or malformed result blocks before reporting counts.

The host emits ACCURACYSNES-BEGIN after any fixed frame budget, even when WRAM contains no completed battery. Validate ACSN, R_VERSION == 1, R_DONE == 0xA5, and 0 < count <= 512; reject invalid blocks before printing any result fields. Otherwise, i < 512 silently drops statuses from an oversized result.

🧰 Tools
🪛 Clang (14.0.6)

[warning] 158-158: variable 'RESULTS' is not initialized

(cppcoreguidelines-init-variables)


[warning] 163-163: variable 'count' is not initialized

(cppcoreguidelines-init-variables)


[warning] 164-164: do not call c-style vararg functions

(cppcoreguidelines-pro-type-vararg)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/accuracysnes/ares_host/ares_host.cpp` around lines 156 - 171,
Validate the WRAM result block before emitting any output in the
results-reporting flow: confirm the magic is “ACSN”, version is 1, done is 0xA5,
and the count read via rd16 at offset 0x06 is within 1..512; return or otherwise
reject invalid blocks before printing any result fields. Also update the
status-processing bound to handle all entries up to the validated count without
silently truncating counts above 512.

Source: Path instructions

for(u32 i = 0; i < count && i < 512; i++) {
printf("status %u %02x\n", i, wram[RESULTS + 0x20 + i]);
}
Expand Down
4 changes: 2 additions & 2 deletions scripts/accuracysnes/ares_host/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ ninja -C "$BUILD" \
# `NALL_MAIN_IMPL` is undefined, and nall's own `main.cpp.o` defines that. Without the include the
# link fails with a bare `undefined reference to 'main'` from crt1.o, which reads like a missing
# object rather than a missing shim.
g++ -std=c++2b -O2 -c "$HERE/ares_host.cpp" -o "$BUILD/ares_host.o" \
g++ -std=c++2b -O2 -g -rdynamic -c "$HERE/ares_host.cpp" -o "$BUILD/ares_host.o" \
-I"$ARES" -I"$ARES/ares" -I"$ARES/nall" -I"$ARES/libco" -I"$ARES/thirdparty" \
-I"$BUILD" -I"$BUILD/ares" \
$(pkg-config --cflags gtk+-3.0) -DHIRO_GTK=3

# The library set and order are cribbed from desktop-ui's own link line in `build.ninja`; ares is
# listed twice there and needs to be here too.
g++ -O2 -o "$OUT" "$BUILD/ares_host.o" \
g++ -O2 -g -rdynamic -o "$OUT" "$BUILD/ares_host.o" \
"$BUILD/nall/nall/CMakeFiles/nall.dir/main.cpp.o" \
"$BUILD/nall/nall/CMakeFiles/nall.dir/nall.cpp.o" \
"$BUILD/nall/nall/CMakeFiles/nall.dir/sljitAllocator.cpp.o" \
Expand Down
Loading