# Skins Representation leaves game logic. An app's board is a grid of role integers (0 = void); a skin is the one dictionary that says what each role wears; faces just apply it. ## The law - Apps emit role ids. No color names, chars, or emoji tables in `mrlyapps` or in web views - ever. - A skin is data: role -> visual. Curated per-app dictionaries live in `mrlyui::skin::` presets, keyed by the app's `skin` setting. - The state carries the resolved dictionary next to the ids, so the web grid, the canvas, the CLI, and PNG all read the same one. ## The shape `mrlyui::skin` defines the closed enum: - `Visual { bg, motif, face }` - a cell background, an optional motif pattern (bg doubles as its color), an optional face on top. - `Face::Glyph(text)` - baked from the bitmap font. - `Face::Emoji(value)` - a unicode string; DOM-only until the emoji atlas exists (`FACE.md`), so canvas surfaces skip it. - `Face::Sprite(rows)` - a small bitmap, e.g. chess pieces. On the wire, state gains two keys: `ids` (rows of role integers) and `skin` (an array indexed by role; each entry `{ bg?, motif?, face?: { as, value | rows } }`). `Skin::tileset(k, ink)` builds the canvas `TileSet` from the same dictionary: solid or motif base, glyph faces baked in ink, emoji faces skipped. Web views use `src/skin.tsx` (`visual()`, `face()`). ## Why Same seed, same verbs, N skins: identical id grids, N renders. One replayed episode times N skins is N perfectly labeled samples with identical semantics - the gym's augmentation story lives in this layer.