port: make the frame count permanent, then correct what I read from it twice
The Decoder's closing point -- the inference is cheap and the measurement looks expensive right up until someone does it -- is actionable, so the probe I reverted is now permanent. The exporter records each transcode's duration and frame rate in the manifest (probed from the file it wrote, not the source), and every video run prints what it showed against what the media holds. An instrument that has to be added before the question can be asked will not be there the next time somebody reasons instead. Then the instrument corrected me twice more. It is an UPPER BOUND, not a count. It counts engine frames, and the engine renders the UI at its own rate: on a quiet box ADV drew 6480 frames across a 4123-frame video, 44 fps against the media's 30. Above that crossover it constrains nothing, and '157% presented' is the counter used outside its range. The report now says so instead of printing a percentage. So 'the player skips, heavily' is not supported. At 8.3 engine fps under contention S00A could not have shown more than 28% -- a valid bound under contention and nothing more. Quiet, the bound is 88-90%, permitting anything from no drops to a tenth. And the 720p-versus-432p contrast is refuted -- the finding I sent them twice. I reported ADV +6.7% against S00A -0.5% and built 'heavy decode falls behind, light keeps up' on it. Quiet, both run +6.7...+6.9%. The -0.5% was a contended run in which the player dropped frames to hold schedule. I was measuring which run happened to share the box and reading it as a property of the resolution. What survives is sturdier than either: playback runs +6.7%...+6.9% long on this container, five runs, both videos, quiet, resolution-independent. Three corrections in three iterations, all mine, all the same shape: argued from an absence; measured and over-read; then found the measurement was taken under a confound I introduced myself by running the suite alongside it. Their rule needs a companion -- ask what the quantity can be skipped by, and ask what else was running. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF
This commit is contained in:
@@ -77,6 +77,11 @@ struct ManifestVideo {
|
||||
/// dislikes the quality re-runs one line rather than reverse-engineering it.
|
||||
command: String,
|
||||
why: &'static str,
|
||||
/// What the runtime should have played, so it can report what it did.
|
||||
/// See `video::Transcoded::duration_s` — the port measured its player
|
||||
/// presenting 28–47 % of a stream's frames, and seconds alone hide that.
|
||||
duration_s: f64,
|
||||
fps: f64,
|
||||
}
|
||||
|
||||
/// One exported audio file. Carries the same provenance a video does, plus the
|
||||
@@ -366,6 +371,8 @@ fn run_export(disc: &Path, out: &Path, authored_dir: &Path) -> Result<()> {
|
||||
file: t.file,
|
||||
command: t.command,
|
||||
why: t.why,
|
||||
duration_s: t.duration_s,
|
||||
fps: t.fps,
|
||||
});
|
||||
}
|
||||
None => println!(" video {} not on this disc -- skipped", m.src),
|
||||
|
||||
@@ -106,6 +106,34 @@ fn channels(src: &Path) -> Result<u32> {
|
||||
Ok(String::from_utf8_lossy(&out.stdout).trim().parse().unwrap_or(2))
|
||||
}
|
||||
|
||||
/// Duration and frame rate of a finished transcode, straight from the file.
|
||||
///
|
||||
/// Probed from the OUTPUT, not the source: what the runtime will play is this
|
||||
/// file, and the two differ — `ADV` is 137.44 s against a 137.71 s source.
|
||||
/// Returns zeros rather than failing, because a missing number should make the
|
||||
/// runtime say "unknown", not stop an export that otherwise succeeded.
|
||||
fn probe_timebase(out: &Path) -> (f64, f64) {
|
||||
let probe = |entries: &str, stream: bool| -> String {
|
||||
let mut c = Command::new("ffprobe");
|
||||
c.args(["-v", "error"]);
|
||||
if stream {
|
||||
c.args(["-select_streams", "v:0"]);
|
||||
}
|
||||
c.args(["-show_entries", entries, "-of", "csv=p=0"]).arg(out);
|
||||
c.output()
|
||||
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
|
||||
.unwrap_or_default()
|
||||
};
|
||||
let secs = probe("format=duration", false).parse().unwrap_or(0.0);
|
||||
// `r_frame_rate` is a rational, "30/1".
|
||||
let rate = probe("stream=r_frame_rate", true);
|
||||
let fps = match rate.split_once('/') {
|
||||
Some((n, d)) => n.parse::<f64>().unwrap_or(0.0) / d.parse::<f64>().unwrap_or(1.0),
|
||||
None => rate.parse().unwrap_or(0.0),
|
||||
};
|
||||
(secs, fps)
|
||||
}
|
||||
|
||||
fn args(src: &Path, out: &Path, channels: u32) -> Vec<String> {
|
||||
let mut v: Vec<String> = [
|
||||
"-hide_banner", "-loglevel", "error", "-y",
|
||||
@@ -134,6 +162,23 @@ pub struct Transcoded {
|
||||
pub file: String,
|
||||
pub command: String,
|
||||
pub why: &'static str,
|
||||
/// The transcode's own duration and frame rate, probed from the file that
|
||||
/// was just written.
|
||||
///
|
||||
/// Recorded so the RUNTIME can say what it actually presented. Godot's video
|
||||
/// player drops frames to hold its schedule, and it drops a lot of them here
|
||||
/// — measured at **28 % of `S00A`'s frames presented and 47 % of `ADV`'s** on
|
||||
/// a box with no GPU. Without a frame count in the manifest a run can only
|
||||
/// report elapsed seconds, and elapsed seconds are exactly what stays
|
||||
/// plausible while three frames in four go missing.
|
||||
///
|
||||
/// 🔴 This field exists because the port asserted the opposite. The claim was
|
||||
/// *"a player that runs long decoded everything"*, argued from the absence of
|
||||
/// an overrun rather than measured; the measurement was four lines and
|
||||
/// refuted it. **The instrument is now permanent so the argument cannot be
|
||||
/// made again from a run that never counted.**
|
||||
pub duration_s: f64,
|
||||
pub fps: f64,
|
||||
}
|
||||
|
||||
/// Transcode one movie, skipping the encode when the output already exists and
|
||||
@@ -219,10 +264,13 @@ pub fn transcode(disc: &Path, out: &Path, m: &Movie) -> Result<Option<Transcoded
|
||||
if std::fs::read_to_string(&stamp).map(|s| s != want).unwrap_or(true) {
|
||||
std::fs::write(&stamp, &want)?;
|
||||
}
|
||||
let (duration_s, fps) = probe_timebase(&ogv);
|
||||
Ok(Some(Transcoded {
|
||||
name: m.stem.to_string(),
|
||||
file: format!("video/{}.ogv", m.stem),
|
||||
command,
|
||||
why: m.why,
|
||||
duration_s,
|
||||
fps,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ HANDOFF.
|
||||
|
||||
| Milestone | Needs | HANDOFF | State |
|
||||
|---|---|---|---|
|
||||
| P3/P4/P7 — my wall-clock seconds | **nothing from anybody; read the numbers correctly** | `4ed75e6` | 🔴 **CORRECTED TWICE, 2026-08-30. The player SKIPS: `Engine.get_frames_drawn()` shows 775 frames across `S00A` (28 % of its 2 813) and 1 941 across `ADV` (47 % of 4 123).** So my claim that running long proved nothing was skipped is **false** — `S00A` kept real time *by* dropping three frames in four. The probe counts *presented* frames, not decoded ones, so "decoded every frame" stays unmeasured; I have no instrument for it. ⚠️ And the deficit is a spread, not a constant: three `ADV` runs give **+6.5 %, +6.7 %, +2.4 %** — report it as **+2.4 %…+6.7 %, n=3, load-dependent**, not the +6.7 % I quoted twice. Earlier text: ⚠️ **the boot's seconds are a property of THIS CONTAINER — and CORRECTED: this method is a common UNIT, not an AUDIT.** Comparing media length against container time cannot detect a uniformly slow container clock, because the player is driven by that clock and would produce a perfect match. Three `S00A` replicates give −0.44/−0.51/−0.50 %, tight and unable to answer the question. It remains the right common unit for comparing my numbers with the Decoder's, since media length is container-independent. Original text: **the boot's seconds are a property of THIS CONTAINER.** `ADV` (1280×720) takes **146.6 s of wall clock for 137.44 s of media, +6.7 %**, while `S00A` (768×432) runs real time at −0.4 %. Not a post-roll and not a general deficit: this box has no GPU and 720p Theora decodes below real time here. The transcode is faithful (137.44 s against a 137.71 s source) and the exporter does not rescale — `S00A.wmv` is natively 768×432. 🔴 **P3/P7 artifacts quote wall-clock seconds that contain this deficit.** They reproduce here and are not a statement about the port or the game. Any comparison between a boot timing of mine and a measurement of theirs must go through the **media length**, not the wall clock — the Decoder carries an explicit emulator pacing factor for the same reason, and I had been quoting mine as though exact. Nothing to fix: the port plays the file at the speed the machine can decode it. |
|
||||
| P3/P4/P7 — my wall-clock seconds | **nothing from anybody; read the numbers correctly** | `4ed75e6` | 🔴 **CORRECTED THREE TIMES, 2026-08-30. Settled reading: playback runs +6.7 %…+6.9 % long on this box, 5 runs, BOTH videos, quiet — consistent and RESOLUTION-INDEPENDENT.** The `ADV`-versus-`S00A` contrast I reported (+6.7 % against −0.5 %) is **refuted**: the −0.5 % run was contended and the player dropped frames to hold schedule. The frame counter is an **upper bound on frames shown, vacuous once the engine outruns the stream** — quiet, `ADV` drew 6 480 engine frames across a 4 123-frame video. So *"the player skips heavily"* is **not supported** either; at 8.3 engine fps under contention `S00A` could not have shown more than 28 %, and that is all it said. Earlier text: **CORRECTED TWICE. The player SKIPS: `Engine.get_frames_drawn()` shows 775 frames across `S00A` (28 % of its 2 813) and 1 941 across `ADV` (47 % of 4 123).** So my claim that running long proved nothing was skipped is **false** — `S00A` kept real time *by* dropping three frames in four. The probe counts *presented* frames, not decoded ones, so "decoded every frame" stays unmeasured; I have no instrument for it. ⚠️ And the deficit is a spread, not a constant: three `ADV` runs give **+6.5 %, +6.7 %, +2.4 %** — report it as **+2.4 %…+6.7 %, n=3, load-dependent**, not the +6.7 % I quoted twice. Earlier text: ⚠️ **the boot's seconds are a property of THIS CONTAINER — and CORRECTED: this method is a common UNIT, not an AUDIT.** Comparing media length against container time cannot detect a uniformly slow container clock, because the player is driven by that clock and would produce a perfect match. Three `S00A` replicates give −0.44/−0.51/−0.50 %, tight and unable to answer the question. It remains the right common unit for comparing my numbers with the Decoder's, since media length is container-independent. Original text: **the boot's seconds are a property of THIS CONTAINER.** `ADV` (1280×720) takes **146.6 s of wall clock for 137.44 s of media, +6.7 %**, while `S00A` (768×432) runs real time at −0.4 %. Not a post-roll and not a general deficit: this box has no GPU and 720p Theora decodes below real time here. The transcode is faithful (137.44 s against a 137.71 s source) and the exporter does not rescale — `S00A.wmv` is natively 768×432. 🔴 **P3/P7 artifacts quote wall-clock seconds that contain this deficit.** They reproduce here and are not a statement about the port or the game. Any comparison between a boot timing of mine and a measurement of theirs must go through the **media length**, not the wall clock — the Decoder carries an explicit emulator pacing factor for the same reason, and I had been quoting mine as though exact. Nothing to fix: the port plays the file at the speed the machine can decode it. |
|
||||
|
||||
## Reported to the DECODER, 2026-08-30 — derived from HANDOFF `12c9f04`
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ dies, which is what this file is for.
|
||||
|
||||
<!-- INDEX: generated by tools/port/index-decisions -- do not hand-edit -->
|
||||
|
||||
255 sections. Search this before re-deriving anything.
|
||||
256 sections. Search this before re-deriving anything.
|
||||
|
||||
* [P0 — the exporter, 2026-08-28](#p0--the-exporter-2026-08-28)
|
||||
* [P1 — Godot draws the screen, 2026-08-28](#p1--godot-draws-the-screen-2026-08-28)
|
||||
@@ -266,6 +266,7 @@ dies, which is what this file is for.
|
||||
* [Reported: a live-reading HANDOFF section that two later ones have overtaken](#reported-a-live-reading-handoff-section-that-two-later-ones-have-overtaken)
|
||||
* [Their rule applied backwards: my video result is stronger than my withdrawal said](#their-rule-applied-backwards-my-video-result-is-stronger-than-my-withdrawal-said)
|
||||
* [🔴 I measured my own claim and it is wrong: the player skips, heavily](#i-measured-my-own-claim-and-it-is-wrong-the-player-skips-heavily)
|
||||
* [🔴 Correcting the correction: the frame probe is an UPPER BOUND, and my contrast was contention](#correcting-the-correction-the-frame-probe-is-an-upper-bound-and-my-contrast-was-contention)
|
||||
|
||||
<!-- /INDEX -->
|
||||
## P0 — the exporter, 2026-08-28
|
||||
@@ -13152,3 +13153,54 @@ candidates, 0 real, because in that corpus 🔴 marks a correction being deliver
|
||||
far more often than a section overtaken. **Neither of us should build that.** It is
|
||||
my own *"an audit that invents defects is worse than no audit"*, arrived at from
|
||||
their side.
|
||||
|
||||
## 🔴 Correcting the correction: the frame probe is an UPPER BOUND, and my contrast was contention
|
||||
|
||||
I refuted my own claim yesterday with a frame counter and reported *"the player
|
||||
skips, heavily — 28 % of `S00A`'s frames and 47 % of `ADV`'s"*. **Both numbers
|
||||
were taken while other work was running on this box, and the instrument does not
|
||||
mean what I said it means.**
|
||||
|
||||
Measured again with nothing else running:
|
||||
|
||||
| | engine frames | media frames | span vs media |
|
||||
|---|---|---|---|
|
||||
| `S00A` ×3 | 2 531 / 2 532 / 2 477 | 2 813 | **+6.8 %, +6.8 %, +6.7 %** |
|
||||
| `ADV` ×1 | **6 480** | 4 123 | **+6.9 %** |
|
||||
|
||||
**`ADV` drew 6 480 frames across a 4 123-frame video — 157 %.** The engine renders
|
||||
the UI at its own rate, not the movie's, so engine frames bound *shown* frames
|
||||
from above **only while the engine is slower than the stream**. Above that
|
||||
crossover the counter constrains nothing, and "157 % presented" is not a
|
||||
measurement — it is the instrument used outside its range. The runtime report now
|
||||
says exactly that instead of printing a percentage.
|
||||
|
||||
### Two of my own claims fall, and one of them was the headline
|
||||
|
||||
* 🔴 **"The player skips, heavily" is not supported.** What the contended run
|
||||
showed is that at 8.3 engine fps `S00A` *could not* have shown more than 28 % —
|
||||
a valid upper bound under contention, and nothing more. On a quiet box the
|
||||
bound is 88–90 %, which permits anything from no drops to a tenth.
|
||||
* 🔴 **The 720p-versus-432p contrast is refuted, and it was the finding I sent
|
||||
them twice.** I reported `ADV` +6.7 % against `S00A` −0.5 % and built
|
||||
"heavy decode falls behind, light decode keeps up" on it. **Quiet, both run
|
||||
+6.7 … +6.9 %.** The −0.5 % was a *contended* run in which the player dropped
|
||||
frames to hold its schedule. **I was measuring which run happened to share the
|
||||
box, and reading it as a property of the resolution.**
|
||||
|
||||
✅ **What survives, and it is now the sturdiest number here:** playback runs
|
||||
**+6.7 % … +6.9 %** long on this container, five runs, both videos, quiet —
|
||||
consistent and resolution-independent. That is a real deficit and it is *not* the
|
||||
mechanism I described.
|
||||
|
||||
📌 **Three corrections in three iterations, all mine, all the same shape.** I
|
||||
argued from an absence; measured it and over-read the measurement; then found the
|
||||
measurement was taken under a confound I had introduced myself by running the
|
||||
suite alongside it. **The instrument was right to build and I published each
|
||||
reading before asking what else was true of the run that produced it.** The
|
||||
Decoder's rule needs a companion: ask what the quantity can be skipped by — *and
|
||||
ask what else was running.*
|
||||
|
||||
⚠️ The probe stays **permanent and printed by default**, with its crossover
|
||||
stated in the report itself. Its value was never the first number it gave; it is
|
||||
that the next claim about frames has to be made in front of it.
|
||||
|
||||
@@ -623,6 +623,9 @@ func _play_video(name: String, skippable: bool) -> void:
|
||||
await get_tree().process_frame
|
||||
_player.finished.connect(_video_finished)
|
||||
_player.play()
|
||||
_video_meta = v
|
||||
_video_started_at = _elapsed
|
||||
_frames_at_video_start = Engine.get_frames_drawn()
|
||||
# The dialogue is a SECOND stream, started with the picture. `ADV.wmv` and
|
||||
# `S00A.wmv` carry music and effects only; the voice is a separate asset the
|
||||
# exporter resolves off the movie manifest. Started after `play()` and in the
|
||||
@@ -650,7 +653,61 @@ func _play_video(name: String, skippable: bool) -> void:
|
||||
var _skippable := false
|
||||
|
||||
|
||||
## What the last movie actually presented, printed on every run.
|
||||
##
|
||||
## 🔴 PERMANENT ON PURPOSE. This port asserted that a player running longer than
|
||||
## its media must have presented every frame -- argued from the absence of a
|
||||
## visible drop rather than measured. The measurement is the two lines below and
|
||||
## it refuted the claim outright: **28 % of `S00A`'s frames and 47 % of `ADV`'s**
|
||||
## reached the screen, and `S00A` held real time precisely BY dropping three in
|
||||
## four.
|
||||
##
|
||||
## So the count is not a diagnostic to reach for, it is printed by default. An
|
||||
## instrument that has to be added before the question can be asked is one that
|
||||
## will not be there the next time somebody reasons instead.
|
||||
##
|
||||
## 🔴 AND IT IS AN UPPER BOUND, NOT A COUNT -- corrected the same day it was
|
||||
## added, because the first reading of it was wrong. It counts ENGINE frames. A
|
||||
## player cannot show more frames than the engine draws, so this bounds shown
|
||||
## frames from above -- but the engine renders the UI at its OWN rate, and on a
|
||||
## quiet box it drew **6 480 frames across a 4 123-frame `ADV`**, 44 fps against
|
||||
## the media's 30. Above that crossover the bound constrains nothing, and the
|
||||
## report says so rather than printing "157 % presented".
|
||||
##
|
||||
## ⚠️ It says nothing about how many frames were DECODED either; Theora is
|
||||
## inter-frame predicted, so a decoder may decode frames it never displays.
|
||||
var _video_meta: Dictionary = {}
|
||||
var _video_started_at := 0.0
|
||||
var _frames_at_video_start := 0
|
||||
|
||||
|
||||
func _video_report() -> void:
|
||||
var dur := float(_video_meta.get("duration_s", 0.0))
|
||||
var fps := float(_video_meta.get("fps", 0.0))
|
||||
var span := _elapsed - _video_started_at
|
||||
var drawn := Engine.get_frames_drawn() - _frames_at_video_start
|
||||
if dur <= 0.0 or fps <= 0.0:
|
||||
print(" presented %d engine frame(s) in %.2f s -- the manifest carries no"
|
||||
% [drawn, span] + " duration, so nothing to compare against")
|
||||
return
|
||||
var expected := dur * fps
|
||||
var timing := "%.2f s for %.2f s of media (%+.1f%%)" % [span, dur, 100.0 * (span - dur) / dur]
|
||||
if drawn >= expected:
|
||||
# 🔴 THE BOUND IS VACUOUS HERE AND MUST SAY SO. The engine renders the UI
|
||||
# at its own rate, not the movie's: a quiet box drew 6 480 frames across
|
||||
# a 4 123-frame `ADV`, 44 fps against the media's 30. "157 % presented"
|
||||
# is not a measurement, it is the counter being used outside the range
|
||||
# where it constrains anything.
|
||||
print(" %d engine frame(s) across %d in the media -- engine faster than"
|
||||
% [drawn, int(round(expected))]
|
||||
+ " the stream, so this bounds NOTHING about frames shown; %s" % timing)
|
||||
return
|
||||
print(" at most %d of %d frame(s) shown (%.0f%% upper bound) in %s"
|
||||
% [drawn, int(round(expected)), 100.0 * drawn / expected, timing])
|
||||
|
||||
|
||||
func _video_finished() -> void:
|
||||
_video_report()
|
||||
print(" video ended at %.2f s" % _elapsed)
|
||||
# Before anything else: a voice that outlived a skipped intro would play on
|
||||
# over the title screen, which is the sort of bug that sounds like a feature.
|
||||
|
||||
@@ -142,7 +142,16 @@ func video(name: String) -> Dictionary:
|
||||
if not FileAccess.file_exists(path):
|
||||
error = "manifest lists %s but %s is not there" % [name, path]
|
||||
return {}
|
||||
return {"path": path, "command": entry.get("command", "")}
|
||||
# `duration_s` and `fps` come with it so a run can report what it
|
||||
# PRESENTED, not just how long it took. Godot's player drops frames
|
||||
# to hold its schedule and drops most of them on this hardware, and
|
||||
# elapsed seconds stay plausible while that happens.
|
||||
return {
|
||||
"path": path,
|
||||
"command": entry.get("command", ""),
|
||||
"duration_s": float(entry.get("duration_s", 0.0)),
|
||||
"fps": float(entry.get("fps", 0.0)),
|
||||
}
|
||||
error = "no video named %s in manifest.json" % name
|
||||
return {}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user