Initial Commit
This commit is contained in:
104
openspec/archive/2026-05-25-enterprise-emblem-solver/proposal.md
Normal file
104
openspec/archive/2026-05-25-enterprise-emblem-solver/proposal.md
Normal file
@@ -0,0 +1,104 @@
|
||||
# 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` |
|
||||
Reference in New Issue
Block a user