core/scroll_compositor.sg (sigil-video 21a7fd5) adds per-layer (scroll_x, scroll_y) viewport offsets to the Lumen compositor — enabling pages taller than the display to scroll. SCROLLCOMP-PASS x86 verified: 160×240 content in a 160×120 viewport; four scroll positions tested (y=0, y=120, y=180 clipped, x=80). GPU path: gpu_submit(CAP_ACCEL, scroll_offset_buf(), fb, fw, fh) routes to display controller pan register (silicon-pending). Software floor: copy_span with offset source address per visible scanline.
scroll_compositor.sg — offset table + API
scroll_x:i32 (LE) + scroll_y:i32 (LE). scroll_comp_set_scroll(idx, sx, sy) writes the pair via poke32. scroll_offset_buf() returns 0xAB0000 (used by the GPU path).scroll_comp_composite(fb_out, pitch, fw, fh): For each active layer (checked via comp_layer_active(idx)), reads (scroll_x, scroll_y) for that layer. For each viewport row r (0 to fh-1): source row = r + scroll_y, clamped to [0, lh-1] where lh = layer height. Horizontal: viewport col c maps to source col c + scroll_x; visible width = lw - scroll_x (clipped at viewport edge). Copies vis_w pixels per row via copy_span. Rows where r + scroll_y ≥ lh are skipped (clip — shows transparent/black below content).gpu_submit(CAP_ACCEL, scroll_offset_buf(), fb, fw, fh) — routes per-layer scroll offsets to the display controller's hardware pan register when CAP_ACCEL is held. Software floor takes over on Pi 3 / no-GPU config.scroll_comp_composite is called before comp_present merges layers into the output framebuffer. The scroll offset is applied at composite time — layers store full content at full resolution; the viewport window is the scroll offset view into that content.SCROLLCOMP-PASS test
160×240 pixel content in a 160×120 viewport. Four probes:
| Test | scroll_x | scroll_y | Expected | Result |
|---|---|---|---|---|
| T1 | 0 | 0 | Blue top half of content visible | ✅ |
| T2 | 0 | 120 | Red bottom half of content visible | ✅ |
| T3 | 0 | 180 | Row 60 clipped (src_row 240 ≥ lh 240) — clip plane fires | ✅ |
| T4 | 80 | 0 | Magenta right half (scroll_x=80 into content) | ✅ |
What this unblocks
Browser scrolling: render_page paints a full-height layer; scroll_compositor lets the viewport slide over it via scroll_y. The next integration point is the style resolver passing overflow: scroll to trigger scroll_comp_set_scroll.