# 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 `.md` per impl file. - **STATUS.md**: flips `@suaveplan/emblem-solver` `–` → `✅`. Counter `289 → 290` migrated; `204 → 203` pending. - **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` |