Dawn in a clock that never runs

The Third Brother's ghost leaves the graveyard at first light. The graveyard's clock does not move. Both of those are true, and only one of them was written down.

Step 21 of docs/PLAYTEST.md reads: “Stand in the graveyard from 06:00 to 06:30. He should start going at 06:22 — the disappear animation, a Poe sound and a burst — and be gone before the day flag flips at 06:29.” Writing the automated version of that step turned up the fact that you cannot do it.

The clock is a property of the scene, and the quest lives in two scenes that hold it

gTimeSpeed is copied from the scene's own envCtx.timeIncrement when the scene loads (Scene_CommandTimeSettings, z_scene_otr.cpp:267). For the Kakariko Graveyard that value is zero. So is Kakariko Village's — which is where the quest's third roost hangs. The entire quest takes place in two scenes where dayTime never advances.

ticks of dayTime per simulated frame, measured at night
Measured, not read off the scene data: 30 stepped frames in each scene, then the same again free-running, then again merely paused. measure_clock_speed.py. Hyrule Field and Zora's River are the control — without a scene that moves, a row of zeroes could just as easily mean the test harness had frozen the world.

Which leaves the departure reachable two ways, and only two. Either the clock is written underneath him — which is what the console does and what the automated step does — or he is placed by a room load whose clock already sits inside the window, so he appears and goes in the same breath. Walk in from Kakariko at 06:25 and that is what you get.

The Sun's Song is not a third way. In a scene whose increment is zero, z_parameter.c:6875 takes its else branch: a fade to black and a scene reload on nextDayTime. A reload is the placement gate — the thing that decides whether he stands up at all — not the departure.

The gate itself, and the off-by-one that isn't there

He leaves when is_day() or (0x4400 ≤ day_time < 0x4555). That second clause is a sliver of about eight game minutes before the engine's own day flag flips, so a player watching the sky lighten sees him go at first light rather than at the instant a boolean changes.

The clock is a u16 that wraps, and that is the whole hazard. Drop the upper bound and day_time ≥ 0x4400 is also true at every hour of the evening — 0xC001, one tick past dusk, is ≥ 0x4400 as surely as 0x4400 is. He would appear at dusk and vanish one tick later, and a test that only ever looks at dawn would never see it.

The full 0x0000–0xFFFF day as a ring. Green: he stands. Red: he goes. The two arcs meet exactly twice, and both meetings were driven one tick at a time.
Sixteen ticks, each set under a standing ghost and given three frames to act on
tickclockengine sayshewhy this tick

The nine “stands” are one walk on one ghost — his actor handle is asserted unchanged the whole way, so “he stayed” is a claim about a single Poe and not about a departure quietly covered by a respawn. The walk starts one tick past dusk, crosses the wrap at 0xFFFF → 0x0000, and finishes on the last tick before first light.

The negative control

The automated step ships with a deliberate break: delete the < day_at bound from the quest script and re-run. It goes red on the chain's first tick — 0xC001 — which is exactly the failure the two-sided comparison exists to prevent. A check that cannot be made to fail is not yet a check.

“Gone before 06:29” is true — for a reason the document doesn't give

The departure is 21 simulated frames of gPoeComposerDisappearAnim, and in the graveyard it costs zero game minutes, because the clock is frozen. The document's promise holds trivially.

Move the same ghost to a scene whose clock runs and it stops holding. The window from first light to the day flag is 341 ticks. At the 20 ticks per frame measured in Hyrule Field, 21 frames of animation is 420.

He would still be dissolving roughly five game minutes after the flag flipped. The document's two-part promise holds where he actually stands, and would not hold anywhere else.

What dawn did not do

At stage 3 he walks away still owing the player a Piece of Heart. Nothing is lost: out to Kakariko by day and back leaves the graveyard empty, and returning at dusk finds him at (660, 181, 20) with the saved store byte-identical and the same line held out. Dawn ends a visit, not a quest.

That last check is done with plain scene warps rather than the usual quest-state warp, and the distinction matters more than it looks. The quest-state warp types quest stage 3 on its way through — so “the store is unchanged across the sunrise” would have been a claim about the two lines the check had just typed.

What was not verified

A footnote that cost an hour: the log that rotates

The first full run of the 23-step suite came back with step 1 red — a step that had been green for days — reporting that the game had printed nothing at all. It had printed everything. The game's log file rolls over at 10 MB, and roughly thirty boots that afternoon had rolled it over ten times.

Half the harness already knew this: one reader anchored its window with an inode and followed the rotation. The other reader — the one that defines a whole test's window — still stored a plain byte offset taken before the boot. That fails two ways, and neither of them mentions rotation:

Both readers now share one inode-anchored window. Five cases are unit-tested: no rotation, each of the two failure modes above, a missing previous file, and two rotations — which returns a superset rather than an empty slice, because every caller is hunting for a line it has just caused, and a superset costs a search while an empty slice costs a false failure.

Commit 9ba002567118 — playtest: step 21 — dawn, and the clock that never gets there.
Card headway:personal/fabric-donor-silk.
Suite after: 18 pass, 2 fail (steps 9 and 20 — two defects those steps exist to report), 3 not yet written. Step 1 green again.
Automated steps written: 19 of 23 → 20 of 23.