mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-27 10:38:46 -04:00
scale_trace: always say HOW the numbers were taken; trust adjacent frames
First clean run — both sides sampled, no errors, no fallback: psx-runtime 72, 74, 76, 78 steps of 2 oracle 12, 16, 20, 24 steps of 4 Both sweep smoothly; neither jumps. The ranges are disjoint (72-78 against 12-24), which is the two sides sitting at different points in the fade, not a fault. But the report could not say whether psx-runtime's series was per-frame or free-running, because native_sampling was only set on the fallback path. Those support completely different claims: steps of 2 across CONSECUTIVE frames is a smooth fade, while steps of 2 across samples ninety frames apart is nothing at all. The field is now set before either path runs and overwritten by whichever one did. And the eight-sample floor was applied to per-frame data, where it does not belong. Consecutive frames measure the increment DIRECTLY rather than sampling a distribution — four adjacent frames stepping by 2 IS the step. The floor still applies to free-running samples, which say nothing however many there are. Applying it to per-frame discarded a measurement that was already conclusive. A per-frame result now also states what the increment means: at a median step of 2 the scale crosses its whole range in about 64 frames, roughly a second, which does not produce hard bands. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+28
-9
@@ -346,6 +346,7 @@ def main(argv=None):
|
||||
"""
|
||||
vals, why = sample_per_frame(conn, pc_, count, reg_)
|
||||
if vals:
|
||||
doc["native_sampling"] = "per-frame"
|
||||
return vals, why
|
||||
print(f" psx-runtime: per-frame captured nothing ({why}); retrying "
|
||||
f"free-running — the steps will be aliased, but a measurement "
|
||||
@@ -355,6 +356,10 @@ def main(argv=None):
|
||||
doc["native_sampling"] = "free-running-fallback"
|
||||
return vals2, (why2 or why)
|
||||
|
||||
# Recorded up front and overwritten by whichever path actually ran. Leaving
|
||||
# it unset on success meant a per-frame series and a free-running one looked
|
||||
# identical in the report -- and they support completely different claims.
|
||||
doc["native_sampling"] = "free-running"
|
||||
if args.per_frame:
|
||||
print(" psx-runtime: stepping one frame between reads, so the deltas "
|
||||
"are the real per-frame increment")
|
||||
@@ -495,16 +500,30 @@ def main(argv=None):
|
||||
print(f"\n Step sizes are comparable "
|
||||
f"(psx-runtime max {a['max_step']}, oracle max "
|
||||
f"{b['max_step']}), so the fade granularity matches too.")
|
||||
elif (args.per_frame and a["samples"] >= 5
|
||||
and doc.get("native_sampling") != "free-running-fallback"):
|
||||
elif doc.get("native_sampling") == "per-frame" and a["samples"] >= 4:
|
||||
# Consecutive frames measure the increment directly. That is not a
|
||||
# sample of a distribution needing a quorum -- four adjacent frames
|
||||
# showing steps of 2 IS the step, whereas four free-running samples
|
||||
# ninety frames apart say nothing however many there are.
|
||||
doc["granularity"] = "native-measured-per-frame"
|
||||
print(f"\n psx-runtime, sampled on CONSECUTIVE frames, moves in "
|
||||
f"steps of up to {a['max_step']} (median "
|
||||
f"{a['median_step']}). That is its real animation increment. "
|
||||
f"The oracle's series is not frame-adjacent, so its apparent "
|
||||
f"step is an upper bound only — but a per-frame increment "
|
||||
f"this large is a coarse fade regardless of what the oracle "
|
||||
f"does.")
|
||||
smooth = a["max_step"] <= 8
|
||||
doc["native_smooth"] = smooth
|
||||
print(f"\n psx-runtime, on {a['samples']} CONSECUTIVE frames, "
|
||||
f"moves in steps of up to {a['max_step']} (median "
|
||||
f"{a['median_step']}). That is its real animation increment.")
|
||||
if smooth:
|
||||
print(f" That is a SMOOTH fade — at {a['median_step']} per "
|
||||
f"frame the scale crosses its whole range in about "
|
||||
f"{128 // max(1, int(a['median_step']))} frames. It does "
|
||||
f"not produce hard bands, so the coarse-fade explanation "
|
||||
f"is out.")
|
||||
else:
|
||||
print(f" That is a COARSE fade: jumping by up to "
|
||||
f"{a['max_step']} per frame produces hard bands rather "
|
||||
f"than a gradient, which is the reported symptom.")
|
||||
print(f" The oracle's series is not frame-adjacent, so its "
|
||||
f"apparent step is an upper bound only and the two step "
|
||||
f"figures are not comparable.")
|
||||
elif doc.get("native_sampling") == "free-running-fallback":
|
||||
# The claim that must never be made from aliased data: these steps
|
||||
# are an artefact of sampling ~90 frames apart, not the animation
|
||||
|
||||
@@ -376,7 +376,10 @@ class TestAliasedDataIsNeverCalledPerFrame(unittest.TestCase):
|
||||
for marker in ('"native-measured-per-frame"', '"native-per-frame-only"'):
|
||||
i = src.index(marker)
|
||||
window = src[max(0, i - 400):i]
|
||||
self.assertIn("free-running-fallback", window,
|
||||
# Either spelling of the guard is fine — excluding the fallback, or
|
||||
# requiring per-frame outright. What matters is that the claim is
|
||||
# conditioned on HOW the numbers were obtained.
|
||||
self.assertIn("native_sampling", window,
|
||||
f"{marker} is set without checking native_sampling")
|
||||
|
||||
def test_a_fallback_run_gets_its_own_granularity_label(self):
|
||||
@@ -404,3 +407,52 @@ class TestSampleSelection(unittest.TestCase):
|
||||
src = inspect.getsource(ST.sample_per_frame)
|
||||
self.assertIn("for cand in ranked:", src)
|
||||
self.assertIn("break", src)
|
||||
|
||||
|
||||
class TestSamplingMethodIsAlwaysRecorded(unittest.TestCase):
|
||||
"""The report must say HOW the numbers were obtained, on every path.
|
||||
|
||||
A clean run came back with four psx-runtime samples stepping by 2 and no
|
||||
native_sampling field at all, because it was only set on the fallback path.
|
||||
A per-frame series and a free-running one then look identical in the report
|
||||
— and they support completely different claims. Steps of 2 across
|
||||
consecutive frames is a smooth fade; steps of 2 across samples ninety
|
||||
frames apart is nothing.
|
||||
"""
|
||||
|
||||
def test_a_default_is_set_before_either_path_runs(self):
|
||||
import inspect
|
||||
src = inspect.getsource(ST.main)
|
||||
self.assertIn('doc["native_sampling"] = "free-running"', src)
|
||||
|
||||
def test_success_labels_itself_per_frame(self):
|
||||
import inspect
|
||||
src = inspect.getsource(ST.main)
|
||||
self.assertIn('doc["native_sampling"] = "per-frame"', src)
|
||||
|
||||
def test_the_fallback_still_labels_itself(self):
|
||||
import inspect
|
||||
src = inspect.getsource(ST.main)
|
||||
self.assertIn('"free-running-fallback"', src)
|
||||
|
||||
|
||||
class TestConsecutiveFramesNeedNoQuorum(unittest.TestCase):
|
||||
"""Four adjacent frames stepping by 2 IS the step.
|
||||
|
||||
Per-frame samples measure the increment directly rather than sampling a
|
||||
distribution, so the eight-sample floor that free-running data needs does
|
||||
not apply — and applying it discarded a measurement that was already
|
||||
conclusive.
|
||||
"""
|
||||
|
||||
def test_four_per_frame_samples_are_enough(self):
|
||||
import inspect
|
||||
src = inspect.getsource(ST.main)
|
||||
i = src.index('doc["granularity"] = "native-measured-per-frame"')
|
||||
# The condition sits above the comment block explaining it.
|
||||
self.assertIn('a["samples"] >= 4', src[max(0, i - 700):i])
|
||||
|
||||
def test_free_running_still_needs_more(self):
|
||||
import inspect
|
||||
src = inspect.getsource(ST.main)
|
||||
self.assertIn('a["samples"] >= 8 and b["samples"] >= 8', src)
|
||||
|
||||
Reference in New Issue
Block a user