test(panel): the terminal scroll-anchor row, which I had given up on

Closes the fourth terminal gap. The row I could not drive was
reproducible; **my delta had the wrong sign** --- `scroll_view(key,
viewport, 3)` moves, `-1` does not --- and I concluded the fixture was
at fault after three attempts rather than trying the other direction.

The sequence: `frame()` registers the panel terminal view, the key is
built from the side window, the viewport is `panel_grid_size` less its
mode-line row, forty published line feeds build the history, and the
baseline is taken AFTER that history exists. Taking it after is what
makes the leg discriminating --- a constant anchor leaves the
post-history baseline unchanged, so the row fails even while
`mapping_revision` is perfectly live.

Two mutations, each failing this row alone:

  constant ANCHOR, live revision   -> the anchor is not in the key
  constant REVISION, live anchor   -> the screen is not in the key

Neither passes on the other's evidence, which is the separation the
terminal half needed: `screen.rs` proves the counter classifies events,
the domain row proves the branch is taken, and these two prove the
daemon's key actually reads both halves of what
`view_mapping_identity` returns.

The owed-witness note is removed from the ledger.

Verified: focused suite 37/37, clippy clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
This commit is contained in:
Levi Neuwirth 2026-08-15 16:28:09 +02:00
parent 0bdfc9b6dd
commit 921989a77c
No known key found for this signature in database
2 changed files with 52 additions and 23 deletions

View File

@ -437,17 +437,6 @@ from #171 and #215.
three of them lost to these two signatures. U9's synthetic-load
control remains unrun and is the cheapest thing that would either
implicate load or clear it.
- **OWED WITNESS — terminal scroll-anchor movement at the daemon
level.** The anchor is in the key (`PanelMappingContent::Terminal`
carries it) and the review asked for a row proving the daemon's
generation moves when it does. **Three attempts failed to drive a
scroll from the panel fixture**: `scroll_lines` needs a viewport the
panel projection registers on its own schedule, and `scroll_view`
with an explicit content size still reports no movement after forty
line feeds, so the history it would move into is not accumulating as
the fixture assumes. **Recorded rather than faked.** Without it, a
`view_mapping_identity` returning a constant ANCHOR while reporting a
live revision passes every terminal row that exists.
- **Gates:** the four `bottom_panel_*` suites, the GUI 1a wire suite,
`PMACS_REQUIRE_GPU=1 cargo test -p pmacs-gpu`, and **`--protocol`**.

View File

@ -1816,17 +1816,57 @@ fn g2_g3_a_terminal_panels_key_tracks_its_screen_and_anchor() {
advancing the revision until §5b's terminal correction"
);
// NOT YET WITNESSED: scroll-anchor movement at this level.
// CHANGING: the view's **scroll anchor** moves, with the child's
// screen untouched. The same coordinate then names a different
// retained row.
//
// The anchor IS in the key — `PanelMappingContent::Terminal` carries
// it — but driving a scroll from here has defeated three attempts:
// `scroll_lines` needs a viewport the panel projection registers on
// its own schedule, and `scroll_view` with an explicit size still
// reports no movement after forty line feeds, so the history the
// scroll would move into is not accumulating the way this fixture
// assumes.
//
// Recorded as OWED rather than faked. Without it, a
// `view_mapping_identity` that returned a constant ANCHOR while
// reporting a live revision would pass every row above.
// This is the leg that separates the anchor from the screen
// revision: the baseline is taken AFTER the history exists, so a
// constant anchor leaves it unchanged and the assertion below fails
// even though `mapping_revision` is perfectly live.
let key = {
let core = session.state.core.borrow();
let side = core.side_window_for(FID).expect("side");
pmacs::terminal::view::TerminalViewKey::new(FID, side, buffer_id)
};
let viewport = {
let core = session.state.core.borrow();
let grid = core.panel_grid_size(FID).expect("a presentable panel");
pmacs_protocol::CellSize::new(grid.rows.saturating_sub(1), grid.cols)
};
for _ in 0..40 {
feed(&session, AnsiEvent::LineFeed);
}
let anchor_of = |session: &Session| {
session
.state
.terminal_manager
.borrow()
.view_mapping_identity(key)
.expect("the panel's terminal view")
.1
};
let before_scroll = mapping_generation(&mut session).expect("a key");
let anchor_before = anchor_of(&session);
assert!(
session
.state
.terminal_manager
.borrow_mut()
.scroll_view(key, viewport, 3),
"the scroll must actually move, or this leg proves nothing"
);
assert_ne!(
anchor_of(&session),
anchor_before,
"scrolling pins the view to a retained row, so the anchor moves"
);
let after_scroll = mapping_generation(&mut session).expect("a key");
assert!(
after_scroll > before_scroll,
"the anchor is part of the mapping — the same coordinate now \
names a different retained row, with the screen untouched"
);
}