seedhammer-v1-companion/platform/v1/platform.go
mineracks ded32c8f07 #4 v1 emulator scaffolding — boots in browser
Phase 2 kickoff. The cmd/emulator binary now builds to WebAssembly,
loads a 240×240 LCD-equivalent canvas in the browser, and accepts
8-button input from either on-screen buttons or mapped keyboard
keys. The actual v1 gui/ package isn't lifted yet — this is the
scaffolding that proves the platform/v1.Platform interface contract
and the build/serve pipeline so the GUI lift can land cleanly later.

  platform/v1/platform.go (interface)
    Button enum: Up Down Left Right Center Button1 Button2 Button3
    Event{Button, Pressed}
    Platform interface: Events() <-chan Event, Display(image.Image)

    The interface is intentionally minimal — engrave/font/QR access
    happens via direct package imports, not through Platform.
    Future additions (camera, persistent storage) widen the
    interface, not the data model.

  cmd/emulator/main.go (//go:build js && wasm)
    browserPlatform implements Platform against the browser:
      - Display() copies the RGBA buffer into a JS Uint8ClampedArray
        and calls back into emulatorPaint(pixels, w, h)
      - Events() returns a 32-slot buffered channel that
        exportPushEvent feeds from JS
    Boot screen draws a stub frame (orange border, centre cross-hair
    tick) so the canvas isn't blank during the GUI-less stage.
    Future: replace drawBootScreen with gui.Loop() once gui/ is
    lifted.

  cmd/emulator/emulator_host.go (//go:build !js || !wasm)
    Host placeholder so `go build ./...` succeeds on non-WASM
    targets. Emits a helpful "build with GOOS=js GOARCH=wasm".

  web/emulator/{index.html, app.css, app.js, build.sh}
    Static PWA shell. Lays out an LCD frame (canvas, 320×320
    rendered with image-rendering:pixelated for crisp 240×240
    upscale), an on-screen joystick grid, three side-key buttons,
    and a keyboard-shortcut reference table. Keyboard mapping
    matches docs/architecture/v1-buttons-and-ui.md proposal.
    Reuses ../composer/{wasm_exec.js, app.css} — DRY base styling,
    no duplication.

The static server now needs to run one directory level higher
(`-d ./web` instead of `-d ./web/composer`) so both /composer/
and /emulator/ are reachable.

Build with: ./web/emulator/build.sh
Serve with: python3 -m http.server -d ./web 38080
Open:       http://localhost:38080/emulator/

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 20:38:43 +10:00

57 lines
1.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package v1 defines the platform adapter layer that v1's gui/, input/,
// and engrave/ packages bind against.
//
// Two build-time targets implement Platform:
//
// - Real device (GOOS=linux GOARCH=arm on Pi Zero): driver/drm for
// LCD frame output, driver/wshat for GPIO buttons, driver/libcamera
// for the QR-scanner camera, driver/mjolnir for the engraver USB
// serial.
//
// - Browser emulator (GOOS=js GOARCH=wasm): browser canvas writer,
// keyboard-event mapper, mock camera reading from a sibling pane's
// canvas, and a null engrave sink (or a visual playback harness).
//
// The interface lives here (not in gui/) so we can swap backends without
// touching the GUI code. gui.Context takes a Platform value and never
// calls any driver/ package directly.
package v1
import (
"image"
)
// Button identifies one of the eight physical inputs on the v1 hardware.
type Button int
const (
ButtonUp Button = iota
ButtonDown
ButtonLeft
ButtonRight
ButtonCenter
Button1
Button2
Button3
)
// Event is what a Platform delivers to the GUI's input loop.
type Event struct {
Button Button
Pressed bool // true = press, false = release
}
// Platform is the contract every backend implements. The GUI receives a
// Platform value at startup and drives every external interaction through
// it. Adding a new backend means implementing this interface — no GUI code
// changes.
type Platform interface {
// Events returns the channel of input events. Closed when the
// platform is shutting down.
Events() <-chan Event
// Display writes a frame to the LCD-equivalent. The image is the
// full screen at the platform's native resolution (240×240 for v1).
Display(frame image.Image)
}