5.5 KiB
Proposal: Enterprise-grade migration of @suaveplan/emblem-solver
- Change ID: enterprise-emblem-solver
- Package:
@suaveplan/emblem-solver - Status: PROPOSED
- Tier: universal (zero browser/Node/React runtime deps)
- Category / plugin:
media/image - Target path:
packages/media/image/emblem-solver/ - Design: ./design.md
- Spec delta: ./specs/emblem-solver/spec.md
Summary
Migrate @suaveplan/emblem-solver from upstream
(/mnt/s/web/suaveplan/packages/universal/emblem-solver, v1.0.3) onto
genesis at enterprise quality, version 0.1.0. The package decomposes an
RGBA pixel grid (pixel-art sprite emblem) into the minimum number of
axis-aligned coloured rectangles and emits a compact SVG by layering them
with z-index so that overlap optimisations reduce the rect count below what
any non-overlapping cover can achieve.
The actual upstream implementation is a layered, multi-strategy solver — not
a single-pass scan-line — and the migration preserves that algorithmic depth
verbatim while adding Zod input validation, an EMBLEM_SOLVER.* error code
registry, injectable Rng, and OTel instrumentation.
Why
Genesis ships no emblem decomposition tool. Downstream consumers
(packer-browser, future game-server emblem packing, future client-side
guild/clan badge editors) need an enterprise-grade SVG converter for
pixel-art emblems. Upstream proves the algorithm works against
well-understood game-emblem fixtures (chessboard, donut, cross, T-shape,
circle, star, flag, shield, crest); we port those fixtures verbatim as the
algorithmic regression suite.
Hand-rolling a replacement would be worse than porting. The upstream package
is small (~2k LOC) and self-contained: zero @suaveplan/* runtime deps. It
fits cleanly into genesis without dragging a dependency train.
What changes
A net-new universal-tier package at packages/media/image/emblem-solver/.
No upstream consumer exists on genesis (grep -r "@suaveplan/emblem-solver" packages/ returns nothing), so this is purely additive.
Affected dependents
- None on genesis today. Downstream packages will start importing the public surface listed below.
Parity Checklist
| Capability | Upstream | Migrated | Notes |
|---|---|---|---|
convert(pixels, w, h, options) pure entry point |
✅ | ✅ | |
analyseGrid exported |
✅ | ✅ | |
parseRGBABuffer + fromIndexedGrid |
✅ | ✅ | |
buildSVG |
✅ | ✅ | |
| 4-connected island flood-fill | ✅ | ✅ | |
| Dual-axis histogram maximal-rect sweep | ✅ | ✅ | |
| Strip decomposition (h+v, picks smaller) | ✅ | ✅ | |
| Enhanced greedy cover (3-key tiebreak) | ✅ | ✅ | |
| Per-island independent cover | ✅ | ✅ | |
| Overlap optimiser (bbox-merge, extend-to-subsume, 3→2 cross replace) | ✅ | ✅ | |
| Adjacent-rect merge pass | ✅ | ✅ | |
| Redundancy elimination across z-stacked layers | ✅ | ✅ | |
| Heap's-algorithm permutations + factorial | ✅ | ✅ | |
Static orderings (area-desc / area-asc / island-aware / perimeter-desc) |
✅ | ✅ | |
auto mode: exhaustive ≤ N!, heuristics+random sampling above |
✅ | ✅ | Random branch now uses injectable Rng (default Math.random) |
ConvertResult.colourAnalysis exposed |
✅ | ✅ | |
| Solid / fully-transparent / single-pixel edge cases | ✅ | ✅ | |
| Sprite fixtures (chessboard, donut, cross, T, circle, star, flag, shield, crest, stripes) | ✅ | ✅ | Ported verbatim |
Zod-validated ConvertOptions |
🚫 | ⬆️ | Enhanced — runtime schema + EmblemSolverError(INVALID_OPTIONS) |
| Structured error hierarchy | 🚫 | ⬆️ | New EMBLEM_SOLVER.* registry via defineCodes |
OTel counter + histogram on convert |
🚫 | ⬆️ | New |
| Deterministic RNG injection | 🚫 | ⬆️ | New — replaces the lone Math.random shuffle site |
Property-based tests via fast-check |
🚫 | ⬆️ | New on geometric kernels (rect cover correctness, SVG round-trip) |
Co-located .md per impl file |
partial | ✅ | Per CLAUDE.md doc rule |
Subpath exports (./types, ./errors, ./svg, ./io, ./grid) |
🚫 | ⬆️ | New for tree-shaking |
No upstream feature is dropped.
Impact
- Code: ~1.8k LOC pure TypeScript in
packages/media/image/emblem-solver/src/. - Build: Standard universal-tier
vite build+ dts emit. - Tests: 100/100/100/100 via
provider: 'istanbul'. Upstream's snapshot fixtures (chessboard8x8,cross3x3,cross6x6,tShape,donut5x5,circle16,flagCrosses,multiIslandStripes,shieldEmblem32,crest12x12,star) ported verbatim as the deterministic regression set. - Docs: README at package root + sibling
.mdper impl file. - STATUS.md: flips
@suaveplan/emblem-solver–→✅. Counter289 → 290migrated;204 → 203pending. - Manifest: regenerated via
bun scripts/folder-reorg/generate-manifest.ts.
Risks & mitigation
| Risk | Mitigation |
|---|---|
auto mode blows up for >8 colours |
Hard maxPermutations cap (default 40320 = 8!), heuristic fallback identical to upstream; Zod refines maxPermutations ≥ 1 |
Non-determinism from Math.random |
Injectable Rng (defaults to Math.random for back-compat); tests pass a seeded fake to lock the random-shuffle branch |
| Coverage gaps in the heuristic branch | Dedicated tests construct >8-colour fixtures to exercise the heuristic + sampling path |
| Floating-point density / score determinism | All scoring is deterministic given inputs; no Date.now / module-scope Math.random |