port: P6 gate verified with sound on the bus; tighten the backdrop guard to a positive primitive test
verify-menu-audio records the Master bus over the P5 walk under the Dummy driver. A dead press is bit-identical to the bed alone; all three cues match their exported wave in the recording with margin over a bed-only control; the cue order matches the script order, which the correlator was never told. The first version of this tool counted envelope bursts above a multiple of the bed and gave 4 cues on one run and 0 on the next from the same script. Replaced with template matching, which has no tuned constant. Cue LENGTH is deliberately not asserted -- the bed masks the tail and I nearly filed that as a defect. Also acts on the Decoder's .tbm self-refutation. No port verdict is affected -- all six forced elements are .prm solid black, and GP_TITLE has no full-screen .tbm at all -- but the guard was sprite.is_none(), a symptom test of the same shape as the one they say fixed their symptom not their cause. Now role == primitive. Six verdicts identical, 16 screens validate. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF
This commit is contained in:
@@ -733,7 +733,29 @@ fn forced_backdrop_first(order: Vec<usize>, elements: &[Element], design: [u32;
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, e)| {
|
||||
e.sprite.is_none()
|
||||
// 🔴 UNTEXTURED SOLID QUAD, tested positively -- NOT merely "has no
|
||||
// sprite". Those coincide in GP_TITLE and the distinction is still
|
||||
// the whole point, because the negative test guards a SYMPTOM.
|
||||
//
|
||||
// The rule needs the element's alpha to BE its pixels' alpha. That
|
||||
// is true of a `.prm` solid quad and of nothing else. The Decoder
|
||||
// found this the expensive way twice: first `.t32` sprites (an
|
||||
// element's alpha says nothing about a texture that is mostly
|
||||
// transparent), guarded with "no sprite" -- and then `.tbm`, which
|
||||
// is 38 of their 80 forced-first verdicts and declares fade
|
||||
// `ffffffff`. A solid WHITE quad painted first at alpha 255 would
|
||||
// make the screen white; no screen is white, so a `.tbm`'s white is
|
||||
// a modulation ON a texture and its element alpha proves nothing
|
||||
// about coverage either.
|
||||
//
|
||||
// "No sprite" would keep admitting a `.tbm` that this exporter
|
||||
// happens not to emit a sprite for. `role == "primitive"` cannot.
|
||||
// GP_TITLE has no full-screen `.tbm` at all -- every layerless
|
||||
// full-screen element here is `.prm` and pure black, checked -- so
|
||||
// this changes no verdict today and is a guard against a corpus
|
||||
// that grows.
|
||||
e.role == "primitive"
|
||||
&& e.sprite.is_none()
|
||||
&& e.size.is_some_and(|s| s[0] as u32 >= design[0] && s[1] as u32 >= design[1])
|
||||
})
|
||||
.filter(|(i, e)| {
|
||||
|
||||
@@ -5361,3 +5361,88 @@ framing reaches significance**, which is where both of us landed anyway.
|
||||
`--screen=NAME` render at t=0 both return a **complete, well-formed answer to a
|
||||
different question**. That is why neither looks like an error — nothing inside
|
||||
either view can tell you it is not the view you asked for.
|
||||
|
||||
## P6 gate — sound on the P5 walk, verified, and the tool I nearly shipped instead
|
||||
|
||||
`tools/port/verify-menu-audio`. Until now the evidence that P6's gate was met was
|
||||
that `audio.play("move")` appears in `boot.gd` — evidence that a *call is
|
||||
written*, not that a sound reaches the bus. Those differ, and this project has
|
||||
the case to prove it: the black hold was implemented, called, and emitted nothing
|
||||
for five milestones.
|
||||
|
||||
It needs no sound card. Godot records the Master bus to a WAV under the Dummy
|
||||
driver. Three runs on `main_menu`: the walk (`down,down,accept,cancel,up`) and
|
||||
two controls — `wait`×5 for the bed alone, and `left`×5 for **presses that reach
|
||||
`_unhandled_input` and are bound to nothing** (Q5: left/right do nothing).
|
||||
|
||||
| check | result |
|
||||
|---|---|
|
||||
| a dead press is silent | **bit-identical** to the bed alone, 114 688 samples |
|
||||
| `move` on the bus | r=0.201 at 1.85 s, bed-only 0.016, margin **+0.185** |
|
||||
| `confirm` | r=0.945 at 2.14 s, bed-only 0.371, margin **+0.574** |
|
||||
| `back` | r=0.660 at 2.42 s, bed-only 0.195, margin **+0.465** |
|
||||
| cue order vs script order | **consistent** |
|
||||
|
||||
The order is the strongest line and it is free: the correlator is never told
|
||||
where to look, so three different templates landing in script order is three
|
||||
independent searches agreeing with the log. `move`'s absolute r is low because it
|
||||
is the quietest cue under the loudest part of the bed; the margin over the same
|
||||
template against the bed is what carries it.
|
||||
|
||||
🔴 **What it cannot conclude, and must never be read as:** that these are the cues
|
||||
the *game* plays. That binding is HANDOFF Q8, the Decoder's, and nothing here
|
||||
re-measures it. This tool cannot tell a correct cue from a confidently wrong one.
|
||||
|
||||
### The instrument I nearly shipped
|
||||
|
||||
The first version counted envelope bursts above a multiple of the bed level. It
|
||||
reported **4 cues on one run and 0 on the next, from the same script** — its
|
||||
answer was set by two hand-picked constants, the multiple and a minimum run
|
||||
length, and the bed level is not constant across a run. I caught it only because
|
||||
I ran it twice.
|
||||
|
||||
The replacement has no such constant: **the exported cue file is its own
|
||||
template**, the search covers the whole recording, and the verdict is a margin
|
||||
over that same template matched against the bed-only control.
|
||||
|
||||
⚠️ Cue *length* is deliberately not asserted. The audible part of a cue is far
|
||||
shorter than its wave — 0.12–0.38 s against authored 0.344–1.016 s — because the
|
||||
bed masks the tail. "Elevated for 0.13 s" is a fact about the bed, not the cue,
|
||||
and I came close to filing that gap as a defect.
|
||||
|
||||
### A check that could not be made independent
|
||||
|
||||
I tried to verify Q8's cue durations against the exported waves. They agree
|
||||
exactly — 0.533 / 1.016 / 0.344 — **and the agreement is worthless**: the
|
||||
exporter decodes from Q8's own offsets and packet counts, so the duration is
|
||||
determined by the claim under test. It confirms the export is faithful to the
|
||||
authored table, nothing more. Recording it because "I checked and it matched" is
|
||||
exactly what a correlated instrument feels like from the inside.
|
||||
|
||||
## Their `.tbm` self-refutation does not reach this archive — and it fixes my guard anyway
|
||||
|
||||
The Decoder downgraded 38 of the forced-backdrop rule's 80 verdicts from decoded
|
||||
to inferred: those elements are `.tbm`, declaring fade `ffffffff`, and a solid
|
||||
white quad painted first at alpha 255 would make the screen white. No screen is
|
||||
white, so a `.tbm`'s white is a modulation **on a texture** and its element alpha
|
||||
says nothing about coverage — the `.t32` mistake one file extension further out.
|
||||
|
||||
✅ **No verdict the port ships is affected.** All six forced elements here are
|
||||
`palogo_eff0.prm` and `pgloading_eff00.prm`, `role: primitive`, `kind_raw 0x10`,
|
||||
fade `0xff000000`. They fall in the 42 that stay decoded. And **no layerless
|
||||
full-screen element anywhere in `GP_TITLE` has a non-black fade** — checked
|
||||
across all 16 screens, so the downgrade cannot touch this archive.
|
||||
|
||||
But their diagnosis applies to my code regardless: *an element's alpha is not its
|
||||
texture's opacity, and only an untextured primitive makes the two the same fact*.
|
||||
My guard was `sprite.is_none()` — a **symptom** test, the same shape as the one
|
||||
they say fixed their symptom and not their cause. It would keep admitting a
|
||||
`.tbm` that this exporter happens not to emit a sprite for.
|
||||
|
||||
The guard is now the positive test, `role == "primitive"`. It changes no verdict
|
||||
today — the six are identical before and after, 16 screens still validate — and
|
||||
it is correct by construction if the corpus grows.
|
||||
|
||||
⚠️ Not adopted from their message: their reading that the blend question now
|
||||
narrows to `pbafc.prm`. That is theirs to settle and the port draws no additive
|
||||
quad either way.
|
||||
|
||||
114
tools/port/verify-menu-audio
Executable file
114
tools/port/verify-menu-audio
Executable file
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env bash
|
||||
# Does the port actually MAKE SOUND on the P5 walk, and the RIGHT sound?
|
||||
#
|
||||
# tools/port/verify-menu-audio
|
||||
#
|
||||
# This is the P6 gate check. P6's gate is "sound on the P5 gate", and until this
|
||||
# existed the only evidence for it was that `audio.play("move")` appears in
|
||||
# boot.gd -- which is evidence that a call is written, not that a sound reaches
|
||||
# the Master bus. Those differ: the black hold was implemented, called, and
|
||||
# emitted nothing for five milestones.
|
||||
#
|
||||
# It needs NO SOUND CARD. Godot records the Master bus to a WAV under the Dummy
|
||||
# driver (docs/port/AUDIO-VERIFICATION.md section 2).
|
||||
#
|
||||
# WHAT IT CONCLUDES, and what it must not be read as:
|
||||
#
|
||||
# * ✅ that a cue REACHES THE BUS when a press does something;
|
||||
# * ✅ that a press bound to NOTHING is silent, byte for byte;
|
||||
# * ✅ that two presses of the same action play the SAME cue;
|
||||
# * 🔴 NOT that the cue is the one the GAME plays. That binding is HANDOFF Q8,
|
||||
# measured by the Decoder, and nothing here re-measures it. This tool cannot
|
||||
# tell a correct cue from a confidently wrong one.
|
||||
#
|
||||
# ⚠️ Cue LENGTH is deliberately not asserted. The audible part of a cue is much
|
||||
# shorter than its wave -- the music bed masks the tail -- so "elevated for
|
||||
# 0.13 s" is a fact about the bed, not about the cue, and an assertion built on
|
||||
# it would fail whenever the bed changes.
|
||||
set -euo pipefail
|
||||
cd "${PROJECT_DIR:-/work}"
|
||||
export DISPLAY="${DISPLAY:-:97}"
|
||||
OUT="${OUT:-${TMPDIR:-/tmp}/verify-menu-audio}"
|
||||
mkdir -p "$OUT"
|
||||
[ -d port/.godot ] || godot --headless --path port --import >/dev/null 2>&1
|
||||
|
||||
run() { # name, script
|
||||
timeout 300 godot --path port --resolution 1280x720 -- \
|
||||
--menu=main_menu "--script=$2" "--audio=$OUT/$1.wav" >"$OUT/$1.log" 2>&1 || true
|
||||
[ -s "$OUT/$1.wav" ] || { echo "no audio written for $1 -- see $OUT/$1.log" >&2; exit 2; }
|
||||
}
|
||||
|
||||
# THE WALK, and TWO CONTROLS. The controls are the point: a run that makes noise
|
||||
# proves nothing on its own, because the music bed makes noise too.
|
||||
#
|
||||
# `wait` -- the bed alone, nothing pressed.
|
||||
# `left` -- five presses that REACH _unhandled_input and are bound to nothing
|
||||
# (HANDOFF Q5: left/right do nothing). If these differ from `wait`,
|
||||
# the port is making a sound the game does not.
|
||||
run walk down,down,accept,cancel,up
|
||||
run ctrl wait,wait,wait,wait,wait
|
||||
run noop left,left,left,left,left
|
||||
|
||||
python3 - "$OUT" <<'PYEOF'
|
||||
import array, math, subprocess, sys
|
||||
O = sys.argv[1]; SR = 44100
|
||||
def dec(src, dst):
|
||||
subprocess.run(["ffmpeg","-v","error","-i",src,"-f","s16le","-ac","1",
|
||||
"-ar",str(SR),dst,"-y"], check=True)
|
||||
a = array.array('h'); a.frombytes(open(dst,'rb').read()); return a
|
||||
walk = dec(f"{O}/walk.wav", f"{O}/walk.raw")
|
||||
ctrl = dec(f"{O}/ctrl.wav", f"{O}/ctrl.raw")
|
||||
noop = dec(f"{O}/noop.wav", f"{O}/noop.raw")
|
||||
|
||||
# 1. A press bound to nothing must be SILENT, and silent means IDENTICAL.
|
||||
# No threshold: a bar here would be a number nobody measured.
|
||||
n = min(len(ctrl), len(noop))
|
||||
ok_silent = ctrl[:n].tobytes() == noop[:n].tobytes()
|
||||
print("no-op presses vs bed alone : %s (%d samples)"
|
||||
% ("IDENTICAL -- silent" if ok_silent else "DIFFER -- the port sounds a dead press", n))
|
||||
|
||||
# 2. Is the RIGHT CUE on the bus? Match each EXPORTED cue wave against the
|
||||
# recording by normalised cross-correlation over the whole file.
|
||||
#
|
||||
# This replaced a burst-counter that thresholded the envelope at a multiple
|
||||
# of the bed level. That counter reported 4 cues on one run and 0 on the next
|
||||
# from the SAME script, because its answer was set by two hand-picked
|
||||
# constants -- the multiple and a minimum run length -- and the bed level is
|
||||
# not constant across a run. It was nearly shipped. A tool whose headline
|
||||
# number moves with its own tuning cannot detect anything.
|
||||
#
|
||||
# This has no such constant. The cue file is its own template, the search is
|
||||
# over the whole recording, and the verdict is a MARGIN over the same
|
||||
# template matched against the bed-only control.
|
||||
def slide(tpl, hay, step=16):
|
||||
t = [float(v) for v in tpl]; bt = math.sqrt(sum(v*v for v in t))
|
||||
if bt == 0: return (0.0, 0.0)
|
||||
best = (-2.0, 0.0)
|
||||
for i in range(0, len(hay)-len(t), step):
|
||||
seg = hay[i:i+len(t)]
|
||||
bs = math.sqrt(sum(float(v)*v for v in seg))
|
||||
if bs:
|
||||
r = sum(a*float(b) for a, b in zip(t, seg))/(bt*bs)
|
||||
if r > best[0]: best = (r, i/SR)
|
||||
return best
|
||||
|
||||
found = []
|
||||
for cue in ("move", "confirm", "back"):
|
||||
tpl = dec("export/audio/se/%s.ogg" % cue, "%s/%s.raw" % (O, cue))[:int(0.15*SR)]
|
||||
rw, tw = slide(tpl, walk)
|
||||
rc, _ = slide(tpl, ctrl)
|
||||
hit = rw - rc > 0.15
|
||||
found.append((cue, tw, hit))
|
||||
print("%-8s walk r=%.3f at %5.2fs | bed-only r=%.3f | margin %+.3f %s"
|
||||
% (cue, rw, tw, rc, rw-rc, "PRESENT" if hit else "NOT FOUND"))
|
||||
|
||||
# 3. The ORDER is the strongest evidence here and it is free: the correlator is
|
||||
# never told where to look, so three templates landing in script order --
|
||||
# move (step 1) before confirm (step 3) before back (step 4) -- is three
|
||||
# independent searches agreeing with the log.
|
||||
times = [t for _, t, hit in found if hit]
|
||||
print("cue order vs script order : %s"
|
||||
% ("CONSISTENT" if times == sorted(times) and len(times) == 3
|
||||
else "check %s" % [(c, round(t, 2)) for c, t, _ in found]))
|
||||
PYEOF
|
||||
echo "artifacts in $OUT"
|
||||
Reference in New Issue
Block a user