105 lines
5.5 KiB
Markdown
105 lines
5.5 KiB
Markdown
# 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` |
|