Commit Graph

22 Commits

Author SHA1 Message Date
mineracks
cf68d6c451 emulator: wake the Events() select on SDCard push
Eject button wasn't taking effect immediately because Platform.Events()
was blocked in a select that only woke on button-channel input or
timer expiry. SDCardEvents go through a separate pending queue and the
select had no way to notice when they arrived — the eject sat in
pending until the next button press or timer expiry, both of which
could take seconds.

Fix: 1-buffered wake channel. signalWake() pokes it (non-blocking),
the select now reads from it as a third case, and the drain-loop at
the end re-drains pending so any event pushed while we waited gets
returned in the same call.

  exportSetSDCard() calls signalWake() after appending to pending.
  Wakeup() (gui.Platform method, previously no-op) now also calls it,
  so any future gui-side wake request works the same way.

The bug was specific to non-button events — button events go through
the events chan directly so they always woke the select.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 21:09:58 +10:00
mineracks
b4635f4efc emulator: stack SD card slot below chassis, not beside it
.emu-device-card was display:flex justify-content:center, so adding
the SD card div as a sibling of .emu-chassis put them side-by-side
in a row. The chassis's internal grid then competed with the SD card
for horizontal space and the LCD column collapsed.

Fix: flex-direction:column on .emu-device-card so the chassis sits
on top at full width, with the SD card affordance stacked below.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 21:04:16 +10:00
mineracks
f8f774c8e3 emulator: SD card eject/insert toggle
Real device's REMOVE SD CARD / inserted-OK screens are driven by
gui.SDCardEvent{Inserted: bool}. On real hardware that's a kernel
hotplug event; in the browser we have to synthesise it.

Added:
  - composer-style "Eject SD card" / "Insert SD card" button below
    the chassis, plus a tiny visual slot affordance with a card
    that animates in/out
  - exportSetSDCard JS bridge in cmd/emulator/main.go that pushes
    gui.SDCardEvent{Inserted: bool}.Event() onto the pending queue
  - JS state tracking + label flip + Go side called on every click

Default state: inserted (matches real device boot). Toggle is a
simple binary — no "ignore warning" simulation since the firmware
already handles that on the Hold-button-to-ignore path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 21:03:04 +10:00
mineracks
1f7d317bff emulator: wire clicks to new disc/key selectors
Chassis redesign renamed .emu-btn → .emu-disc-zone (joystick zones)
and .emu-key (side keys), but app.js was still querying .emu-btn.
Result: no click listeners attached after the chassis swap — only
the keyboard worked.

Fix:
  - els.buttons selector covers both new class names
  - keyboard handler's visual-feedback selector dropped the .emu-btn
    prefix; matching on data-btn alone finds the right element no
    matter which class it carries

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 21:00:35 +10:00
mineracks
174f1bfc05 emulator: redesign chassis to match real v1 device layout
Layout was a generic dev-tool affordance (small canvas on top, square
button grid below). Real device is landscape orientation with the
LCD recessed in the middle, ONE disc joystick on the left side, and
THREE round side keys stacked on the right.

  before:                    after:
  +----------------+         +---------------------------+
  |   [canvas]     |         |  (\)   +-------+   o     |
  |                |         |   |    | LCD   |   o     |
  |  [ U ]         |         |  (/)   |       |   o     |
  | [L][C][R]      |         |        +-------+         |
  |  [ D ]         |         +---------------------------+
  |                |          orange anodised chassis
  | [K1][K2][K3]   |
  +----------------+

The chassis is a CSS-only render — orange gradient with edge highlight
+ inset shadow stack so it reads as machined aluminium. The disc
joystick is a single circular metal-look element with 5 overlapping
square-ish hit zones (top/bottom/left/right/centre); arrow glyphs
hint at the directions but the look is one cohesive control rather
than five separate buttons. The three side keys are round metal-look
domes with press states.

Footer copy refreshed: no more "Phase 2 scaffolding" caveat — the
real upstream GUI is running. Notes the camera and engraver are
stubbed.

Responsive breakpoint at 600px shrinks chassis + controls so the
emulator stays usable on phones.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 20:58:33 +10:00
mineracks
9675c05ff1 emulator: real gui — lift upstream v1.3.0 gui/ + wire Platform
The Phase 2 scaffolding boot screen is replaced with the actual
upstream v1.3.0 gui package running in browser. cmd/emulator now
calls gui.NewApp(plat, version) + drives a.Frame() in a goroutine.

Lifts:
  gui/                         (top-level UI + state machine)
  gui/assets                   (icons, fonts, 9-patch images)
  gui/layout                   (constraint-based UI layout)
  gui/op                       (drawing op primitives)
  gui/saver                    (screensaver + idle timeout)
  gui/text                     (text shaping)
  gui/widget                   (button/menu/keyboard widgets)
All 11 .go files lifted verbatim from seedhammer/seedhammer @ v1.3.0
(commit 2f071c1d...), import paths rewritten seedhammer.com → mineracks.

browserPlatform now implements gui.Platform (12 methods):
  Events(deadline)   — drains v1.Event chan, maps to gui.ButtonEvent
                       (gui.Button enum order matches platform/v1.Button,
                        so the conversion is a direct uint cast)
  Wakeup()           — no-op (no sleep state in browser)
  PlateSizes()       — backup.SquarePlate, backup.LargePlate
  Engraver()         — nullEngraver stub (browser can't punch metal)
  EngraverParams()   — copy of mjolnir.Params {StrokeWidth: 38,
                       Millimeter: 126}. Inlined because tarm/serial
                       (mjolnir's USB dep) doesn't compile to js/wasm.
  CameraFrame(size)  — emits FrameEvent{Error: stubbed} so QR-scan
                       screens fall through cleanly; real handoff
                       lands when SeedSigner sim wires up (Phase 2.5)
  Now()              — time.Now()
  DisplaySize()      — 240×240
  Dirty(r)           — records rect, resets chunk cursor
  NextChunk()        — one-shot: returns full sub-image once per
                       Dirty cycle, then flushes the whole frame
                       buffer to JS via emulatorPaint()
  ScanQR()           — returns nil decodes (stub)
  Debug()            — false

The Engraver interface is satisfied by nullEngraver — Engrave() returns
"engraver not connected" so the GUI's engrave flow fails cleanly
instead of looking like it's working.

WASM grows 2.7MB → 8.0MB (gui package + btcd + crypto deps + fonts
all linked in). Acceptable for the v1 emulator one-time cache.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 20:53:06 +10:00
mineracks
c427c68359 Composer: SH-03 mid-hole at Y=52 — "SH-01 on top of SH-02"
The user (the manufacturer) clarified the SH-03 geometry: it's
literally SH-01 stacked above SH-02, with the SH-01 bottom row of
holes aligned to the SH-02 top row.

Math checks out:
  SH-01 = 85×55, holes at y=3 and y=52 (3mm inset from each edge)
  SH-02 = 85×85, holes at y=3 and y=82
  stack with shared middle row:
    SH-03 top      = y=3    (SH-01 top inset)
    SH-03 middle   = y=52   (SH-01 bottom = SH-02 top, shared)
    SH-03 bottom   = y=131  (SH-02 bottom inset = 134-3)
  total height    = 52 + 82 = 134 mm ✓

Previous Y=45 guess corrected to Y=52.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 20:46:36 +10:00
mineracks
a42a410fd3 Composer: SH-03 mid-hole at Y=45 (upper-third, not centerline)
User confirmed: the two extra clamping holes on the SH-03 long sides
sit at the upper-third position, not the geometric centerline that
I was rendering. Matches the engraved sample plate photos where the
"DISMISS / MUSEUM" row appears just below the middle hole pair.

  before: middle holes at (3, 67) and (82, 67)   ← geometric centre
   after: middle holes at (3, 45) and (82, 45)   ← upper-third

Constants picked up by holePositions(). SH-01 and SH-02 still 4-corner.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 20:45:49 +10:00
mineracks
d7dd748fe4 #5 SH1E Pi-side decoder + fuzz harness
Two pieces required before any real-device release ships:

(1) cmd/sh1e-decode — a Pi-side CLI that validates and pretty-prints
    an SH1E envelope. Reads from stdin, a file, or a hex string
    (`-hex "53 48 31 45 ..."`, spaces/colons/pipes stripped so you
    can paste straight from the composer's hex dump pane).

    Output: ACCEPT line with byte count + fingerprint, then per-block
    summaries (plate type, every text block's font/size/anchor/align/
    rotation/content, every SVG path's anchor/scale/rotation/d-string).
    On rejection: REJECT <TAG>: <error> where TAG is a stable label
    matching the sentinel error name — easy to grep in CI fuzz runs.

    Run it like:
      sh1e-decode some-design.sh1e
      sh1e-decode < piped-stdin
      sh1e-decode -hex "53 48 31 45 01 4f 00 ..."

(2) engrave/wire/sh1e/fuzz_test.go — native Go 1.18+ fuzz harness for
    Decode and a faster envelope-only variant.

    Property: any input must either return a valid Design that
    round-trips through Encode to byte-identical output (canonical
    encoding) OR return a non-nil error. A panic, hang, or
    out-of-memory is a test failure — the Pi controller runs this
    parser on untrusted QR-scanned bytes and a crash there is a
    real-world fault.

    Seed corpus: valid envelopes from the existing tests +
    deliberately corrupted variants (length-field flipped, payload
    byte flipped) + obvious shape attacks (empty, wrong magic, wrong
    version, mostly-FF spam).

    Smoke-tested for 5 seconds → 0 crashes, 933k execs/sec, 60 new
    interesting inputs discovered, 72 corpus entries. Spec calls for
    1 CPU-week before any release ships; CI runs a shorter window
    per merge.

    Run with:
      go test -fuzz=FuzzDecode -fuzztime=10m ./engrave/wire/sh1e
      go test -fuzz=FuzzDecodeEnvelopeOnly -fuzztime=10m ./engrave/wire/sh1e

Plus a CRC-stdlib drift smoke test guarding against a future
dependency swap quietly changing the CRC table.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 20:41:34 +10:00
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
mineracks
0203be2e8f Composer: hole positions from production CAD + SVG error diagnostics
(1) Mounting holes were rendering at 6.5mm inset — a guess. The actual
    Mineracks production DXF (Name-Plate-85x85-316L 2B.DXF, the SH-02
    plate) places 4 circles at (3, 3), (3, 82), (82, 3), (82, 82) with
    radius 1.5mm. Centres sit on the outerMargin line; the hole edge
    falls 1.5mm shy of the plate edge.

    holeInsetMM:        6.5  → 3.0
    holeDiameterMM:     3.5  → 3.0  (true M3 clearance from CAD)
    holeDangerDiameterMM: unchanged at 7mm (covers the M3 nut/head
        footprint per the spec)

    Hole fill now #fff so the eye reads "metal removed here" instead
    of a printed dot. Same 3mm inset is applied to SH-01 and SH-03 —
    same physical drill jig, just different plate aspect.

(2) SVG-mode error message when no drawable shapes are found now
    enumerates what was actually in the file (e.g. "found: 12 <g>,
    4 <text>, 2 <tspan>") and gives a targeted hint:
      - if <text>/<tspan>: "text logos need 'Path → Object to Path'
        in Inkscape first"
      - if <image>: "raster images can't be engraved; re-trace as
        vector outlines"
      - if <use>/<symbol>: "unresolved references; expand in your
        editor first"
    Helps users figure out what to fix in their .svg without having
    to guess.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 20:38:14 +10:00
mineracks
3ab72cbfdd Composer: M3 mounting holes on preview + auto shape-to-path
Two user-facing polish items spotted at the preview stage:

(1) Plate previews now render the physical M3 mounting holes. SH-01
    and SH-02 get 4 corner holes; SH-03 (Large, 85×134mm) gets 6 —
    4 corners plus 2 mid-edge holes on the long sides, matching the
    physical clamping pattern. Each hole shows as:
      - a grey filled circle (3.5mm visual diameter, slightly larger
        than the actual M3 clearance for preview contrast)
      - a red dashed "no-engrave" exclusion ring (7mm diameter)
    The user can see at a glance where the engraver must not punch.

    Hole positions:
      corners at (holeInset, holeInset) with mirrors for the other
      three quadrants, where holeInset = 6.5mm — midway between
      outerMargin (3mm, the no-engrave boundary) and innerMargin
      (10mm, the safe text area). The 3-10mm band is where SeedHammer
      drills the holes on real plates.

    holePositions(plateDims) is a small helper exposed for the future
    when an actual gui/ port needs the same numbers.

(2) SVG file upload now auto-converts <rect>, <circle>, <ellipse>,
    <line>, <polyline>, <polygon> to <path d="..."/> in the browser
    before extracting d-strings. Lifted Gangleri42's rewriteShapes
    algorithm verbatim (same Bezier control-point ratios for the
    circle/ellipse approximations).

    Previously the composer rejected files with no <path> elements
    and told the user to "flatten shapes in your editor first" —
    which was a hard-fail UX for a fixable problem. Now any SVG
    with drawable shapes Just Works.

    Transforms still need manual flattening (getCTM-based bake is a
    bigger chunk); the help text reflects this.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 20:27:11 +10:00
mineracks
f4a61f6cf0 #3 SVG path mode — second composer tab
Editor card now has two tabs: "Block text" and "SVG paths". Picking
an .svg file extracts every <path d="..."/> via DOMParser, hands the
d-strings to WASM, which builds sh1e.SvgPath entries (one per path)
anchored at (innerMargin, innerMargin) at 100% scale. Live preview
renders them on the plate via native browser <path> support — same
stroke styling as the engraving font so the visual is consistent.

JS side (web/composer/app.js):
- onSVGFile(): reads file, runs DOMParser on the text, harvests
  every <path>'s `d` attribute, errors out clearly if no paths
  found ("flatten shapes to paths in your editor first")
- mode state ("text" | "svg") switches which encoder/preview is
  called from refresh(), showQR(), showBytes()
- setMode() flips the active tab + the visible editor pane
- a summary line shows filename, path count, total chars

Go side stays as-is (composerEncodeSVG / composerPreviewSVG /
composerQRSVG were wired in the previous commit). Plate chrome
rendering shared between text- and svg-preview via writePlateChrome
so the two modes draw the same plate outline + margin guides.

Limitations (acceptable for MVP, documented in the tab help text):
- Only flat <path d="..."> elements are recognised; <rect>,
  <circle>, <use>, transforms, and nested <svg> are ignored. Users
  should flatten in their editor (Inkscape: Object → Path, then
  Flatten Transforms).
- Strict-subset path syntax check (M/L/H/V/Q/C/Z only — no arcs
  per the SH1E spec) is not enforced composer-side yet. The Pi
  parser will reject non-subset paths. Composer-side validation
  is a follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 20:23:05 +10:00
mineracks
50b5301545 Composer: switch to vector engraving face + swap layout columns
Three tightly-related polish changes:

1. Preview now uses font/constant.Font — the v1 firmware's *vector*
   engraving face — instead of bitmap Comfortaa. Every <path> stroke
   in the preview is the actual stroke the engraver will follow on
   the plate. Reads the same MoveTo/LineTo segment data the upstream
   stepper consumes. vector-effect=non-scaling-stroke keeps the
   stroke width visually constant regardless of the SVG scale.

   The bitmap-faithful renderer is gone — engrave fidelity is more
   useful than LCD fidelity for the composer use case. The
   bitmap.Face import path is dropped from main.go.

2. Layout columns swapped. Editor was on the right, preview on the
   left. Now editor (taller, wider) is on the LEFT and plate picker +
   preview + actions stack on the RIGHT. Matches the natural
   "pick a plate → type → see result" reading order. Plate section
   gains a .plate-card class so it can be placed in the grid area.

3. Stale-error fix: typing in any input now clears the Show-SH1E-
   bytes "enter at least one line first" warning. Previously stuck
   around until the bytes button was clicked again with valid input.

Also extends the composer's JS export surface (composerPreviewSVG,
composerEncodeSVG, composerQRSVG) and adds the SVG-mode helpers in
Go (readSVGArgs, makeSVGDesign, writePlateChrome). JS shell doesn't
call these yet — wiring lands in the next commit (stage #3 of the
five-stage push).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 20:21:32 +10:00
mineracks
daac2d8ac5 #2 Pixel-faithful Comfortaa preview
Replace the system-monospace SVG <text> placeholder with actual
bitmap-glyph rendering from font/comfortaa.Regular16 — the same
16-pixel Comfortaa face the v1 firmware ships on the LCD. Every
"on" pixel in the bitmap becomes part of a horizontal-run <rect>
inside a transform="translate scale" group, so the preview is
pixel-identical to what the device's screen would show.

Implementation (cmd/composer/main.go):

- faceForFont(FontID, sizePt) -> *bitmap.Face
  Maps SH1E font IDs to bitmap faces. Today everything resolves
  to comfortaa.Regular16 — Poppins/Constant fall back until we
  wire their faces in.

- renderTextRowBitmap(sb, lineLayout)
  Two-pass over the text:
    1. Walk to compute total pixel width (advance + kerning) for
       horizontal alignment math.
    2. Emit one <g> with translate to (l.XMM, l.YMM + ascent*scale)
       and scale = fontMM / 16. For each glyph: AlphaAt threshold
       at 0x80, coalesce horizontal runs into single rects.
  Threshold-binary (not grayscale) because the engraver itself
  is binary — a pixel either gets punched or it doesn't.

Updated preview-note to reflect the new fidelity: "Each pixel in
the preview corresponds to a pixel the engraver will punch."

WASM size 3.6 → 3.5 MB (the simple <text> SVG → bitmap rect SVG
swap is roughly even; font binary data was already linked).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 20:13:44 +10:00
mineracks
2755a685d1 #1 QR display — composer becomes end-to-end useful
New JS export from cmd/composer:
  composerQR(plateType, lines) -> {svg, modules, bytes}

Encodes the design as SH1E (same path as Encode), then runs the
bytes through kortschak/qr at medium error-correction level (15%
damage tolerance — sensible for a phone-screen-to-camera handoff).
Returns an inline SVG of the QR matrix with a 4-module quiet zone
baked in, drawn as horizontal-run rects (one per dark run, NOT one
per cell — ~half the markup).

UI: a new orange primary button "Show QR" beside the debug bytes
button. Click → fullscreen overlay with the QR centred on a white
card, byte/module count below, and a close button. Click outside
the card or hit Escape to dismiss.

This is the Phase 1 unlock that turns the composer from "interesting
demo" into "I can hold my phone up to a SeedHammer v1 and engrave
this plate". Single-frame QR for SH1E payloads up to ~200 bytes
(well above the typical 90B for a 12-word seed plate). BBQr for
multi-plate manifests comes later.

WASM grew 3.4MB → 3.6MB from pulling in the QR encoder. Acceptable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 20:10:47 +10:00
mineracks
14486455c6 Composer UX polish — live preview, plate-aware lines, hex layout
Live SVG preview of the plate, rendered from the same lineLayout
function as Encode so the visual always matches what gets engraved.
The preview shows the plate outline, both margin guides (outer 3mm /
inner 10mm), and each non-empty text block at its (XMM, YMM) anchor.

Plate-aware line counts: composerPlateTypes now reports `max_lines`
per plate (computed from H minus innerMargin minus textYStartMM,
divided by stride). UI hides input rows above that count and
restores them when a bigger plate is picked again — no data loss
on plate switch.

  Small  85×55  → 5 lines
  Square 85×85  → 9 lines
  Large  85×134 → 15 lines

textYStartMM bumped 5 → 11mm so the first line sits inside the
innerMargin safe-engrave guide (it was visibly outside before).
textXMM bumped 5 → 11mm for the same reason.

Hex dump:
  - 8 bytes per row instead of 16 (~33 chars wide, fits any column)
  - shorter offset prefix (auto-width by total size)
  - now a debug-only collapsible alongside the live size meter

Layout fixes:
  - .preview no longer hard-pinned to 1:1 aspect (lifted webnfc CSS
    was sized for SH-II's square plate)
  - main > * gets min-width: 0 so wide hex content can't inflate
    the grid column past its fr unit
  - html, body get overflow-x: hidden as a root-level safety net
  - text baseline now calculated as YMM + fontMM*0.78 instead of
    relying on dominant-baseline="hanging" — Safari renders the
    latter inconsistently which made Large-plate text invisible

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 20:07:06 +10:00
mineracks
a4bcf364c7 Phase 1 milestone: first buildable composer WASM + PWA shell
End-to-end proof that the Go-to-WASM pipeline works and the SH1E
encoder produces canonical bytes loadable by a browser.

  cmd/composer/main.go (135 lines)
    //go:build js && wasm
    Three JS exports:
    - composerVersion() string
    - composerPlateTypes() Array<{id, name, w_mm, h_mm}>
    - composerEncodeText(plateType:number, lines:string[]) Uint8Array
    Encoder maps each non-empty line to a sh1e.TextBlock with v1
    plate constants (Comfortaa 12pt, 5mm x-offset, 8mm y-stride,
    left-aligned). Layout polish in follow-up commits; today the
    point is that the wire format is real SH1E.

  cmd/composer/composer_host.go (13 lines)
    //go:build !js || !wasm placeholder so `go build ./...` is clean
    on non-WASM targets; emits a helpful "use GOOS=js GOARCH=wasm"
    message if invoked.

  web/composer/index.html (61 lines)
    Fresh shell — no NFC/bridge UI (v1 uses QR handoff). Plate
    radio group, 12-line text editor, size meter, output pane.

  web/composer/app.js (101 lines)
    Vanilla JS, no framework, no bundler. Loads wasm_exec.js +
    composer.wasm, builds UI from composerPlateTypes(), wires
    Generate button to composerEncodeText() + hex-dump output.

  web/composer/app.css
    Lifted verbatim from Gangleri42/seedhammer cmd/webnfc/app.css
    (liquid-glass surfaces, system-theme-aware). 304 lines, MIT/
    Unlicense.

  web/composer/manifest.webmanifest
    PWA manifest with v1 branding.

  web/composer/build.sh
    One-command rebuild: stages wasm_exec.js from GOROOT, builds
    composer.wasm with -trimpath -ldflags='-s -w'. Prints the
    serve command + URL.

Verified end-to-end:
  - WASM builds at 3.4MB
  - HTTP server returns 200 for /, /composer.wasm (application/
    wasm), /wasm_exec.js, /app.js, /app.css
  - sh1e tests still green
  - go build ./... clean on host (placeholder file)

Deferred to follow-up commits:
  - Live SVG preview rendering (needs engrave/font integration)
  - QR display (probably via a small Go QR library compiled in)
  - SVG path mode (the second composer tab)
  - SeedSigner sim handoff bus

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 19:37:08 +10:00
mineracks
9b3a3bba21 SH1E v0.1 reference encoder + decoder + tests
The new code we're adding ourselves — the envelope format that ships
plate designs from the browser-side composer to a Pi controller.
Full spec at docs/architecture/sh1e-spec.md.

Implementation:

  engrave/wire/sh1e/sh1e.go (~325 lines)

  - Envelope: 4-byte magic "SH1E" + 1-byte version + 2-byte LE
    payload_len + 4-byte LE CRC32-IEEE + N-byte CBOR payload.
    11-byte overhead. uint16 length cap = 65535-byte payload.
  - Public types: Design, TextBlock, SvgPath, PlateType (Small/
    Square/Large), FontID (Comfortaa/Poppins/Constant), Alignment
    (Left/Center/Right).
  - Encode(d Design) -> []byte: validates, computes the SHA-256
    design fingerprint over fields 1-3, canonical-CBOR encodes
    the whole Design (with fingerprint as field 4), wraps in
    envelope. Same Design always produces byte-identical output.
  - Decode([]byte) -> Design: header check, length check, CRC
    check, CBOR unmarshal, canonical-encoding round-trip check
    (re-encodes the decoded value and compares bytes — catches
    non-canonical input), structural validation, fingerprint
    recomputation + match.
  - Sentinel errors for every rejection mode (errors.Is-friendly):
    ErrBadMagic, ErrUnsupportedVersion, ErrLengthMismatch,
    ErrCRCMismatch, ErrBadCBOR, ErrNotCanonical, ErrTruncated,
    ErrFingerprintMismatch, ErrPayloadTooLarge, ErrTooManyBlocks,
    ErrTooManyPaths, ErrTextTooLong, ErrPathTooLong, ErrNonASCII,
    ErrInvalidEnum, ErrOutOfRange, ErrInvalidRotation.

  engrave/wire/sh1e/sh1e_test.go (~270 lines)

  - Round-trip: minimal design + design-with-logo.
  - Stability: same input -> byte-identical output across encodes.
  - Fingerprint independence: encoder overwrites caller-supplied
    Fingerprint with the recomputed one.
  - Envelope rejections: truncated, bad magic, wrong version,
    length-mismatch, corrupted CRC, fingerprint mismatch (forged
    payload with valid CRC).
  - Validation rejections: bad plate type, bad font ID, font size
    out of range, text too long, non-ASCII text, too many blocks,
    too many paths, path too long, bad alignment, bad rotation,
    bad scale percent.

All 17 test packages green. Strict-subset SVG path validator (M/L/
H/V/Q/C/Z only, no arcs / S / T) deliberately deferred to live
alongside the path renderer in a later commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 18:51:16 +10:00
mineracks
9261cf368a Lift composer substrate from upstream v1.3.0
Twelve packages lifted from seedhammer/seedhammer @ v1.3.0
(commit 2f071c1d8f23eb7fd39b15fc0acb8874113f801e):

  address/     — bitcoin address parsing
  backup/      — v1 plate dimensions + UR-coded multi-plate backup
  bc/          — Blockchain Commons: ur, fountain, bytewords, urtypes,
                 xoshiro256 (5 subpkgs)
  bip32/       — HD-key derivation
  bip39/       — mnemonic seed phrases + 2048-word wordlist
  engrave/     — text/QR → MoveTo/LineTo command stream conversion
  seedqr/      — SeedQR / CompactSeedQR encoders
  image/       — paletted, rgb565, alpha4, ninepatch image formats
  nonstandard/ — bitcoin descriptor + script parsing
  font/        — bitmap + vector font runtime
  font/{comfortaa,poppins,constant,bitmap,vector}/  — actual fonts
  driver/mjolnir/  — MarkingWay USB-serial engraver driver

Plus an earlier-aside backup_test.go restored (its deps are now lifted).

Import paths globally rewritten seedhammer.com → mineracks namespace
via single sed pass; verified no orphan refs remain. go.mod adopts
upstream's full dep set plus the replace-directive for the patched
kortschak/qr fork.

  go build ./...     clean (all 27 packages)
  go test ./...      clean (12 packages with tests, all passing)

NOT lifted in this commit:
  - driver/{wshat,drm,libcamera}  (hardware-specific GPIO/LCD/camera —
    will be platform-v1/-shaped abstractions instead)
  - gui/                (depends on the above; lifts in Phase 2)
  - cmd/{controller,...} (Pi binary entrypoints — not needed for the
    companion repo)
  - zbar/               (QR scanner — needs libcamera)

Next:
  - Write the SH1E reference encoder/decoder in engrave/wire/sh1e/
  - Lift Gangleri42's cmd/webnfc/ shell + retune to v1 plates
  - First buildable composer WASM with a working preview

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 18:36:40 +10:00
mineracks
d62384658d Lift bezier/ + bspline/ from Gangleri42 fork
First batch of code lifts: hardware-agnostic curve math packages.

- bezier/: cubic + quadratic Bezier curve representation +
  tessellation. Lifted verbatim from Gangleri42/seedhammer
  @ 0a3c63ef path bezier/. Stdlib only.

- bspline/: B-spline curves + an LP-based optimiser that finds the
  smallest engrave-stroke representation of a path. Lifted from
  Gangleri42/seedhammer @ 0a3c63ef path bspline/. Depends on
  bezier/ + gonum (BSD-3, brought in transitively).

Import paths rewritten from `seedhammer.com/X` to
`github.com/mineracks/seedhammer-v1-companion/X`. No other changes.

go build ./... and go test ./bezier/... ./bspline/... both clean.

Doc.go stubs updated to reflect LIFTED status with the source
commit pinned.

Deferred to next commit:
- backup/ (needs bc/fountain + bc/ur transitively for UR codes)
- font/{comfortaa,poppins,constant}/ (needs font/bitmap runtime)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 18:29:29 +10:00
mineracks
3696dd6b34 Initial skeleton — Phase 1 scaffolding
A browser-based companion for SeedHammer v1 hardware. Three coordinated
PWAs (composer, emulator, combined-sim) plus an optional Android wrapper,
inspired by Gangleri42's SeedHammer II fork but retargeted to v1's
Pi-Zero / WaveShare / MarkingWay hardware.

What's in this commit:

- LICENSE: Unlicense (matching upstream)
- README.md: project overview + status + roadmap
- CREDITS.md: upstream provenance + pinned baseline SHAs
- docs/architecture/: five authoritative design docs (lifted from
  the prep work in mineracks-infrastructure):
  * BASELINES.md — pinned SHAs, license audit, path-mapping table
  * v1-engrave-spec.md — MarkingWay USB-serial wire protocol audit
  * v1-buttons-and-ui.md — GPIO map, UI screen flow, keyboard map
  * sh1e-spec.md — composer-to-Pi envelope format spec
  * seedsigner-reuse.md — Pyodide strategy + jumbo (SeedSigner+) support
- Go package skeleton with doc.go contracts:
  backup, bezier, bspline, engrave, engrave/wire, engrave/wire/sh1e,
  font (+ comfortaa, poppins, constant), gui, input, internal/golden,
  platform/v1
- cmd/ entrypoints with stub main(): composer, emulator, combined-sim,
  seedsigner-sim
- web/ static-shell skeleton
- go.mod (module github.com/mineracks/seedhammer-v1-companion, Go 1.22)
- go build ./... + go vet ./... both clean

Next: lift universal packages (backup, font, bezier, bspline) from
upstream seedhammer/seedhammer at v1.3.0 verbatim.

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