From fc4d466b8bcb4ec9fe8f54d8b1d99230c4f60101 Mon Sep 17 00:00:00 2001 From: Daniel Hobi Date: Sun, 23 Aug 2026 14:56:15 +0000 Subject: [PATCH] "SOLVED" was a note to a reader of the previous page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two headings in mov.md carried a `— SOLVED` tag, and the question of what it meant has an exact answer: it is not a claim about the format and not a record of a fix. It means THIS PAGE USED TO SAY SOMETHING ELSE. `A sound can name the frame that follows it` got the tag in 29d75db, whose own message says why: the page "had described the field and concluded 'no shipped movie uses the jump field', which is what made this a shipped bug". That bug is #2 — the pocket watch playing 6.97 s of a 27.1 s monologue and then parking on the frame for ever. `A movie carries its own pacing` predates the squashed import, but its body is the same shape: the port derived a frame rate from the soundtrack for years and did not have to. So the tag is addressed to somebody who read the page before, and there are exactly two of them in the doc set with no OPEN or UNSOLVED counterpart anywhere — which leaves a reader today asking which of the other twenty sections are unsolved. The answer is none of them; there was never an axis. Dropped, and the history kept where it belongs. The pacing section already opened with its own ("derived a frame rate for years"), so that one only needed the recovery moved into the prose. The sound section was missing the better half of the story, which is now in it: the page did not merely fail to know, it published the opposite, and a documented wrong answer is worse than a documented gap because nothing goes looking for it. This also shortens an anchor that had broken twice — six links pointed at `#a-movie-carries-its-own-pacing-—-solved-out-of-the-demo-build-s-engine`, an em dash and an apostrophe in a slug. All six updated. What this deliberately does NOT do is strip the voice. `## The behaviour rules that took a while to pin down` is a sibling heading and stays: it is prose about the subject. `— SOLVED` was a tag about the document. Docs only. Build clean, every anchor resolving. Co-Authored-By: Claude Opus 5 (1M context) --- docs/editors/movies.md | 2 +- docs/engine/formats/mov.md | 24 +++++++++++++++--------- docs/engine/runtime/host.md | 2 +- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/docs/editors/movies.md b/docs/editors/movies.md index dc8608b..0b81c58 100644 --- a/docs/editors/movies.md +++ b/docs/editors/movies.md @@ -16,7 +16,7 @@ segment picker and why every edit is addressed to the segment it was made in. | Part | What you can do with it | |------|-------------------------| | the **segment picker** | a film is a **chain of segments** ([MOV](../engine/formats/mov.md#a-file-is-a-chain-of-segments)), and this is where you move between them — each option gives its frame count and the container its header sits at, which is the index every location inside it is relative to. Hidden when the file has only one. Switching restarts the decode chain and the frame cursor, because each segment's frames are delta-encoded against a fresh buffer, exactly as the player treats them | -| the **picture** | scrub any frame, step through, or **▶ Follow the machine** — which plays at the film's **own authored pace** (`frameHoldMs`, the same rule the game uses; see [pacing](../engine/formats/mov.md#a-movie-carries-its-own-pacing-—-solved-out-of-the-demo-build-s-engine)) and stops where the movie itself stops: on a frame with regions, saying so, because that frame waits for a click | +| the **picture** | scrub any frame, step through, or **▶ Follow the machine** — which plays at the film's **own authored pace** (`frameHoldMs`, the same rule the game uses; see [pacing](../engine/formats/mov.md#a-movie-carries-its-own-pacing)) and stops where the movie itself stops: on a frame with regions, saying so, because that frame waits for a click | | **clicking it** | does what the movie would do with that click. A region's type 2 really jumps to its target frame; an exit or a chain to another file is reported, since there is no sequence here to leave. A click outside every region does nothing, exactly as in game | | the **frame list** | every frame with its action, its outgoing edges (`→target`, `⇒event`, exit), its entry sound and whether it is an action frame — filterable, and "only frames that wait" reduces a 300-frame cutscene to the handful that are interactive | | a **frame's logic** | its name, its action code, and the three names it carries (entry sound, chained movie, target frame). A frame with no logic container says so: it is a plain animation frame, and there is nowhere to put an action | diff --git a/docs/engine/formats/mov.md b/docs/engine/formats/mov.md index 659b969..3da1b5c 100644 --- a/docs/engine/formats/mov.md +++ b/docs/engine/formats/mov.md @@ -114,7 +114,7 @@ downstream has to know which segment it is looking at. |-------|----------| | Header `+0x02` | i32 **format version** — 4 (4.0) is the only one the reader accepts | | Header `+0x18` | **playback flags** — see [Escaping a movie](#escaping-a-movie) | -| Header `+0x1c` | i32 **minimum frame hold**, in ticks — the film's own frame rate, and a FLOOR rather than a cap. See [pacing](#a-movie-carries-its-own-pacing-—-solved-out-of-the-demo-build-s-engine) | +| Header `+0x1c` | i32 **minimum frame hold**, in ticks — the film's own frame rate, and a FLOOR rather than a cap. See [pacing](#a-movie-carries-its-own-pacing) | | Header `+0x24`/`+0x26` | i16 x/y **screen origin** — where the picture sits on the 512×384 screen. (0,0) everywhere but the demo's letterboxed 512×264 films, which centre themselves with (0,60); the engine draws there and subtracts it from the mouse before region hit-testing (`0x44ad08`) | | Header `+0x28` | i32 the CD drive's **KB/s**, written at load — read only to compute the cue table's streaming lead (below), and 300 (2× CD-ROM) when it is 0 | | Header `+0x2c` | i32 container index of the **next segment**, 0 = last | @@ -149,7 +149,7 @@ type-2 goto to frame 5) and escapes it with the single cue Without the cue, playback ping-pongs 5↔6 forever, which was exactly the reported symptom ("behaves normally at the beginning, then never advances"). -### A sound can name the frame that follows it — SOLVED +### A sound can name the frame that follows it The second name field of a one-shot record (`+0x1a`) is the frame playback jumps to **when that sound ends**, and it works like a cue: it comes due out of any @@ -166,7 +166,12 @@ all of them this one film — and it is load-bearing: the pocket watch's monolog is five chunks over ONE still picture, chained `01 → "blah1" → 02 → "blah2" → 03 → "blah5" → 06 → "blah6" → 07 → "endwatch"`, 27.1 s in all. The port fired the first chunk and sat on the frame for ever -("missing voice line upon picking up watch, plays only first part of it"). The +("missing voice line upon picking up watch, plays only first part of it"). + +**This page is why.** It described the field and then concluded that no shipped +movie used it, so the port dropped it — and a documented wrong answer is worse +than a documented gap, because nothing goes looking for it. Measured, 30 records +across the six editions use the field, all of them this one film. The watch frames also show what the clearing rule is for: every one of them carries an exit region and a whole-screen region to `endwatch`, whose entry sound is `sil` — a second of **silence** that both takes the channel from the line and @@ -185,7 +190,7 @@ frame record's `+16`. Its whole layout: | Offset | Type | Field | |-------:|------|-------| | +0 | i16 | the frame's own **action type** (the codes below) | -| +2 | i32 | how long the frame is **held**, in ticks — see [pacing](#a-movie-carries-its-own-pacing-—-solved-out-of-the-demo-build-s-engine) | +| +2 | i32 | how long the frame is **held**, in ticks — see [pacing](#a-movie-carries-its-own-pacing) | | +6 | byte | per-frame **flag bits** (same section) | | +0x12 | pstr | **entry sound** (below) | | +0x22 | pstr | **event** — a movie to chain to (types 3/4) | @@ -270,7 +275,7 @@ These are the non-obvious rules, verified against `MENU.MOV`, `TURKNMES.MOV`, `CURTAINS.MOV`, `BEDLAMP.MOV` and `FAUCET.MOV`: - **Interactive movies open as a silent still** and pause on region frames - (starting with the first) — *unless* the frame sets [flags bit 2](#a-movie-carries-its-own-pacing-—-solved-out-of-the-demo-build-s-engine), + (starting with the first) — *unless* the frame sets [flags bit 2](#a-movie-carries-its-own-pacing), which says "these regions are live but do not stop for them". Clicks **outside** any region do nothing. - **A click plays the region's sound, then jumps** per its type. A target that @@ -379,7 +384,7 @@ measurement of the wrong thing. Its frames are authored at **50 ms**; the water repeats about three times under the 3.62 s sound. **Regionless cutscenes** carry their own per-frame timing — see -[A movie carries its own pacing](#a-movie-carries-its-own-pacing-—-solved-out-of-the-demo-build-s-engine) +[A movie carries its own pacing](#a-movie-carries-its-own-pacing) below, which is the rule the player uses. What the file does *not* say is whether a movie is self-paced at all, and that is the one pacing decision still the port's: `chooseFrameInterval` in @@ -420,10 +425,11 @@ matter for playing the audio itself: loop-table bed as the backstop. The bed is halted with the film either way — `TI.EXE`'s own teardown does that on the no-next-segment path (`0x449d40`). -### A movie carries its own pacing — SOLVED, out of the demo build's engine +### A movie carries its own pacing -The port derived a frame rate from the soundtrack for years. It did not have to: the -timing is **in the file**, and it is per frame. +Recovered out of the demo build's engine. The port derived a frame rate from the +soundtrack for years and did not have to: the timing is **in the file**, and it +is per frame. | Where | Field | |-------|-------| diff --git a/docs/engine/runtime/host.md b/docs/engine/runtime/host.md index 1d4d5da..24f782d 100644 --- a/docs/engine/runtime/host.md +++ b/docs/engine/runtime/host.md @@ -842,7 +842,7 @@ jump fires out of any wait, a modal region wait included; each fires once, on th segment's own clock. **Pacing is the film's, not ours.** `frameHoldMs` — `max(frame hold, segment -floor)`, [recovered from the demo build](../formats/mov.md#a-movie-carries-its-own-pacing-—-solved-out-of-the-demo-build-s-engine) +floor)`, [recovered from the demo build](../formats/mov.md#a-movie-carries-its-own-pacing) — decides every advance. `chooseFrameInterval` only decides *whether* a movie is self-paced at all (0 = a click-through close-up); a frame authored to wait for the spoken line holds until the movie's own event sounds are done. The soundtrack is