diff --git a/.github/release-notes/v2.3.1.md b/.github/release-notes/v2.3.1.md index d3d1d42f..3bfad469 100644 --- a/.github/release-notes/v2.3.1.md +++ b/.github/release-notes/v2.3.1.md @@ -1,29 +1,13 @@ -RustyNES **v2.3.1 "Plumb Line"** is a measurement release. It makes the -performance apparatus trustworthy and then uses it — and what it found is that -**none of the ten hot-path candidates it measured yielded a shippable -improvement.** That is a claim about those ten, not about the core as a whole: -two core leads the campaign surfaced (the APU at 18.7% of frame, and `range.rs` -inlined inside `Ppu::tick` at 1.52%) remain **unmeasured** and are carried -forward. - -**No emulation-core changes.** AccuracyCoin holds at **exactly 141/141** and -nestest is 0-diff, verified after every experimental probe was reverted rather -than merely asserted by construction: this release did land and remove real -edits. +RustyNES **v2.3.1 "Plumb Line"** is a measurement release. It makes the performance apparatus trustworthy and then uses it — and what it found is that **none of the ten hot-path candidates it measured yielded a shippable improvement.** That is a claim about those ten, not about the core as a whole: two core leads the campaign surfaced (the APU at 18.7% of frame, and `range.rs` inlined inside `Ppu::tick` at 1.52%) remain **unmeasured** and are carried forward. + +**No emulation-core changes.** AccuracyCoin holds at **exactly 141/141** and nestest is 0-diff, verified after every experimental probe was reverted rather than merely asserted by construction: this release did land and remove real edits. ## Why a measurement release Two failures in the preceding release motivated it. -- **v2.3.0's adopted PPU optimization measured `+2%` on a contended host and - `−5.13%` re-measured quiet** — the same commit, opposite sign. The project's - adopt/reject bar is only as good as the host it runs on, and nothing noticed - the host. -- **The profile the campaign was scoped from does not contain the APU.** - `perf report` shows zero `rustynes_apu::` symbols at any percent limit, because - fat LTO inlines the APU wholesale into `cpu_clock`. The working split - "PPU ~53%, CPU+bus ~39%" had folded roughly a fifth of the frame into the wrong - bucket. +- **v2.3.0's adopted PPU optimization measured `+2%` on a contended host and `−5.13%` re-measured quiet** — the same commit, opposite sign. The project's adopt/reject bar is only as good as the host it runs on, and nothing noticed the host. +- **The profile the campaign was scoped from does not contain the APU.** `perf report` shows zero `rustynes_apu::` symbols at any percent limit, because fat LTO inlines the APU wholesale into `cpu_clock`. The working split "PPU ~53%, CPU+bus ~39%" had folded roughly a fifth of the frame into the wrong bucket. ## New measurement tooling @@ -33,13 +17,9 @@ Two failures in the preceding release motivated it. | `frame_breakdown.sh` — attribution by source file | the **APU is 18.7% of frame time**; `perf report --inline` does *not* recover it | | `ab_check.sh` — adoption A/B with an A/B/A order-bias control | the reference drifts up to **−1.17% from run position alone** | -Corrected subsystem split: **PPU 52.1% · APU 18.7% · CPU 10.1% · bus/scheduler -coupling 9.9% · std inlined at call sites 6.7% · mappers 2.5%.** The CPU proper -is about a third of what the symbol profile implied. +Corrected subsystem split: **PPU 52.1% · APU 18.7% · CPU 10.1% · bus/scheduler coupling 9.9% · std inlined at call sites 6.7% · mappers 2.5%.** The CPU proper is about a third of what the symbol profile implied. -`bench_relative_check.sh` additionally declines to emit a verdict when the host -was too noisy to resolve the effect under test, keyed on a robust MAD-based -coefficient of variation. +`bench_relative_check.sh` additionally declines to emit a verdict when the host was too noisy to resolve the effect under test, keyed on a robust MAD-based coefficient of variation. ## Ten candidates measured, ten rejected @@ -52,36 +32,24 @@ coefficient of variation. | the target is too small to matter | the `bg_split_state` capability gate (0.09% of frame) | | forbidden by the ownership model | hoisting `PpuBusAdapter` (borrow checker, with no `unsafe` permitted) | -Six distinct mechanisms, which is what makes this a finding rather than one bad -assumption repeated: **the per-dot loop has no incidental overhead left to -reclaim.** Its ~3.78 ms is work the accuracy model requires. That corroborates -the existing record, where bounds-check elision and a SIMD blitter both measured -*slower*. +Six distinct mechanisms, which is what makes this a finding rather than one bad assumption repeated: **the per-dot loop has no incidental overhead left to reclaim.** Its ~3.78 ms is work the accuracy model requires. That corroborates the existing record, where bounds-check elision and a SIMD blitter both measured *slower*. ## Two near-misses Worth recording, because each would have shipped on a single reading: -- One candidate produced a textbook **−1.84% … −2.75% at p = 0.00 on all four - workloads** — entirely an order-bias artifact. It measured as exactly zero on - re-run. This is what prompted the A/B/A control. -- Another measured **−0.51% at p = 0.00 on a shipped configuration** with a clean - control, then **+0.01% (p = 0.96)** on re-run. +- One candidate produced a textbook **−1.84% … −2.75% at p = 0.00 on all four workloads** — entirely an order-bias artifact. It measured as exactly zero on re-run. This is what prompted the A/B/A control. +- Another measured **−0.51% at p = 0.00 on a shipped configuration** with a clean control, then **+0.01% (p = 0.96)** on re-run. Both were caught only by requiring an independent second run. ## Also in this release -- The PGO workflow's BOLT probe no longer reports success without BOLT. It ran - `apt-get install bolt` and trusted the exit status — but on Ubuntu that package - is the **Thunderbolt 3 device manager**, so the stage failed on the tool it had - just "confirmed" instead of skipping as its best-effort contract intends. -- Every rejected experiment is recorded in `docs/performance.md` with its - numbers, its order-bias control, and the mechanism behind the null result. +- The PGO workflow's BOLT probe no longer reports success without BOLT. It ran `apt-get install bolt` and trusted the exit status — but on Ubuntu that package is the **Thunderbolt 3 device manager**, so the stage failed on the tool it had just "confirmed" instead of skipping as its best-effort contract intends. +- Every rejected experiment is recorded in `docs/performance.md` with its numbers, its order-bias control, and the mechanism behind the null result. ## Verification -- `cargo test --workspace --features test-roms` green — AccuracyCoin **141/141**, - `visual_regression` 9/9, nestest 0-diff. +- `cargo test --workspace --features test-roms` green — AccuracyCoin **141/141**, `visual_regression` 9/9, nestest 0-diff. - Workspace clippy clean at `-D warnings`; `cargo fmt --all --check` clean. - `shellcheck` clean on every touched script. diff --git a/docs/SALVAGE_MANIFEST.md b/docs/SALVAGE_MANIFEST.md index 7634e092..c19da3c7 100644 --- a/docs/SALVAGE_MANIFEST.md +++ b/docs/SALVAGE_MANIFEST.md @@ -102,3 +102,31 @@ testing, and a built MkDocs static site — all spent/regenerable, none salvaged. Only `scripts/probes/probe_rev.rs` was promoted from untracked scratch to committed (see the row above) — everything else from the prior pass stays as previously recorded. + +## 2026-08-06 — pre-reboot salvage (manual, curated) + +Run before rebooting for an NVIDIA driver/kernel update. The `tmp-salvage` dry +run offered **305 candidates / ~600 MB**; **3 files** were taken. The rest was +deliberately left to be wiped: + +| left behind | why | +| --- | --- | +| `perf.data` (586 MB), `probe.perf`, `fp.data`, `di.data`, `bolt.log` | perf recordings and CI logs — regenerable with one command | +| ~100 `.md`/`.json` PR-review files | already posted to GitHub PR #348 | +| `fisk-*.png`, `v2p-*.png` (8 MB) | screenshots, possibly commercial-ROM framebuffers — not swept in unexamined | +| `/tmp/accuracycoin-build/` | third-party MIT source + a Windows `nesasm.exe`; vendoring is a deliberate provenance decision, not a salvage side effect | +| `agy-review.sh`, `agy-review-selftest.sh` | duplicates of files already in `scripts/` and `Local_Only-Projects/antigravity-pr-review/` | +| `p3.sh`, `p4.sh`, `probe*.sh`, `mi.rs` | extraction artifacts (YAML pulled out for `shellcheck`) and a temp probe already reverted on purpose | + +Everything built during the session was **already committed** (`frame_probe.rs`, +`ab_check.sh`, `frame_breakdown.sh`, the `perf_log_check.py` p99 gate), so the +scratchpad held no unique source beyond the three below. + +| file | → | note | +| --- | --- | --- | +| `reflow.py` | `scripts/release-automation/` | markdown full-width reflow; fixed the release-notes formatting. Existed nowhere else on disk. | +| `assemble.py` | `scripts/release-automation/` | HTML fragment → document (needs `bs4`). | +| `guardrails_assemble.py` | `scripts/release-automation/` | same, for the provenance-guardrails doc. | + +SHA-256 verified identical to source after copy. See +`scripts/release-automation/README-doc-tools.md` for usage. diff --git a/scripts/release-automation/README-doc-tools.md b/scripts/release-automation/README-doc-tools.md new file mode 100644 index 00000000..de8932b9 --- /dev/null +++ b/scripts/release-automation/README-doc-tools.md @@ -0,0 +1,52 @@ +# Release-notes / doc rendering helpers + +Three one-off tools rescued from `/tmp` before a reboot (2026-08-06). They were +written during the v2.2.5–v2.3.0 documentation work, existed **nowhere else on +disk**, and would have been lost. Recorded here so they are findable rather than +rediscovered. + +| script | needs | what it does | +| --- | --- | --- | +| `reflow.py` | stdlib only | Unwraps hard-wrapped markdown into single full-width lines. | +| `assemble.py` | `bs4` | Assembles a rendered HTML fragment into a full document. | +| `guardrails_assemble.py` | `bs4` | Same, for the provenance-guardrails doc: injects a title block and reddens a curated set of hard takeaways. | + +## `reflow.py` — the one you will want again + +This is the tool that fixed the GitHub release-notes formatting complaint: notes +published from v2.2.5 onward had been hard-wrapped at ~80 columns, which GitHub +renders as artificially narrow text instead of using the full width available. + +It unwraps paragraphs and list items to one line each while **preserving** +blank lines, ATX headings, horizontal rules, fenced code blocks, tables, +blockquotes, and raw HTML lines — the things that break if naively joined. + +```bash +python3 scripts/release-automation/reflow.py < in.md > out.md +``` + +Worth running over any hand-wrapped `.github/release-notes/vX.Y.Z.md` before +publishing. + +## The `bs4` pair + +`assemble.py` and `guardrails_assemble.py` take a rendered HTML fragment and +produce a standalone document. They need BeautifulSoup, which is **not** a +project dependency — install it in a throwaway venv rather than adding it to the +repo: + +```bash +python3 -m venv /tmp/venv && /tmp/venv/bin/pip install beautifulsoup4 +/tmp/venv/bin/python scripts/release-automation/assemble.py frag.html out.html +``` + +Both are specific to the one-time provenance/guardrails PDF build +(`ref-docs/`) and are kept for reproducing those artifacts, not for routine use. + +**They are preserved verbatim and are not ruff-clean** (`SIM115` context +managers, `UP031` percent-format). That is deliberate: `bs4` is not installed +here and no sample fragment survives, so a lint rewrite could not be executed to +prove it still behaved. Rewriting code you cannot run is a worse trade than a +style nit. Clean them up the first time you actually need them, with a real +input to test against. `reflow.py` — which *is* testable, being stdlib-only — was +fixed and verified. diff --git a/scripts/release-automation/assemble.py b/scripts/release-automation/assemble.py new file mode 100755 index 00000000..cce6d12d --- /dev/null +++ b/scripts/release-automation/assemble.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +import sys + +from bs4 import BeautifulSoup, NavigableString + +frag_path, out_path = sys.argv[1], sys.argv[2] +html = open(frag_path, encoding="utf-8").read() +soup = BeautifulSoup(html, "html.parser") + +# --- RED for curated genuine takeaways (exact text contains) --- +KEY = [ + "more serious LLM error", + "did not follow it", + "AI self-attestation of license compliance is not trustworthy", + "single most important piece of evidence", + "Correct.", + "not written purely from hardware documentation", +] +for s in soup.find_all("strong"): + t = s.get_text() + if any(k in t for k in KEY): + cls = s.get("class", []) + s["class"] = cls + ["key"] + +# --- top-level elements, in order --- +els = [c for c in soup.contents if getattr(c, "name", None)] +table_i = next(i for i, e in enumerate(els) if e.name == "table") +note_i = next(i for i, e in enumerate(els) + if e.name == "p" and e.get_text().lstrip().startswith("NOTE")) + +before = "".join(str(e) for e in els[:table_i]) +table = str(els[table_i]) +middle = "".join(str(e) for e in els[table_i + 1:note_i]) + +# --- italicize everything after "): " in the NOTE paragraph (PDF only) --- +note_p = els[note_i] +MARKER = "): " +kids = list(note_p.children) +head, tail_nodes, splitting = [], [], True +for k in kids: + if splitting and isinstance(k, NavigableString) and MARKER in k: + pre, post = str(k).split(MARKER, 1) + head.append(NavigableString(pre + MARKER)) + if post: + tail_nodes.append(NavigableString(post)) + splitting = False + elif splitting: + head.append(k) + else: + tail_nodes.append(k.extract() if hasattr(k, "extract") else k) +if tail_nodes: # only rebuild if the marker was found + note_p.clear() + for h in head: + note_p.append(h) + em = soup.new_tag("em") + for t in tail_nodes: + em.append(t) + # Bold + upright "Fiskbit" inside the italic note body (PDF styling only) + for tnode in list(em.find_all(string=True)): + if "Fiskbit" in tnode: + parts = str(tnode).split("Fiskbit") + repl = [] + for i, seg in enumerate(parts): + if i > 0: + st = soup.new_tag("strong") + st["class"] = ["upright"] + st.string = "Fiskbit" + repl.append(st) + if seg: + repl.append(NavigableString(seg)) + tnode.replace_with(*repl) + break + note_p.append(em) +note = str(note_p) + +TITLE = """
+

RUSTYNES · INCIDENT RECORD · GPL PROVENANCE

+

Provenance Failure Post-Mortem

+

How GPL Emulator Code Was Lifted Despite a Black-Box Instruction

+

Forensic Root-Cause Analysis · 2026-08-04 · RustyNES v2.2.9

+
""" + +body = (TITLE + + '
' + before + '
' + + table + + '
' + middle + '
' + + '
' + note + '
') + +doc = ('' + 'Provenance Failure Post-Mortem' + + body + '') + +open(out_path, "w", encoding="utf-8").write(doc) +print("assembled ->", out_path, + "| before/middle/note split at table_i=%d note_i=%d" % (table_i, note_i), + "| reddened %d strong tags" % len(soup.select("strong.key"))) diff --git a/scripts/release-automation/guardrails_assemble.py b/scripts/release-automation/guardrails_assemble.py new file mode 100755 index 00000000..0d8c60a1 --- /dev/null +++ b/scripts/release-automation/guardrails_assemble.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +"""Assemble the guardrails HTML: inject a title block, redden a curated +set of hard takeaways. Single-column theme handles the rest of the flow.""" +import sys + +from bs4 import BeautifulSoup + +frag_path, out_path = sys.argv[1], sys.argv[2] +html = open(frag_path, encoding="utf-8").read() +soup = BeautifulSoup(html, "html.parser") + +# --- RED reserved for a few genuine, full-span takeaways --- +KEY = [ + "capability plus availability plus an accuracy objective", + "C used as if it were A", + "source physically unavailable to the agent", + "Never launder", +] +reddened = 0 +for s in soup.find_all("strong"): + t = s.get_text() + if any(k in t for k in KEY): + s["class"] = s.get("class", []) + ["key"] + reddened += 1 + +body = "".join(str(c) for c in soup.contents) + +TITLE = """
+

COMMUNITY BEST-GUIDANCE · AI-ASSISTED EMULATOR DEVELOPMENT

+

Provenance & License Guardrails

+

A ready-to-ingest ruleset for Claude Code and other agentic / AI-assisted development tools

+

Preventing the copyleft-source-lifting trap · 2026-08-04

+
""" + +doc = ('' + 'Provenance & License Guardrails' + + TITLE + body + '') + +open(out_path, "w", encoding="utf-8").write(doc) +print("assembled ->", out_path, "| reddened %d strong tags" % reddened) diff --git a/scripts/release-automation/reflow.py b/scripts/release-automation/reflow.py new file mode 100755 index 00000000..4972c27c --- /dev/null +++ b/scripts/release-automation/reflow.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +"""Unwrap hard-wrapped markdown paragraphs/bullets into single full-width lines. + +Preserves: blank lines, ATX headings (#...), horizontal rules (--- / ***), +fenced code blocks (```), tables (| ...), blockquotes (> ...), and raw HTML lines. +Joins wrapped continuation lines within a paragraph or a single list item. +""" +import sys + +STRUCT_PREFIXES = ("#", ">", "|") + + +def is_list_item(s): + t = s.lstrip() + if t[:2] in ("- ", "* ", "+ "): + return True + # ordered "N. " or "N) " + i = 0 + while i < len(t) and t[i].isdigit(): + i += 1 + return i > 0 and i < len(t) and t[i] in ".)" and t[i + 1 : i + 2] == " " + + +def is_hr(s): + t = s.strip() + return len(t) >= 3 and set(t) <= {"-", "*", "_"} and len(set(t)) == 1 + + +def is_struct(s): + t = s.lstrip() + if not t: + return False + if t[0] in STRUCT_PREFIXES: + return True + if t.startswith("<") and t.rstrip().endswith(">"): + return True # standalone HTML line + return is_hr(s) or is_list_item(s) + + +def reflow(text): + lines = text.split("\n") + out = [] + buf = [] # accumulated logical line pieces + in_code = False + + def flush(): + if buf: + first = buf[0] + indent = first[: len(first) - len(first.lstrip())] + joined = indent + " ".join(p.strip() for p in buf) + out.append(joined) + buf.clear() + + for ln in lines: + stripped = ln.strip() + # Fenced code block toggle. + if stripped.startswith(("```", "~~~")): + flush() + out.append(ln) + in_code = not in_code + continue + if in_code: + out.append(ln) + continue + if stripped == "": + flush() + out.append("") + continue + # Blockquote: join consecutive `>` lines into one full-width `> ...` line. + if stripped.startswith(">"): + content = ln.lstrip()[1:] + content = content.removeprefix(" ") + if buf and buf[0].lstrip().startswith(">"): + buf.append(content) # continuation of the current blockquote + else: + flush() + buf.append(ln.rstrip()) # seed with the full `> ...` line + continue + if is_struct(ln): + # A structural line starts its own logical line. For a list item we + # still want to absorb ITS wrapped continuations, so seed the buffer. + flush() + if is_list_item(ln): + buf.append(ln.rstrip()) + else: + out.append(ln.rstrip()) + continue + # Plain text: continuation of the current paragraph/list item, or a new + # paragraph if the buffer is empty. + buf.append(ln.rstrip()) + flush() + # Collapse any accidental >1 consecutive blank lines to a single blank. + res = [] + for ln in out: + if ln == "" and res and res[-1] == "": + continue + res.append(ln) + return "\n".join(res).rstrip() + "\n" + + +if __name__ == "__main__": + sys.stdout.write(reflow(sys.stdin.read()))