Files
suaveplan/openspec/archive/2026-05-25-enterprise-emblem-solver/proposal.md
2026-05-27 23:29:53 +01:00

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