port: the runtime now says what the voice export is missing, at the moment it plays it
The manifest has carried the gap for weeks and the runtime printed '+ voice ADV' and nothing else. A reader of manifest.json gets a paragraph; a person LISTENING gets clean dialogue and no way to learn a stream is absent. NEW GAME already announces the screens it jumps over; audio had no equivalent. ManifestAudio gains -- one line naming what is KNOWN missing, absent meaning nothing is known rather than nothing is wrong -- and MenuAudio carries it so _play_video can print it. Verified on the boot's ADV and P7's S00A. The first version of the message was FALSE for one of the two assets: it said 'one is a start-truncated stream', which is ADV's story, where S00A's dropped chunks are digitally silent. Caught by reading the output for both, which I nearly skipped because the ADV line was obviously right. Now states the counts and points at the entry's why. A message generated once from a template but true only for the case it was written against is harder to see than a wrong number -- the sentence is well-formed and confident in both places. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF
This commit is contained in:
@@ -95,6 +95,18 @@ struct ManifestAudio {
|
||||
peak_dbfs: Option<f32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
duration_s: Option<f32>,
|
||||
/// 🔴 One line saying what this asset is KNOWN to be missing, for the
|
||||
/// runtime to announce. Absent means nothing is known to be missing --
|
||||
/// never that the asset was checked and is complete.
|
||||
///
|
||||
/// It exists because the export could already say this and the RUNTIME
|
||||
/// could not. `why` carries the full account, but it is a paragraph aimed
|
||||
/// at a reader of the manifest; a player hears clean dialogue and has no
|
||||
/// way to learn that a stream is absent from it. This port already
|
||||
/// announces the two measured screens NEW GAME jumps over, on the principle
|
||||
/// that a gap is announced before it is opened. Audio had no equivalent.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
incomplete: Option<String>,
|
||||
/// The game's own cue identifier where one is a NAME MATCH. Absent means
|
||||
/// nobody has claimed one -- never that the binding is unknown.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -505,6 +517,15 @@ impl From<audio::Exported> for ManifestAudio {
|
||||
why: a.why,
|
||||
peak_dbfs: a.peak_dbfs,
|
||||
duration_s: a.duration_s,
|
||||
incomplete: (a.kept_waves < a.sub_waves).then(|| {
|
||||
format!(
|
||||
"{} of {} streams. The running game decodes all {} concurrently. \
|
||||
Nothing in the audio reveals the gap -- what plays is clean dialogue. \
|
||||
WHICH streams are dropped and why differs per asset; the manifest \
|
||||
entry's `why` says, and it is not the same story twice.",
|
||||
a.kept_waves, a.sub_waves, a.sub_waves
|
||||
)
|
||||
}),
|
||||
name_match: a.name_match,
|
||||
loop_mode: a.loop_mode,
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ dies, which is what this file is for.
|
||||
|
||||
<!-- INDEX: generated by tools/port/index-decisions -- do not hand-edit -->
|
||||
|
||||
128 sections. Search this before re-deriving anything.
|
||||
129 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)
|
||||
@@ -139,6 +139,7 @@ dies, which is what this file is for.
|
||||
* [The voice export now carries every qualifying stream — and a unity sum was refused by our own check](#the-voice-export-now-carries-every-qualifying-stream--and-a-unity-sum-was-refused-by-our-own-check)
|
||||
* [Their stream assignment does not fit my region — weights NOT applied](#their-stream-assignment-does-not-fit-my-region--weights-not-applied)
|
||||
* [The resolver starts late, and my "duplicate tail" was a real stream all along](#the-resolver-starts-late-and-my-duplicate-tail-was-a-real-stream-all-along)
|
||||
* [The export knew the voice was incomplete; the runtime did not say so](#the-export-knew-the-voice-was-incomplete-the-runtime-did-not-say-so)
|
||||
|
||||
<!-- /INDEX -->
|
||||
## P0 — the exporter, 2026-08-28
|
||||
@@ -7498,3 +7499,46 @@ not have:
|
||||
✅ The assignment itself still stands — their ratio test was chosen to be immune to
|
||||
the clipping — but ⚠️ chunk 0's absolute level was measured over 62 % of its
|
||||
stream, so its 0.05 dB agreement is luckier than it reads.
|
||||
|
||||
## The export knew the voice was incomplete; the runtime did not say so
|
||||
|
||||
The manifest has carried a full account of the voice export's gap for weeks, and
|
||||
the runtime printed `+ voice ADV` and nothing else. That asymmetry is the
|
||||
dangerous one for audio specifically: a reader of `manifest.json` gets a
|
||||
paragraph, and a person **listening** gets clean dialogue with no way to learn
|
||||
that a stream is absent from it.
|
||||
|
||||
This port already governs the same situation elsewhere — NEW GAME announces the
|
||||
two measured screens it jumps over rather than skipping them silently. Audio had
|
||||
no equivalent, so:
|
||||
|
||||
* `ManifestAudio` gains **`incomplete`**, one line naming what an asset is *known*
|
||||
to be missing. Absent means nothing is known to be missing — **never** that the
|
||||
asset was checked and found complete.
|
||||
* `MenuAudio` carries it alongside the stream, and `_play_video` prints it at the
|
||||
moment the voice starts.
|
||||
|
||||
```
|
||||
+ voice ADV
|
||||
🔴 KNOWN INCOMPLETE: 2 of 3 streams. The running game decodes all 3 …
|
||||
```
|
||||
|
||||
Verified on both paths — the boot's `ADV` and P7's `S00A`.
|
||||
|
||||
### 🔴 And the first version of the message was false for one of the two assets
|
||||
|
||||
It read *"one is a start-truncated stream this export drops"*. That is `ADV`'s
|
||||
story: its dropped chunk is the start-clipped remainder of ctx0. **`S00A`'s
|
||||
dropped chunks are digitally silent** — a different reason entirely — and the
|
||||
message would have told anyone running the new-game intro something untrue about
|
||||
their own asset.
|
||||
|
||||
Caught by reading the output for both, which took one command and which I nearly
|
||||
skipped because the `ADV` line was obviously correct. The wording now states the
|
||||
counts and points at the entry's `why`, because **which streams are dropped and
|
||||
why is not the same story twice** and a single sentence cannot carry both.
|
||||
|
||||
⚠️ Worth naming as its own shape: a message generated *once* from a template but
|
||||
*true* only for the case it was written against. It is the failure mode of every
|
||||
generic warning, and it is harder to see than a wrong number because the sentence
|
||||
is well-formed and confident in both places.
|
||||
|
||||
@@ -543,6 +543,17 @@ func _play_video(name: String, skippable: bool) -> void:
|
||||
# would be authoring a sync constant nobody measured.
|
||||
if audio.play_voice(name):
|
||||
print(" + voice %s" % name)
|
||||
# 🔴 SAY WHAT IS MISSING, at the moment it is played.
|
||||
#
|
||||
# The manifest has known the voice export is incomplete for weeks and the
|
||||
# runtime did not repeat it. That asymmetry is the dangerous one for
|
||||
# audio: a reader of `manifest.json` gets a paragraph, and a person
|
||||
# LISTENING gets clean dialogue with no way to learn a stream is absent.
|
||||
# The same principle already governs NEW GAME, which announces the two
|
||||
# measured screens it jumps over rather than skipping them silently.
|
||||
var gap := audio.incomplete_for(name)
|
||||
if gap != "":
|
||||
print(" 🔴 KNOWN INCOMPLETE: %s" % gap)
|
||||
else:
|
||||
# Said out loud: silence is the audio failure that looks like success,
|
||||
# and "this cutscene is unvoiced" is a real answer for most of the disc.
|
||||
|
||||
@@ -24,6 +24,14 @@ extends Node
|
||||
var cues: Dictionary = {}
|
||||
## Role -> {stream, loop}, from the entries of kind `bgm`.
|
||||
var beds: Dictionary = {}
|
||||
## Movie name -> what that voice export is KNOWN to be missing, from the
|
||||
## manifest's `incomplete`. Empty for an asset with no known gap.
|
||||
var _voice_gaps: Dictionary = {}
|
||||
|
||||
## What `movie`'s voice export is known to be missing, or "" if nothing is.
|
||||
func incomplete_for(movie: String) -> String:
|
||||
return String(_voice_gaps.get(movie, ""))
|
||||
|
||||
## Movie name -> stream, from the entries of kind `voice`.
|
||||
##
|
||||
## A cutscene's dialogue is NOT in its `.ogv`. On this disc a movie carries music
|
||||
@@ -86,6 +94,11 @@ func configure(tree: ExportTree) -> bool:
|
||||
# authored about which recording belongs to which picture.
|
||||
stream.loop = false
|
||||
voices[String(entry["name"])] = stream
|
||||
# Carried alongside the stream so the runtime can announce a known gap
|
||||
# at the moment it plays one. Absent means nothing is KNOWN to be
|
||||
# missing -- never that the asset was checked and is complete.
|
||||
if entry.has("incomplete"):
|
||||
_voice_gaps[String(entry["name"])] = String(entry["incomplete"])
|
||||
_:
|
||||
push_warning("manifest audio entry %s has kind %s, which this build does not play"
|
||||
% [entry.get("name", "?"), entry.get("kind", "?")])
|
||||
|
||||
Reference in New Issue
Block a user