calic 247cdf0575 Initial commit — procedural blob-schema reference asset lib v0.1.0
Companion implementation to the design discussion at
sporel-meta/docs/design/2026-05-28-autotile-blob-styles-design.md §9.

Ships the S-V2E2-RM-Blob 14-slot schema in four canonical style
profiles, generated procedurally so the asset lib can be rebuilt
from scratch on any platform with Python 3 + Pillow:

  Styles (14 base slots each, sub-cell-grid layout):
    rectilinear   — crisp 90-degree corners (dungeon walls, retro)
    diagonal_cut  — 45-degree chamfered convex corners (iso-feel)
    organic       — rounded quarter-arc convex corners (water/moss)
    concave       — concave-bite convex corners (frost/web)

  Material variants (single-material atlases, one tinted set each):
    blob_rect_stone   — dark blue-gray (#282C38)
    blob_rect_grass   — mid green       (#4A7838)
    blob_rect_wood    — light brown     (#8B6B3F)

  Multi-material testbench atlas:
    blob_testbench    — 3 materials x 14 slots = 42 tiles in one
                        atlas, for the vagrant-skeleton showcase

Per-style collision.json (axis-aligned rect-list, row+column merged
to minimum form: slot 13 collapses to a single 64x64 rect, slot 4
to a single 32x64 vertical bar, cross variants max 3 rects).
Phase-1: same rectilinear collision shared across all 4 styles,
max 5 px deviation at corners; per-style polygons deferred to
atlas-baker E2.

Reference sheets at docs/reference_sheet_<style>.png show all 14
slots per style in a 7x2 grid with slot index + bitmask + 3x3
neighbour mini-diagram. Color showcase at docs/color_showcase.png
demonstrates that one atlas serves N visual materials via runtime
tint (5 sample colours).

Atlas packer (scripts/atlas_pack.py) is a pure-Python substitute
for sporel-tool-atlas-baker. It produces the Sporel-conformant
4-file atlas set (tiles.atlas.json + tiles.atlas.lock.json +
tiles.diffuse.atlas.png + 1x1 placeholder tiles.height.atlas.png)
with 2 px edge-replicated padding around every tile to prevent
bilinear sampler bleed (sister-tile colours leaking at UV
boundaries — the artefact that caused the green-flicker bug
caught during 0.5.0c integration with vagrant).

Generation pipeline (scripts/bake.sh):
  generate.py        -> 56 base sprites + 4 collision.json
  reference_sheet.py -> 4 reference_sheet PNGs
  color_showcase.py  -> color_showcase.png
  bake_testbench.py  -> 42-tile mixed-material atlas
  bake_singles.py    -> 3 x 14-tile single-material atlases

All output deterministic from the seed scripts (no random state).
.gitignore excludes the baked atlases (assets/atlases/) — those
are reproducible from sources via the bake scripts.

License: CC0 (procedural output, no third-party authorship claim).
Safe for any public-facing build.

Used in production by sporel-module-vagrant-skeleton via the
asset_aliases blob_testbench + blob_rect_stone — see the matching
commit there for the consumer-side integration.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 20:51:44 +02:00

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

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

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

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

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):

{
  "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

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:

"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.<your-material-name>, then either:

  1. Hand-author 14 PNGs in _sources/<your-pack>/, 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

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

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.
Description
Procedurally-generated S-V2E2-RM-Blob 14-slot reference asset lib (4 styles, CC0). Companion to autotile-blob-styles design paper.
Readme 184 KiB
Languages
Python 92.5%
Shell 7.5%