# lib-asset.prototype-blob-geom Procedurally-generated **reference assets** for the Sporel blob autotile scheme (`S-V2E2-RM-Blob`, 14-slot reduction). All sprites are simple geometric primitives, drawn programmatically by the bundled Python scripts. Intended as a **starting template** for human artists who will produce real blob-material sprite sheets (stone, wood, water, ...) that slot into the same 14-slot schema. **Version:** 0.1.0 **Lib-ID:** `lib-asset.prototype-blob-geom` **Role:** asset (pure data, no Lua code) **License:** CC0 (procedural output, no authorship claim) **Requires:** (none) ## What's in here ``` prototype-blob-geom/ ├── manifest.lib ├── LICENSE ← CC0 ├── README.md ← this file ├── .gitignore ├── assets/ │ └── _sources/ │ ├── blob_rectilinear/ ← 14 raw 64x64 PNGs + collision.json │ ├── blob_diagonal_cut/ ← (same slot count, different visual style) │ ├── blob_organic/ │ └── blob_concave/ ├── scripts/ │ ├── generate.py ← procedural writer (sprites + collision) │ ├── reference_sheet.py ← per-style overview │ ├── color_showcase.py ← runtime-tint demo │ └── bake.sh ← regen everything + atlas-baker for all 4 styles └── docs/ ├── reference_sheet_rectilinear.png ├── reference_sheet_diagonal_cut.png ├── reference_sheet_organic.png ├── reference_sheet_concave.png └── color_showcase.png ``` ## The 14-slot schema Each tile reads its 8 neighbour cells. The neighbour-bitmask is clockwise from north, LSB-first: | bit | direction | |----:|:---------:| | 0 | N | | 1 | NE | | 2 | E | | 3 | SE | | 4 | S | | 5 | SW | | 6 | W | | 7 | NW | A diagonal bit only counts if **both** adjacent cardinals are also set (blob-gating). After gating and reducing the 47 valid patterns under 90° rotation + mirror symmetry, you land on **14 canonical slots**: | Slot | Name | Canonical bitmask | Pattern (cardinals + diagonals) | |-----:|--------------|------------------:|----------------------------------------| | 0 | isolated | `0b00000000` | no neighbours | | 1 | end | `0b00000001` | 1 cardinal (N) | | 2 | corner_open | `0b00000101` | 2 adjacent cardinals (N+E), no diag | | 3 | corner_full | `0b00000111` | 2 adjacent cardinals + diagonal (NE) | | 4 | straight | `0b00010001` | 2 opposite cardinals (N+S) | | 5 | tee_open | `0b00010101` | 3 cardinals (N+E+S), no diags | | 6 | tee_half | `0b00010111` | 3 cardinals + 1 diag (NE) | | 7 | tee_full | `0b00011111` | 3 cardinals + 2 diags (NE+SE) | | 8 | cross_open | `0b01010101` | 4 cardinals, 0 diags | | 9 | cross_q1 | `0b01010111` | 4 cardinals + 1 diag | | 10 | cross_q2adj | `0b01011111` | 4 cardinals + 2 adjacent diags | | 11 | cross_q2opp | `0b01110111` | 4 cardinals + 2 opposite diags (NE+SW) | | 12 | cross_q3 | `0b01111111` | 4 cardinals + 3 diags | | 13 | solid | `0b11111111` | fully surrounded | At runtime the engine (or atlas-baker, future M.2+) maps every one of the 47 raw blob patterns to one of these 14 slots by applying the canonical rotation + mirror needed to bring the raw pattern to its canonical form. Artists only ever draw the 14 base sprites. ## Styles All 4 styles share the **same filled-sub-cell mask per slot** (every 64×64 tile is conceptually a 4×4 grid of 16×16 sub-cells; the filled-sub-cell logic is identical across styles). They differ only in how **external convex corners** of the material region are rendered: - The mini neighbour-diagram in the bottom-right of each cell shows which of the 8 surrounding cells share material with the centre cell. - Slot 13 (`solid`) is identical across all styles because there are no external convex corners when material continues seamlessly into all 8 neighbours. - All other slots have at least one external boundary corner, and the styles diverge there. ### Rectilinear Sharp 90° corners everywhere. Crisp, blocky, pixel-perfect look. Suits **dungeon walls, stone floors, retro pixel art**. Easiest to author custom art for — every sub-cell is just a filled rectangle. ![Rectilinear reference sheet](docs/reference_sheet_rectilinear.png) ### Diagonal-cut External convex corners chamfered with a 45° straight line. Gives an **architectural, octagonal feel** — slot 0 reads as an octagon, slot 1 as a "tab", slot 13 stays flat (no boundary). Good for **isometric-feel walls, polished stone, art-deco trim**. ![Diagonal-cut reference sheet](docs/reference_sheet_diagonal_cut.png) ### Organic External convex corners replaced with smooth quarter-arc curves (rounded-rectangle treatment). Gives a **bauchy, flowing, blob-like feel**. Suits **water, moss, lava, slime, vegetation** — anything that should look like it has surface tension. ![Organic reference sheet](docs/reference_sheet_organic.png) ### Concave External convex corners bitten with an inward quarter-disk. The material boundary at corners has **concave indentations**, giving a **filigree, crystalline, spider-web feel**. Suits **ice frost, cracks, mycelium, arcane growths** — anything that should look like it's reaching out fragilely. ![Concave reference sheet](docs/reference_sheet_concave.png) ## Collision Each style's source directory ships a `collision.json` describing the per-slot collision footprint. Geometry is expressed as a list of axis-aligned rectangles (row-wise + column-wise merged to the minimum useful count). All four styles ship **the same rectilinear collision**; for diagonal-cut / organic / concave the visible boundary deviates by at most 5 px at convex external corners, which is acceptable for typical gameplay collision. Pixel-perfect overrides are a future refinement modders can ship per material. Format (one entry per slot): ```json { "schema": "blob-14", "tile_size_px": 64, "shape_kind": "rect_list", "slots": [ { "slot": 4, "name": "straight", "bitmask": "0b00010001", "rects": [[16, 0, 32, 64]] }, { "slot": 8, "name": "cross_open", "bitmask": "0b01010101", "rects": [[16, 0, 32, 16], [0, 16, 64, 32], [16, 48, 32, 16]] }, { "slot": 13, "name": "solid", "bitmask": "0b11111111", "rects": [[0, 0, 64, 64]] } ] } ``` Worst case (cross variants) is 3 rectangles; slot 13 collapses to a single 64x64 rect; slot 4 to a single 32x64 vertical bar. The numbers are tile-local pixels; the consuming engine translates by the tile's world position. Friendly to Box2D / Chipmunk / AABB-tree physics backends. Per the design paper §6 the atlas-baker (planned E2) will fold these into a sibling `tiles.collision.json` next to the diffuse atlas. ## Color variations The atlases ship in a single neutral stone-gray. Materials don't fork the atlas — they **declare a colour tint at the material level**, and the engine multiplies the atlas's per-pixel alpha against the tint at render time. One geometric atlas, N visual materials: ![Color variations across 5 material tints](docs/color_showcase.png) This is the production architecture: defining a new material is a manifest-level declaration of `{schema, style, tint, collision_policy}`, not an asset fork. The bake-pipeline could in principle pre-tint atlases for shader-free renderers, but for the M.2+ atlas format the tint is applied at draw call time. Tints used in the showcase above: | Material | Tint | |----------|-------------| | stone | `#282C38` (default) | | grass | `#4A7838` | | earth | `#5C3D24` | | wood | `#8B6B3F` | | ice | `#7BA8C8` | Modders define their own palette in their module manifest. ## How to use this lib **As a real consumer:** add to module manifest's `deps` and `asset_aliases`, choosing which style's atlas you want: ```json "deps": [ {"id":"lib-asset.prototype-blob-geom","version":"0.1.0"} ], "asset_aliases": { "blob_demo_v1": "lib-asset.prototype-blob-geom" } ``` Maps reference slots by name (`slot_05_tee_open`) or the runtime selects them by computed bitmask. **As a template for new art:** copy the directory, rename to `lib-asset.`, then either: 1. Hand-author 14 PNGs in `_sources//`, one per slot, using one of the reference sheets above as your style guide; OR 2. Modify `scripts/generate.py` if you want to keep the procedural approach for a different geometric primitive (e.g. a chunkier blocky style or a more aggressive organic curve). The atlas-baker (`scripts/bake.sh`) packs your 14 PNGs into the runtime atlas format — same pipeline as `prototype-subterrain` and every other Sporel asset lib. ## Regenerating ```bash python scripts/generate.py # 14*4 = 56 base sprites + 4 collision.json python scripts/reference_sheet.py # 4 reference sheets python scripts/color_showcase.py # color showcase image bash scripts/bake.sh # all of the above + atlas-bake all 4 styles ``` Dependencies: Python 3.10+ with Pillow; Node.js (for atlas-baker). ## References - Design paper: [`sporel-meta/docs/design/2026-05-28-autotile-blob-styles-design.md`](../../../sporel-meta/docs/design/2026-05-28-autotile-blob-styles-design.md) - Boris the Brave, ["Classification of Tilesets"](https://www.boristhebrave.com/2021/11/14/classification-of-tilesets/) — origin of the `S-V2E2-RM-Blob` notation - cr31, ["Blob Tileset"](https://www.cr31.co.uk/stagecast/wang/blob.html) — canonical 47-blob reference - ADR-0005 (Reserved namespace prefixes — `lib-asset.*` is not reserved, this is a community lib) ## Notes - **License: CC0.** This lib is safe for any context including public-facing builds, unlike the `toBeDeleted` prototype-asset libs (subterrain, fa-starter) that carry third-party licensing. - The output sprites are intentionally simple. Real game-grade art for the same schema should add: bevels, shadow under-tiles, texture noise, edge anti-aliasing, and per-style-profile collision polygons (see the design paper §6). - The four styles ship as **parallel atlases**, not as a single combined atlas. A consumer module picks one style per material; mixing styles for the *same* material within one map would create visual incoherence.