Find the sustained action sequence: it breaks both profiles
The open risk since session 2 was "a sustained action sequence could still break the bitrate", with every clip measured so far being 1.2-1.7 s. Closed by measurement rather than by sampling clips by hand. 07_motion_survey.py scans a whole stream at 96x72 for the hottest sliding window of inter-frame difference. On 00223 the spread between the quietest and hottest sustained 10 s windows is 10.6x, which is the argument for not eyeballing it. Hottest is t=539.4s, the Singe endgame. There, with the fixed lam the CLI uses, sasi overshoots 110 -> 129.6 KB/s (+18%) and scsi 280 -> 373.8 KB/s (+34%). Rate control moves from "insurance, not a fix" to required, and is promoted above the full-disc survey. The bus is not broken -- 381.6 KB/s still fits the 488 KB/s figure -- so FINDINGS 21 survives, at 78% of the pipe instead of a comfortable margin. Three further corrections fall out: - The two largest streams on the disc are bonus material. 00216 is the feature with a burned-in commentary PiP; 00215 is the commentary. 00223 is the clean 9.4 min. A size-ranked survey would have encoded live action. - On hard content the 256-colour scene palette (31.33 dB) binds well before the X68000 display (40.81 dB); scsi is already within 0.51 dB of it. - FINDINGS 24.5's architecture question resolves to "both paths, chosen per frame": 30-53% of frames sit above the 70% crossover. Picking per frame costs a median 37.0% of the frame budget and caps at 53.6%. Reporting for this is wired into encode.py, which previously only printed a mean over all frames -- the one statistic that cannot answer a per-frame question. extract.py takes optional start/dur; 08_mode_map.py renders source | decoded | block-mode map to .webm. Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
This commit is contained in:
@@ -31,6 +31,12 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
import numpy as np
|
||||
import vq as VQ, vq_hybrid as H, ratectl as RC
|
||||
|
||||
# Measured on the emulated 68000, FINDINGS 24. Instruction cycles against
|
||||
# zero-wait-state memory, so these are floors, not hardware predictions.
|
||||
BLIT_PCT = 53.6 # V1: compose in RAM, then a row-linear movem.l blit
|
||||
DIRECT_PCT = 76.6 # V4: write every block straight into GVRAM
|
||||
CROSSOVER_PCT = 100 * BLIT_PCT / DIRECT_PCT
|
||||
|
||||
|
||||
def pack_modes(mode):
|
||||
"""2 bits per block, MSB-first -- cheap for the 68000 to shift out."""
|
||||
@@ -133,6 +139,24 @@ def main():
|
||||
print(f" modes: SKIP {r['skip']:.1f}% V1 {r['v1']:.1f}% "
|
||||
f"V4 {r['v4']:.1f}% RAW {r['raw']:.1f}%")
|
||||
|
||||
# PER-FRAME non-SKIP distribution. The mean above cannot answer the
|
||||
# decoder-architecture question (FINDINGS 24.5): decode-direct-to-GVRAM
|
||||
# costs 76.6% of a 12fps frame budget x (non-SKIP fraction), while
|
||||
# compose-in-RAM-then-blit is a flat 53.6% regardless. They cross at 70%,
|
||||
# and that is a decision taken FRAME BY FRAME -- a scene cut is ~100%
|
||||
# non-SKIP and a held frame near 0%, so their mean describes no real frame.
|
||||
ns = np.array([100 * (mm != 0).mean() for mm in enc["modes"]])
|
||||
over = int((ns > CROSSOVER_PCT).sum())
|
||||
print(f" non-SKIP blocks/frame: median {np.median(ns):.1f}% "
|
||||
f"p90 {np.percentile(ns, 90):.1f}% max {ns.max():.1f}%")
|
||||
print(f" frames above the {CROSSOVER_PCT:.0f}% blit crossover: "
|
||||
f"{over}/{len(ns)} ({100*over/len(ns):.1f}%) -> "
|
||||
f"{'compose+blit wins on those' if over else 'direct-to-GVRAM wins throughout'}")
|
||||
cost = np.minimum(BLIT_PCT, DIRECT_PCT * ns / 100)
|
||||
print(f" display cost if the player picks the cheaper path per frame: "
|
||||
f"median {np.median(cost):.1f}% p90 {np.percentile(cost, 90):.1f}% "
|
||||
f"max {cost.max():.1f}% of a 12fps frame")
|
||||
|
||||
if a.preview:
|
||||
from PIL import Image
|
||||
f = len(idx) // 2
|
||||
|
||||
@@ -35,7 +35,13 @@ def extract(stream, outdir, fps=12, mode="crop", start=None, dur=None):
|
||||
return n
|
||||
|
||||
if __name__ == "__main__":
|
||||
# extract.py <stream> <outdir> [fps] [mode] [start_s] [dur_s]
|
||||
# start/dur matter for the long streams: 00223 is 9.4 min and the parts
|
||||
# that stress the codec are a few seconds each (tools/analysis/07 finds
|
||||
# them). Without them a survey silently averages action into idle scenes.
|
||||
stream, outdir = sys.argv[1], sys.argv[2]
|
||||
fps = int(sys.argv[3]) if len(sys.argv) > 3 else 12
|
||||
mode = sys.argv[4] if len(sys.argv) > 4 else "crop"
|
||||
extract(stream, outdir, fps, mode)
|
||||
start = float(sys.argv[5]) if len(sys.argv) > 5 else None
|
||||
dur = float(sys.argv[6]) if len(sys.argv) > 6 else None
|
||||
extract(stream, outdir, fps, mode, start, dur)
|
||||
|
||||
Reference in New Issue
Block a user