Commit Graph
8 Commits
Author SHA1 Message Date
NoahandClaude Fable 5.1 a512d56650 qemu: scroll and home verbs in the driver
scroll X Y DY and home are judged by the channel's own ack, like nav and
wake: the deterministic stand-in for a swipe that only brings a control
into view (flare-edge #174), and the fresh-boot state a live panel needs
before each flow (flare-edge #173).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3G6m9Aw5RyVY4ZowtKzEj
2026-09-08 16:48:09 -06:00
NoahandClaude Fable 5.1 bd5c5af9db qemu: structural hash relative to the background, taps that land on the pixel
imgtools.structural() marked a cell occupied when its grey exceeded an
absolute 10/255, and the WardenOS page background is grey 16: every cell
of every region read occupied and no structural check could ever fail
(#19). Occupancy is now grey deviating from the crop's own median by more
than DEVIATION_THRESHOLD, or edge energy above EDGE_THRESHOLD. Measured on
real captures: a switch knob left/right differs in 240 of 256 cells (was
0), a dark card reads its icon and text and nothing else. Every committed
reference is recaptured with flare-edge tools/flow-run-all.sh --capture.

qmp.py to_axis() truncated the pixel-to-axis conversion and LVGL's
evdev calibration truncates on the way back, so many pixels landed one
short (130 -> 5924 -> 129) and a tap could miss the control hit had just
confirmed at that pixel (#21). It now rounds up to the smallest axis value
that truncates to the requested pixel; test_qmp_drive.py asserts the round
trip for every pixel at three panel sizes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3G6m9Aw5RyVY4ZowtKzEj
2026-09-08 13:53:59 -06:00
NoahandClaude Fable 5.1 eb16a301c0 qemu: wake verb, idle stat, and no fbcon cursor in the framebuffer
- tests/qmp.py: `wake` is a judged verb (the reply must be the channel's
  own ack) and `assert_stat` knows `idle` (0 awake, 1 dimmed, 2 asleep).
  warden-ui's sleep.c swallows the touch that wakes a dimmed or blanked
  panel, so every compiled flow script now opens with wake (flare-edge
  #148); tests cover both.
- run.sh: vt.global_cursor_default=0. fbcon shares the virtio-gpu
  framebuffer with warden-ui and its cursor blinked an 8x2 block onto the
  top-left corner in some boots and not others, which made a structural
  pixel reference fail by one cell (#18). The panel has no fbcon on its
  display, so this only makes the rig match it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013aHKWzT5EF86RFKRMtAv9n
2026-09-07 23:39:07 -06:00
NoahandClaude Fable 5.1 86a9544dcc qemu: json/stat/region channels, seeding, driver tests
- rootfs/sbin/init: the control bridge answers `@cat PATH` locally so the
  driver can read /tmp/warden-web-status.json out of the guest. That file
  has no trailing newline; the bridge adds one so the sentinel stays on
  its own line and the line-based reader never blocks.
- tests/qmp.py: wait_json/assert_json (dotted paths, eq/ne/contains/
  len_eq/len_ge/gt/lt), assert_stat off the FIFO's stats reply,
  capture_region, assert_region NAME [TOLERANCE] and assert_ocr. A
  tolerance other than the captured one, a reference box that does not
  fit the screendump, a missing reference or a missing tesseract is FATAL
  for that step and the run continues (flare-edge #147).
- tests/imgtools.py: P6 reader, crop, perceptual and structural hashes,
  compare, with a self-test.
- tests/test_qmp_drive.py: drive() with QMP and the control channel
  faked, pinning the per-step ok/fail/fatal contract.
- mkimage.sh SEED_DIR and ui-drive.sh --seed/--refs: settings fixtures
  staged into userdata before warden-ui starts, and a reference store
  handed to the driver.
- ci: the driver tests and the imgtools self-test run in qemu-tools.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013aHKWzT5EF86RFKRMtAv9n
2026-09-07 23:26:01 -06:00
NoahandClaude Fable 5.1 8a57057053 Bridge warden-ui's debug channel out of the qemu VM
The rig could drive the UI and detect one outcome: the process died. Nothing
could ask the UI what page it was on or what a tap would land on, because
that channel is a FIFO inside the guest and the initramfs is busybox-only
with no sshd. Scenarios therefore asserted nothing and screenshots went
unread.

run.sh --ctl exposes a second pci-serial port as a unix socket, the same
device the RS485 bridge already rides, listed first so it is always ttyS0.
It also puts warden.ctl on the kernel command line, and init bridges only
when that marker is present: a VM launched with --rs485 alone has a ttyS0
too, and that one is the Modbus wire. The bridge relays one command line in
and the FIFO's reply out, then a sentinel so the reader needs no timeout.

qmp.py gains the channel verbs (nav, page, stats, hit, assert_page,
assert_hit), records every step to results.jsonl as ok/fail/fatal, continues
past an assertion mismatch so one run reports every broken expectation, and
checks the console after EVERY step for the stage-2 init's EXITED line so a
crash is pinned to the step that caused it. assert_hit matches the widget's
bounding box: an icon has no usable caption and two list rows share a class,
but the geometry the UI itself resolved is exact.

The vocabulary is what tools/warden-ctl already speaks over SSH to a real
panel, so a script that runs here runs there. Verified end to end on the rig
(11/11 verbs round-tripped) and against the bench panel, where the same
commands returned byte-identical results.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013aHKWzT5EF86RFKRMtAv9n
2026-09-07 21:54:52 -06:00
NoahandClaude Opus 5 f245917570 qemu: drive scripted UI scenarios
ui-shot.sh proves touch reaches the UI in one tap. Verifying a UI change needs
a SEQUENCE -- swipe through the app rows, open a submenu, tap a tab, bring up
the keyboard -- and booting per step costs about a minute under TCG, so:

- qmp.py gains a `drive` mode: one connection, one boot, a script of
  tap/swipe/fling/shot/sleep steps in PANEL PIXELS rather than the raw
  0..32767 tablet axis. Swipes interpolate their motion, because LVGL decides
  a gesture from the movement between indev polls and a press-then-release
  with nothing in between is a click, not a scroll.
- ui-drive.sh runs such a script against a booted VM and collects the
  screenshots.

It also FAILS on a UI that died mid-script. warden-ui crashing leaves its last
frame in the framebuffer, so screendumps keep returning a plausible picture of
a program that no longer exists; stage-2 init now announces the exit and its
status on the console, and ui-drive.sh greps for that after the run. This is
what caught the SIGSEGV behind flare-edge#125.

Stage-2 init also mounts devpts. The UI's Terminal page opens a PTY, so
without it that page could only ever report "no PTY available" -- it rendered,
which made a screenshot scenario look fine while the one thing the page does
was untestable.

tests/scripts/nav-stress.txt is the first committed drive script: the
navigation sequence that reproduces flare-edge#125.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T2D2KtdgwbhbF6Mo64eUrn
2026-09-03 11:50:20 -06:00
BFE EngineeringandClaude Fable 5 2756de0b46 review: iteration-1 fixes across CI, bridge, VM harness, and docs
CI/pipeline:
- KERNEL_TARBALL passed as a YAML env literal '~' was never tilde-expanded
  and would have failed every hosted kernel-build dispatch; the path is now
  exported from the shell. Verified reproducible before the fix.
- Every job gets timeout-minutes; boot smoke uses timeout -k so a wedged
  qemu is SIGKILLed instead of holding the job.
- Tarball fetch + fail-closed sha256 verification deduplicated into
  build/fetch-kernel-tarball.sh (with curl retries), used by build-kernel.sh
  and both CI jobs. busybox fetch gains retries too.
- ccache layer for kernel-build (cache keyed on defconfig+patches) recovers
  the incremental-compile speed the ephemeral-runner move cost.
- build-kernel.sh now asserts every fragment option survived olddefconfig —
  merge_config -m pastes text and Kconfig silently drops unmet symbols.

rs485-bridge:
- pending-buffer cap (2x max RTU ADU) instead of unbounded growth;
  explicit accept-loop error handling with backoff instead of .flatten();
  per-arm inline bounds instead of the string-keyed lookup whose default
  would have mis-bounded a future get-input; control-socket cleanup errors
  surfaced; flag-shaped values rejected in arg parsing; doc example uses a
  private mktemp dir. Test timing margins widened for contended runners
  (gap 25->120ms, 60x margin on the split-frame test).

VM harness:
- stage-1/stage-2 boot scripts share one validated slot parser and one
  by-name populator (qemu/rootfs/etc/warden-lib.sh) — the duplicated
  parser had already diverged on validation; userdata/oem mount failures
  now fail fast with a greppable sentinel; udhcpc fallback keys off the
  interface actually having an address; switch_root applet guarded.
- boot-smoke delegates the qemu invocation to run.sh (machine shape lives
  in ONE place); run.sh port 0 disables a hostfwd.
- mkimage: unknown partition names fail at build time; DISK_END is a max,
  not last-entry; --state keys validated as filenames.
- portal-scenario: mock readiness is asserted (no silent fall-through),
  hostfwd port collisions retried, mount-failure sentinel fails fast.
- ui-shot: fixed sleeps replaced with bounded screendump polling; the
  repaint assertion is real and documented as such. qmp.py loses its
  module-global and gains argv validation.

Docs/scrub: bench-host paths and the site AP name removed from six more
port docs and two evidence tables; path-bearing build artifacts (.elf,
.map) untracked (the 154-byte firmware .bin is path-free and stays);
ADR-0003 marked visibility-superseded by ADR-0007; stale section
cross-reference fixed; flare-edge noted as private for outside readers;
stale root-level review report removed per the new workspace rule.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018HUayid7W5w7jBdb9Rrj1K
2026-08-30 08:19:17 -06:00
BFE EngineeringandClaude Fable 5 13b7d063a2 qemu: display + touch scenario, ADR-0006, docs
Phase 4+5 of the device sim:

- Display + touch verified end-to-end: virtio-gpu at 720x720 (fbdev
  emulation) renders the real WardenOS dashboard from the static LVGL
  fbdev+evdev UI build (flare-edge qemu-vm-support tools/build-ui-vm.sh);
  QMP input-send-event taps the Metrics tab and qemu/tests/ui-shot.sh
  asserts the repaint from screendumps. Two load-bearing QEMU flags found
  and documented: -global virtio-mmio.force-legacy=false (gpu/input are
  VERSION_1-only) and the 200ms press hold (an instantaneous press+release
  lands inside one LVGL indev poll and never clicks).
- qemu/tests/qmp.py: minimal QMP client (screendump, tap, quit).
- stage-2 init starts warden-ui when present and fb0 exists.
- docs/decisions/0006-qemu-device-sim.md: virt-not-custom-board, the
  enters-at-kernel boundary, fragment policy, naming, consequences.
- docs/architecture.md: new section 7 (device emulation), order-of-work
  item 7; modbus cross-reference to the bridge.
- qemu/README.md: emulated-vs-not table, scenarios, gotchas, host/runner
  requirements. docs/ci-cd.md: runner needs one-time qemu-system-arm
  install (fail-closed smoke until then, [maintainer]-gated). Repo README updated.

Final sweep on this commit: shellcheck clean, bridge 7/7 tests, boot smoke
PASS, portal scenario PASS (check-in + fw pull + signed .wfw download),
ui-shot PASS (touch navigates to Metrics) — all under the final flags.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018HUayid7W5w7jBdb9Rrj1K
2026-08-29 20:16:36 -06:00