docs: what MEDIA-01 enforces in state-media-v1 section 2

Two readings of the section that are now checked rather than assumed: the
bootstrap window is exactly the boundaries where max(0, boundary - delay)
is zero and its repeated O[0] is the same artifact, and a chunk that
continues the previous one exactly cannot claim a discontinuity, which is
what keeps the restore rule checkable instead of advisory.
This commit is contained in:
acamilo 2026-09-22 14:54:48 +00:00
parent 8d999b2881
commit 903629db00
2 changed files with 53 additions and 1 deletions

View file

@ -65,6 +65,21 @@ the sample position relative to the episode's configured audio origin, with inte
firstSample/sampleRate. Crash restore preserves sample position under a new epoch; first
chunk marks discontinuity. Within an epoch, chunks cannot overlap or go backwards.
**Amendment, 2026-09-22 (MEDIA-01).** Two readings of the paragraphs above, made explicit
because they are now enforced:
- The bootstrap window is exactly the boundaries where `max(0, boundary - observationDelaySteps)`
is zero, that is `boundary <= observationDelaySteps`. Inside it the repeated `O[0]` is the
**same artifact**, not a fresh render of the same scene; outside it the producing boundary
advances one per step, and a frame from any other boundary -- older or newer -- is a step
failure. A producer therefore keeps a queue of `observationDelaySteps + 1` frames and nothing
more, so there is no older frame available to substitute.
- Within an epoch, `discontinuity` marks a range the stream actually skipped. The first chunk
after a restore marks it, and a later chunk may mark it when it starts past where the previous
chunk ended; a chunk that continues the previous one exactly is continuous by construction and
its flag is refused. Without that reading the restore rule is advisory, because a stream could
set the flag on every chunk and satisfy it by accident.
The environment provides **native game output**. Sensor transformations belong to the agent
profile. Resizing for viewers, overlays, composition, audio mixing/resampling, encoding,
browser delivery and streaming belong to the application/presentation layer. No bus or

View file

@ -24,7 +24,7 @@ use fly_session::media::{
};
use fly_session::phase::Phase;
use fly_session::types::*;
use fly_session_types::media::{check_imported_asset, require_finite_samples};
use fly_session_types::media::{AudioTimeline, check_imported_asset, require_finite_samples};
both_transports!(
one_shared_image_reaches_both_agents_through_owned_attachments,
@ -38,6 +38,7 @@ both_transports!(
one_audio_chunk_per_boundary_with_an_exact_sample_budget,
required_agent_input_is_never_coalesced_while_spectator_snapshots_are,
a_persistent_asset_and_a_transient_artifact_are_different_identities,
a_restored_audio_source_resumes_and_marks_the_discontinuity,
);
const STEPS: u64 = 3;
@ -466,6 +467,7 @@ async fn a_persistent_asset_and_a_transient_artifact_are_different_identities(vi
router.stats().sealed_artifacts == before
})
.await;
assert!(registry.contains(&asset));
assert_eq!(
registry.resolve(&asset).expect("still installed"),
body.as_bytes(),
@ -474,6 +476,41 @@ async fn a_persistent_asset_and_a_transient_artifact_are_different_identities(vi
f.shutdown().await;
}
/// A restored epoch resumes the preserved sample position, and its first chunk marks the
/// discontinuity that says so. The producer and the validator agree about which epoch it is.
async fn a_restored_audio_source_resumes_and_marks_the_discontinuity(via: Via) {
let f = default_fixture(via).await;
let client = f.harness.observer().await.unwrap();
let descriptor = fly_session::environment::CounterEnvironment::audio_descriptor();
let step = hz(f.harness.config.step_hz).expect("a positive cadence");
let resumed_at = 2_400;
let mut source = AudioSource::restored_at(descriptor.clone(), resumed_at);
let (first, _first_handle) = source
.produce(&client, &step, 3)
.await
.expect("the restored source produces");
assert_eq!(first.first_sample, resumed_at, "the sample position is preserved");
assert!(first.discontinuity, "the first chunk after a restore marks it");
let (second, _second_handle) = source
.produce(&client, &step, 3)
.await
.expect("the next chunk");
assert!(!second.discontinuity, "only the first chunk of the epoch marks it");
assert_eq!(second.first_sample, resumed_at + first.sample_frames);
// The validator accepts exactly this sequence under a restored timeline, and refuses it
// under a fresh one: the flag is what distinguishes the two epochs.
let mut restored = AudioTimeline::restored_at(&descriptor, resumed_at);
restored.accept(&first, &descriptor).expect("the restored epoch");
restored.accept(&second, &descriptor).expect("and its next chunk");
let mut fresh = AudioTimeline::fresh(&descriptor, resumed_at);
fresh
.accept(&first, &descriptor)
.expect_err("a fresh episode's first chunk is not a discontinuity");
f.shutdown().await;
}
/// The sample budget is exact when the cadence does not divide the sample rate: 8 kHz at 60 Hz
/// is 133, 133, 134 and the total after three steps is exactly 400.
#[test]