Skip to content

screen_loader_lines reads the firmware's text mode off the panel, counts its rows where the logo never is, and judges the panel once two dumps agree - #640

Closed
Japabu wants to merge 5 commits into
mainfrom
wt/toyos-loaderlines
Closed

Japabu wants to merge 5 commits into
mainfrom
wt/toyos-loaderlines

Conversation

@Japabu

@Japabu Japabu commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

Aims to fix the screen_loader_lines red on main. The loader is correct. The test's row counter was counting the firmware's boot logo.

Unproven until a nightly shows it. The setup that went red is CI's KVM with Debian OVMF in 240x56. That claim holds only once gh workflow run nightly.yml --ref wt/toyos-loaderlines runs with its headSha equal to this PR's head. On the shard that runs the test, its log must carry these two lines, in this order:

  [screen] the panel carries all 41 rows the console put at its edge in its 240x56 text mode
  PASS  screen_loader_lines

The review quoted the first line with , 14 of them after the GOP query at the end. This head drops that clause (see the fix below), so the line above is what the test prints now. Any mode other than 240x56 makes the root-cause table below wrong, even on a green run.

Root cause

The red. Main's nightlies 36550208853 (7e151819e) and 36696295750 (ace064f9d, guest shard 8) were red, and so were the branch nightlies 36600425263 and 36709239346. All four gave the same numbers:

FAIL screen_loader_lines: the panel carried 26 rows at the GOP query and 41 at the loader's last line, a growth of 15, where the loader printed 14 lines between them, 14 rows at 240 columns

The last green runs were 36400924827 (main a7cd32765, shard 4) and 36496779560 (shard 6). Both printed the panel grew 16 row(s) across the GOP query, 26 to 42, for 16 line(s) printed. The only loader change between a7cd32765 and 7e151819e is #583.

The mechanism. OVMF draws the TianoCore logo (BootLogoEnableLogo, which also turns the cursor off) after the console's last ClearScreen. The logo is Logo.bmp, 193x58 pixels, centred on the panel. The graphics console then draws its 8x19 cells, and it overwrites only the cells a line reaches. Main's text_row_bands counted bands of lit scanlines across the whole width, and the logo broke that count in two ways:

  • A short row printed over the logo joins it into one band taller than the pitch. That band is refused, so the row is not counted.
  • A row printed across the logo's columns erases the logo within its own cell row. The rows next to it are then no longer joined to the logo and count again.

1090cf6ea (in #583) removed the loader's 128-column RTC zone line. Since then the 165-column Black box line lands on the logo's second row and erases it there, so at the last line the GOP row counts again and the growth comes out one more than the lines printed. The model below reproduces all of these numbers.

The firmware's text mode differs by host

Round 1 read the panel's first 8 cells from x=0. On the dev host that found no lit pixel at all: the orchestrator's runs of efc6e0c21 were red three times out of three with the panel carries 0 band(s) of lit scanlines, alone, under the exclusive-GOP control, and in the whole suite.

The reason is that edk2's graphics console centres its text mode on the panel (InitializeGraphicsConsoleTextMode's DeltaX and DeltaY). It wraps a line when its row is full and scrolls the text area once the cursor passes the last row (GraphicsConsoleConOutOutputString). Which mode it draws in is the firmware build's choice:

Host Firmware Text mode Text starts at The loader's console
CI guest shards Debian ovmf-generic 240x56 x=0, y=8 41 rows, no scroll
dev host Homebrew QEMU 11.1.1's edk2-x86_64-code.fd 80x50 x=640, y=65 58 rows, so the panel scrolls and keeps the last 49

Where these modes come from. CI's serial names its mode. Nightly 36696295750, job 109873180506, carries the terminal's ESC[8;056;240t before its last ESC[2J. The dev host's mode comes from a pixel model built from edk2's source (GraphicsConsole.c, LaffStd.c, BootLogoLib.c, Logo.bmp): its cells, wrap and scroll, fed the red nightly's console and counted with main's whole-width counter. For every mode edk2 offers on a 1920x1080 panel, the model's counts at the GOP query and at the last line are:

Mode 80x25 80x50 100x31 128x40 160x42 240x56
Model: GOP query, last line 24, 24 37, 47 29, 30 29, 36 25, 39 26, 41
  • CI recorded 26 and 41. Only 240x56 gives that pair, and it is the mode CI's serial names.
  • Main's local passes in the orchestrator's logs recorded 37 to 47 (5 runs) and 38 to 47 (8 runs). Only 80x50 comes close. Its 37 and 47 come from the CI console, which is not the console those local boots printed, so it matches them closely, not exactly.
  • 80x50 also explains the round-1 red: its text starts at x=640, past the first 8 cells.

The fix

  • Ppm::firmware_text_mode reads the mode off the panel. It picks from the modes edk2 offers there (TextMode::offered: UEFI's 80x25 and 80x50, mGraphicsConsoleModeData, and the whole panel).
    • The mode's first cell column must hold the panel's leftmost lit pixel, because every row starts there.
    • Its rows must hold everything lit in its first 8 cells. That rule is what separates 80x25 from 80x50.
    • If no mode fits, or more than one does, the panel is refused by name.
  • Ppm::edge_rows reads each of that mode's rows in its first 8 cells, cell by cell. A glyph never leaves its cell. A centred logo narrower than 512 pixels never reaches those cells in any mode 80 or more columns wide. Bands, the median pitch and their refusals are gone.
  • TextMode::panel reads the same rows off the console. It takes every line since the last ESC[2J as it came off the serial and wraps it at the mode's columns. A line that fills its last row leaves a blank row after it, which is edk2's wrap followed by the line's own CRLF. A line feed on the last row scrolls the text. The review's note had TextMode::printed folded into it. printed's one other reader was the success line's N of them after the GOP query, which no judge reads, so that clause is gone too.
  • The console's lines are not stripped of CSI sequences. After the last ESC[2J the only sequence is the leading cursor-home. The review measured that it changes no row on either console. At 240 columns it lengthens the first line from 116 to 126 columns, still one row. At 80 the line takes 2 rows either way, and both scroll off.
  • The panel is judged once two dumps in a row agree. uefi-services' println! sends a line's CRLF as its own OutputString, and edk2 scrolls on that line feed at the last row. If ConSplitter writes the terminal before the graphics console, the serial line is complete before the panel has scrolled. The test therefore dumps the panel back to back, with no delay, until two consecutive dumps carry the same text mode and row map, and judges that map.
    • A dump with any row that decodes in the kernel's font is refused as the kernel's repaint, as before.
    • The loop is bounded by qemu.budget(30 s), a liveness ceiling only. If the panel is still changing when it passes, the test fails and prints both dumps' maps.
    • It calls screendump directly. screendump_while takes an Fn predicate and returns only the last dump, so carrying the previous map through it would need a RefCell.
    • Not after the kernel's first record. The review's first option was to dump after the kernel's first [kernel line on the 16550. panic_console::arm commits panic console: armed and repaints the whole panel before serial::init makes the 16550 a backend. Every dump after that line would meet the refusal. Job 108841154205's serial shows that record as the first kernel line, right after Loader log: the kernel handoff begins.
  • The test requires the console and the panel to agree row by row. When they do not, it prints both counts and both row maps. The GOP line's width no longer decides the columns, but the line must still be on the console.
  • The exclusive-GOP control now reaches this judgement. Its panel stops before the GOP line, and the model expects every row.
  • The host check firmware_rows_at_the_edge stages both real geometries with a pixel-level edk2 renderer: cells blitted whole over a centred 193x58 logo, wrapping, and a scroll that moves the text area's pixels, logo included. In each mode it requires:
    • the mode found, and both row maps matching counts derived by hand. At 80x50, 53 rows are printed, the first four scroll off, and 46 are lit. At 240x56, 50 rows are printed and 48 are lit.
    • that the exclusive-GOP control's panel, which stops after the first 45 lines, is read in the staged mode and does not carry the console's rows.

Gates

All at 6e0d7da82.

Command Result
cargo test --test toyos-checks -- firmware_rows_at_the_edge screen_decoder EXIT=0, 2 passed
cargo test --no-run --test toyos-build EXIT=0
cargo run -- --ci host EXIT=0, [ci] Host: 55 step(s), all green, checks::firmware_rows_at_the_edge ... ok
exclusive-gop.patch applied, then cargo run -- --build-only EXIT=0; the patched tree builds, and the tree was restored

Mutations of the judge and of the control, each applied as a checked patch, built, run and reverted. Every mutant built (cargo test --no-run --test toyos-checks, EXIT=0). Each was then run with cargo test --test toyos-checks -- firmware_rows_at_the_edge, and each went red:

Mutant EXIT What the check said
Edge read from x=0 (round 1's bug) 101 2 modes fit at x=640 (80x25 and 80x50)
No scroll in TextMode::panel 101 80x50: carrying 46 where the console put 50
Whole row read instead of the first 8 cells 101 80x50: carrying 47 where the console put 46
No blank row after a line that fills its last row 101 80x50: carrying 46 where the console put 47
No vertical fit in firmware_text_mode 101 2 modes fit
The 80x50 control's panel given every line 101 80x50 control: carrying 46 where the console put 46
The 240x56 control's panel given every line 101 240x56 control: carrying 48 where the console put 48

The first five mutants fail on the 80x50 case before the 240x56 case runs. The last two show that the control fires in each mode on its own.

Unsure

  • Two dumps have to fit between the scroll and the kernel's repaint. The kernel repaints the panel in panic_console::arm, just after ExitBootServices. Main's single dump after the loader's last line fitted in that window on CI's KVM: the red nightlies counted 41 rows there and did not refuse. Nobody has measured whether two dumps fit. If they do not, the test goes red with the repaint refusal and the kernel's decoded screen, not green.
  • A dump taken mid-scroll or mid-repaint that firmware_text_mode cannot read fails at once rather than being dumped again.
  • TextMode::offered copies edk2's mode table. A firmware that draws in a mode outside it is refused by name rather than counted.
  • If a future OVMF left the cursor on, the equality would go red on the cursor's cell. It would fail loudly, not pass silently.
  • The issue entries about this test's earlier sightings were left as they are: zero bands, zero growth, and the GOP-query race.

🤖 Generated with Claude Code

https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L

Japabu and others added 3 commits September 30, 2026 17:35
…s logo never is

screen_loader_lines was red on main's nightlies 36550208853 (7e15181) and
36696295750 (ace064f), and on the branch nightlies 36600425263 and
36709239346, every time with the same numbers:

    the panel carried 26 rows at the GOP query and 41 at the loader's last
    line, a growth of 15, where the loader printed 14 lines between them,
    14 rows at 240 columns

It was green on 36400924827 (main a7cd327) and 36496779560, both "26 to 42,
for 16 line(s) printed". The loader is not at fault: a growth of fifteen is a
panel that kept drawing. The instrument is.

OVMF's PlatformBootManagerAfterConsole draws the TianoCore logo after the
console's last ClearScreen (edk2 BootLogoEnableLogo: Logo.bmp, 193x58,
centred, so x 863..1055 and y 511..568 on the 1920x1080 panel), and the
graphics console then draws its 8x19 cells from the top-left corner,
overwriting only the cells a line reaches. text_row_bands scanned the whole
width: a row printed short over the logo joined it into one band taller than
the pitch and was dropped, and a row printed across the logo's columns, 107
to 131, wiped the logo out of its own cell row, freeing the rows beside it. The GOP line lands on
row 26 (y 502..520), the logo's first. At the GOP query the logo is whole
beneath it and the GOP row is dropped with it: 26 counted for 27 printed.
1090cf6 (#583) removed the loader's 128-column "RTC zone" line, so the
165-column "Black box" line now lands on row 27 and wipes the logo there;
at the last line the GOP row counts again, and the growth is one more than
the lines printed. Before it, the RTC line stopped four cells short of the
logo's right edge, the remnant kept rows 26 and 27 joined, and the root
bridges' 410-column dump (26ad88c, the same PR) gave two rows of slack.

The oracle is edk2's own source: GraphicsConsole.c (the text origin, whole
cells blitted, the wrap), LaffStd.c (the 8x19 font), BootLogoLib.c with
Logo.c and Logo.bmp (the logo and its place, and the cursor it turns off),
modelled and fed the nightlies' console lines, then counted by
text_row_bands. It gives 26 and 41 for the red lines and 26 and 42 for the
green ones, every number recorded; without the logo it gives 27, 41 and 44,
none of them. Taking the RTC line alone out of the green console turns it
red (26 to 43 for 15 lines on 16 rows); taking the root-bridge line alone
out leaves it green.

- text_row_bands reads the panel's first EDGE_CELLS cells only: every
  console row starts there, and a logo centred on the panel is not there.
  The same model counts 27, 41, 27 and 44 there, the rows printed each time.
- The test boots once, to the loader's last line, and holds the panel to
  exactly the rows the console printed since the firmware's last ESC[2J;
  screen::edge_rows computes them by the same edge rule. The GOP-query dump
  goes: an equality needs no baseline, and that dump raced the loader, which
  goes on printing while it is taken (the 23 against 24 rows recorded in
  issues/build/parallel-tests-red-under-other-suites.md). A panel the query
  stopped carries the rows up to the query and no more, and the red names
  that number beside the one it wanted.
- firmware_rows_at_the_edge, a harness check, stages a panel in edk2's
  geometry with a centred block that a long row cuts and two short rows
  join, and holds text_row_bands and edge_rows to its 15 rows. Counted across
  the whole width it is 13: that mutation of text_row_bands reds it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
The orchestrator's guest runs of efc6e0c went red with "the panel carries
0 band(s) of lit scanlines" three times out of three: alone, under the
exclusive-GOP control, and in the whole suite. The first 8 cells of the panel
were dark because the firmware this host boots does not draw its text there.

What the panel holds depends on the firmware. edk2's graphics console centres
its text mode on the panel (InitializeGraphicsConsoleTextMode's DeltaX and
DeltaY), wraps a line when its row is full, and scrolls the text area once the
cursor passes the last row (GraphicsConsoleConOutOutputString). Which mode it
draws in is the firmware build's choice:

- CI boots Debian's ovmf-generic, which draws in 240x56: text from x=0 and
  y=8, and the loader's 41 rows fit without scrolling.
- The dev host boots Homebrew QEMU 11.1.1's own edk2-x86_64-code.fd, which
  draws in 80x50: text from x=640 and y=65. At 80 columns the console fills
  58 rows, so the panel scrolls and keeps the last 49.

These modes were not read off a screendump. They come from an edk2 pixel
model: that source's cells, wrap, scroll and Logo.bmp, fed the red nightly's
console and counted with main's whole-width counter. In 80x50 it counts 37
rows at the GOP query and 47 at the last line, which are main's local passes
exactly ("37 to 47"). In 240x56 it counts CI's 26 and 41. No other mode edk2
offers on a 1920x1080 panel gives either pair. 100x31, 128x40 and 160x42 have
too few rows for 47, and 80x25 has too few for either. Only 80x50 starts its
text past the first 8 cells.

So the counter can no longer assume a margin, a column count or the absence
of a scroll:

- Ppm::firmware_text_mode picks the mode off the panel, out of the modes edk2
  offers there (TextMode::offered). It must be the one whose first cell
  column holds the leftmost lit pixel, where every row starts. Its rows must
  also hold everything lit in its first 8 cells, and that is what separates
  80x25 from 80x50.
- Ppm::edge_rows reads each of that mode's rows in its first 8 cells, cell by
  cell. A glyph never leaves its cell, and a centred logo narrower than 512
  pixels never reaches those cells. Bands, pitches and their refusals are
  gone.
- TextMode::panel is the same row read off the console: each line since the
  last ESC[2J, wrapped at the mode's columns. A line that fills its last row
  leaves a blank row after it. The text scrolls on a line feed at the last
  row.
- The test requires the two to agree row by row, and prints both maps and
  their counts when they do not. The GOP line's width no longer decides the
  columns. The exclusive-GOP control now reaches this judgement: its panel
  stops before the GOP line while the model expects every row.

firmware_rows_at_the_edge stages both real geometries with a pixel-level
edk2 renderer: cells blitted whole over a centred 193x58 logo, wrap, and a
scroll that moves the text area's pixels. It checks the mode found and the
rows against counts derived by hand: 46 lit rows in 80x50 after four
scrolled-off rows, and 48 in 240x56.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
`cargo run -- --ci host` went red at 92d4b11 on one step, clippy's
manual_is_multiple_of in TextMode::printed. This spells it the way clippy asks.

92d4b11's message gets two sentences wrong:

- "Only 80x50 starts its text past the first 8 cells" is false. Every mode
  edk2 offers on a 1920x1080 panel except 240x56 starts past them. What
  singles out 80x50 is the row counts, as the sentences before it say.
- Main's local passes were not matched "exactly". They counted 37 or 38 rows
  at the GOP query and 47 at the last line. The model counts 37 and 47 when
  fed the CI console, which is not the console those local boots printed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
@Japabu Japabu changed the title screen_loader_lines counts the panel's left edge, where the firmware's logo never is screen_loader_lines reads the firmware's text mode off the panel and counts its rows where the logo never is Sep 30, 2026
Japabu added a commit that referenced this pull request Sep 30, 2026
Reverts 51e64b3 and deletes the issue 45db225 filed for it,
issues/build/screen-loader-lines-counts-one-row-more-than-the-loader-printed.md.
The red was not a flake: the counter read OVMF's boot logo as text rows,
the cause since #583, and #640 corrects the count. The test stays with this
branch's rule as a red that is fixed rather than deleted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
@Japabu

Japabu commented Sep 30, 2026

Copy link
Copy Markdown
Collaborator Author

Orchestrator runs at 382919c (cargo test --test toyos-build -- <args>; logs orch/logs/640r2-*.log):

job args patch exit line
head screen_loader_lines — 0 PASS screen_loader_lines (2s)
exclusive-GOP control screen_loader_lines loaderlines/r2/…exclusive-gop 1 FAIL screen_loader_lines: the panel's first 8 cells carry 38 rows of text where the console put 49 there in its 80x50 text mode, row by row
whole (none) — 0 test result: ok. 498 passed, 498 total (360.3s)

@Japabu
Japabu marked this pull request as ready for review September 30, 2026 17:16
@Japabu

Japabu commented Sep 30, 2026

Copy link
Copy Markdown
Collaborator Author

Review round 1, head 382919c5b. CI host passed at this head (run 36750167870, job 110006442089, whose log shows checks::firmware_rows_at_the_edge ... ok). The only guest evidence is issuecomment-5916132737: the dev host, TCG, Homebrew edk2, 80x50.

Net lines: +282 −130 = +152. The harness and guest test (tests/common/screen.rs, tests/toyos.rs) are +182 −130 = +52. The host check (tests/checks/screen.rs, tests/checks.rs) is +100.

The brief's questions

  • Is the fix proven for CI's firmware? No, it is only modelled. The edk2 behaviour the model rests on does match upstream GraphicsConsole.c: the mode table at :42-52, centring at :276-323, wrap and then mCrLfString at :1044-1048, and the last-row line feed scrolling at :933-965. CI's own serial also names the mode: nightly 36696295750, job 109873180506, carries ESC[8;056;240t before its last ESC[2J. The PR body does not use that. But this check has never run on Debian OVMF under KVM. Its 240x56 host case is the author's own renderer, and every mutant listed in the PR body fails on the 80x50 case first.
  • What a nightly must show: run gh workflow run nightly.yml --ref wt/toyos-loaderlines, with the run's headSha equal to the head being judged. On the shard that runs the test, the log must carry [screen] the panel carries all 41 rows the console put at its edge in its 240x56 text mode, 14 of them after the GOP query and then PASS screen_loader_lines.
    • 41 and 14 are the red nightly's console counted by this model: 41 lines after the last ESC[2J, all under 240 columns and all lit in their first 8 cells, 14 of them after GOP: mode. bootloader/ is unchanged since ace064f9d.
    • Any mode other than 240x56 makes the root-cause table wrong, even on a green run.
  • Written or copied? Written by the author from edk2's source. Only the mode table's numbers and the >> 1 centring are verbatim, and those are data. None of edk2's control flow is carried over. No edk2 file or asset enters the tree.
  • A second implementation? No. The tree has no edk2 console model and no CSI stripper: git grep '\x1b' in tests/ and src/ finds only src/kernelconsole.rs's fixture, which does a different job. Inside the diff, edk2_panel re-implements wrap and scroll beside TextMode::panel. Both come from one reading of edk2, so they agree on anything that reading got wrong.
  • Is the size earned? Yes. The net +52 buys the mode inference and a row model. They replace a band counter that miscounted and a two-boot comparison, and both are deleted.

BLOCKER

  • tests/toyos.rs:3637-3639 — the one screendump is taken after the serial line arrives, not after the panel has scrolled. That is a race, and the equality check cannot tolerate it.
    • uefi-services 0.23 println! sends the line feed as its own OutputString("\r\n") (uefi 0.26 output.rs:169-209). edk2 scrolls on that line feed at the last row (GraphicsConsole.c:933-965). At 80x50 the loader's 58 rows scroll, so the last line feed moves every row.
    • wait_for_ready returns on the completed serial line (tests/common/qemu.rs:4783). If ConSplitter writes the terminal before the graphics console, the dump lands before the scroll and every row is one off. Nobody has measured that order.
    • main's range check tolerated this. The PR's own "Unsure" names it, and issues/build/parallel-tests-red-under-other-suites.md:296-348 records this test's panel lagging its marker.
    • To close it, take the dump after an event the guest emits only after the scroll. The kernel's first [kernel record on the 16550 follows ExitBootServices, and the repaint refusal stays. Alternatively, repeat screendump_while until the row maps agree, bounded, and fail with both maps.
  • PR body, "Fixes the screen_loader_lines red on main" — this claim has no measurement on the setup that was red: CI's KVM with Debian OVMF in 240x56. It needs the nightly above before The redlist and the recorded durations go: a red test is fixed or deleted with its issue, a flaky test is deleted at once, and shards deal the task list in turn #639 restores the test on it.

NOTE

  • tests/checks/screen.rs:124-139 — no staged case in the host check can fail; it only proves agreement. Add a control in both modes: edk2_panel(1920, 1080, staged, &lines[..45]) must differ from mode.panel(lines.iter().copied()).
  • tests/toyos.rs:3659-3671 — drawn changes no row on either measured console. The only CSI after the last ESC[2J is the leading cursor-home: 116→126 columns at 240, and 2 rows at 80 either way, scrolled off. Deleting it passes both runs. Delete it, or give it a host input where it decides a row.
  • tests/common/screen.rs:287 — TextMode::printed is public only for the success line's "N of them after the GOP query". Fold it into panel and drop that clause.

REMOVE

SEND BACK

Japabu and others added 2 commits September 30, 2026 20:52
The one screendump was taken as the loader's last line reached the serial.
uefi-services' println! sends that line's CRLF as its own OutputString, and
edk2's graphics console scrolls on it when the cursor is on the last row, so
a ConSplitter that writes the terminal before the graphics console lets the
dump land before the scroll, one row off everywhere. The review asked for a
dump after a later event; the kernel's first 16550 record is not one: arm()
commits `panic console: armed` and repaints the whole panel before
serial::init makes the 16550 a backend, so every dump after that record
would meet the repaint refusal.

The test now dumps the panel back to back until two dumps carry the same
text mode and row map, and judges that map. A dump the kernel has repainted
is refused as before; a panel still changing when a 30-second liveness
ceiling (budget-scaled) passes fails with both maps.

Review notes:
- firmware_rows_at_the_edge stages, in both modes, the exclusive-GOP
  control's panel: one that stopped after the 45 short lines. It must be read
  in the staged mode and must not carry the console's rows.
- The CSI stripper goes: after the last ESC[2J the only sequence is the
  leading cursor-home, which changes no row in either measured console.
- TextMode::printed is folded into TextMode::panel. Its one other reader was
  the success line's "N of them after the GOP query", which no judge reads
  and which goes with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
@Japabu Japabu changed the title screen_loader_lines reads the firmware's text mode off the panel and counts its rows where the logo never is screen_loader_lines reads the firmware's text mode off the panel, counts its rows where the logo never is, and judges the panel once two dumps agree Sep 30, 2026
@Japabu

Japabu commented Sep 30, 2026

Copy link
Copy Markdown
Collaborator Author

Orchestrator runs at 6e0d7da (logs orch/logs/640r3-*.log):

job args exit line
head screen_loader_lines 0 [screen] the panel carries all 49 rows the console put at its edge in its 80x50 text mode
exclusive-GOP control screen_loader_lines + exclusive-gop.patch 1 FAIL screen_loader_lines: the panel's first 8 cells carry 38 rows of text where the console put 49 there in its 80x50 text mode, row by row
whole (none) 1 test result: FAILED. 501 passed, 1 failed, 0 invalidated, 502 total (324.6s) — the one red is process_stats (exit 101), a filed flake (process-stats-exits-101-beside-other-guests.md) that #639 deletes

Nightly at this head: https://github.com/ToyOSOrg/ToyOS/actions/runs/36763711317 (queued behind 36760010132 at 382919c, whose twelve guest shards all passed).

@Japabu

Japabu commented Oct 1, 2026

Copy link
Copy Markdown
Collaborator Author

Review round 2, head 6e0d7da82. CI host passed at this head: run 36763674229, job 110052319695 (headSha 6e0d7da82). Its log shows checks::firmware_rows_at_the_edge ... ok and [ci] Host: 55 step(s), all green.

Round 1 BLOCKERs

  • CLOSED — tests/toyos.rs: one dump raced the last line feed's scroll. The branch now dumps back to back until two dumps agree, which is the option round 1 named. Measured green at this head by the orchestrator (80x50, 49 rows) and by nightly 36763711317, guest (10), job 110093628053 (240x56, 41 rows).
  • CLOSED — PR body, "Fixes": the claim had no measurement on CI's firmware. Nightly 36763711317 (headSha 6e0d7da82), job 110093628053, carries [screen] the panel carries all 41 rows the console put at its edge in its 240x56 text mode and then PASS screen_loader_lines (2s).

The brief's questions

  • Is the test right? It passes on both edk2 builds it has met. The exclusive-GOP control is red on the dev host at 80x50. At 240x56 the control has run only against the author's host renderer.
    • It still cannot wait on its event. After the last loader line's scroll, the guest emits nothing until the kernel repaints the panel. So "two dumps agree" stands in for the scroll by elapsed time.
    • The guest is never stopped, and the race with the repaint is now two dumps wide instead of one.
    • Both failures come out red, not green. That is a flake source on a test with five recorded reds: issues/build/parallel-tests-red-under-other-suites.md:296-348, and main's nightlies 36550208853 and 36696295750.
  • Is its oracle independent? It is independent of the code under test: the loader draws nothing, OVMF draws the panel, and the serial says what OVMF was given.
    • The expectation is not checked independently of its author. TextMode::panel and edk2_panel are one reading of edk2 plus a copy of its mode table. So the host check proves the pixel reader against that reading and cannot catch a misreading of edk2. Only the two runs on real firmware check the reading.
  • The ladder. The behaviour is one token: query_gop opens GraphicsOutput with GetProtocol and not with open_protocol_exclusive (bootloader/src/main.rs:377-395).
    • A host test cannot reach it: the host has no UEFI.
    • Metal cannot reach it: no rig reads the panel, and on the T14 loader.log already holds every line the loader prints (bootloader/src/loaderlog.rs:3-4).
    • A guest test reaches it only through a hand model of edk2's console, pixel heuristics, and a race with the repaint.
    • The bottom rung reaches the regression that actually happened (3b17d6a57): the safe open_protocol_exclusive, which is what an agent tidying away an unsafe block reaches for. Refuse it at compile time. The bootloader is already linted with -D warnings on the PR gate (job 110052319695, the two clippy: bootloader steps).
    • Cut the guest test.

Net lines: +292 −129 = +163. Production 0, tests +163. The cut deletes about 150 of main's lines instead.

BLOCKER

  • tests/toyos.rs:3748-3832 — delete screen_loader_lines rather than fix it — it is a guest-only check of a one-token decision that compile time reaches. This branch adds +163 lines where the deletion removes them. Delete, along with it:
  • clippy.toml — make uefi::table::boot::BootServices::open_protocol_exclusive a disallowed-methods entry. Each of the bootloader's ten exclusive opens then carries #[expect(clippy::disallowed_methods, reason = …)]. Show the measurement in the PR body: planting bs.open_protocol_exclusive::<GraphicsOutput>(gop_handle) in query_gop turns cargo run -- --clippy red, with its exit code and log, and green again once it is removed — why: this is the regression 3b17d6a57 fixed and the only one the cut test guarded. Serial still carries every line, so without this every other tier stays green on it.

REMOVE

  • tests/toyos.rs:472-474 — "Two boots… a count of rows against a count of lines" — false at this head: the test boots once and compares row maps.
  • PR body — every section describes code this verdict deletes, and "Unproven until a nightly shows it" has since been measured. The deletion's body names what goes, the ladder rung that replaces it, and the planted red/green clippy measurement, and nothing else.

SEND BACK

Japabu added a commit that referenced this pull request Oct 1, 2026
…es what it guarded

Red on main's nightly 36696295750 at `ace064f9d`, `guest (8)`: "the panel
carried 26 rows at the GOP query and 41 at the loader's last line, a growth
of 15, where the loader printed 14 lines between them, 14 rows at 240
columns". A red test is fixed or deleted. #640's count has run on no
nightly, and the behaviour the test guarded, `query_gop` opening
`GraphicsOutput` without EXCLUSIVE, #660 refuses at compile time:
`clippy.toml` disallows `open_protocol_exclusive`, and the loader's
exclusive opens are bounded by `Exclusive`, which `GraphicsOutput` does not
implement.

This reverts `cb9a8d848`'s code: the registration and its comment, the
screen arm, `Ppm::text_row_bands`, and `bootlog::LOADER_GOP_LINE` with its
row in the loader-lines gate, which only it read. `loaderlog::GOP_AT` stays:
the loader prints it.

Reverting this commit restores the test.

`cargo test --test toyos-build --no-run`: exit 0, no warning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
Japabu added a commit that referenced this pull request Oct 1, 2026
`issues/build/nothing-refuses-the-loader-an-exclusive-gop-open.md` records
`212516e71` and its revert, the nightly red it was deleted on, and #660 as
the landing that refuses an exclusive open of `GraphicsOutput` at build
time, with that refusal as its exit. `parallel-tests-red-under-other-suites`
loses "`screen_loader_lines` is #640's to fix" and points at it.

`212516e71`'s message says #640's count has run on no nightly. It has: the
nightly 36763711317 at #640's head `6e0d7da82` passed `screen_loader_lines`
in `guest (10)`, which the new issue says. The test is deleted rather than
fixed because #640 is superseded by #660's guard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
@Japabu

Japabu commented Oct 1, 2026

Copy link
Copy Markdown
Collaborator Author

Closed as superseded: main deleted screen_loader_lines (212516e, in #639), and #660 replaces what it guarded with a compile-time refusal of an exclusive GOP open (clippy.toml bans open_protocol; every open goes through protocol::get). Nothing to carry.

@Japabu Japabu closed this Oct 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant