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

mrlyapps or in web views - ever.

mrlyui::skin::<app> presets, keyed by the app's skin setting.

grid, the canvas, the CLI, and PNG all read the same one.

The shape

mrlyui::skin defines the closed enum:

pattern (bg doubles as its color), an optional face on top.

atlas exists (FACE.md), so canvas surfaces skip it.

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.