Releases: Shilo/PentaTile
PentaTile v0.2.0
[0.2.0] — 2026-04-29
BREAKING — Project rename: TetraTile → PentaTile
The entire project has been renamed from TetraTile to PentaTile.
This is a breaking change with no backwards-compatibility shims, per the project's no-backwards-compat policy.
Renamed surface:
- Addon folder:
addons/tetra_tile/→addons/penta_tile/ - Plugin id:
tetra_tile→penta_tile - Core class:
TetraTileMapLayer→PentaTileMapLayer - Contract class:
TetraTileAtlasContract→PentaTileAtlasContract - Layout base:
TetraTileLayout→PentaTileLayout - Layout subclasses:
PentaTileLayoutPentaHorizontal,PentaTileLayoutPentaVertical - All GDScript files:
tetra_tile_*.gd→penta_tile_*.gd - All
.tres/.tscnassets:tetra_*→penta_* - Custom data layer keys:
tetra_role→penta_role,tetra_lock_rotation→penta_lock_rotation - Requirement IDs:
TETRA-01..03→PENTA-01..03,TETRA-SYNTH-01..12→PENTA-SYNTH-01..12 project.godotconfig name:"TetraTile"→"PentaTile"
Added — Penta codename anchors
README.md§ What is a Penta tileset? — canonical labeled-diagram section defining the 5 archetypes (IsolatedCell, Fill, Border, InnerCorner, OppositeCorners) and "Penta" as a coined term alongside Wang and Blob.CLAUDE.md§ Coined-Term Discipline — project invariant reserving "Penta" exclusively for the 5-archetype format; prohibitsPentaCache,PentaDecoder, or any unrelated "Penta" prefix.
BREAKING — Phase 2: Architectural Simplification + Native Layout Library
PentaTileAtlasContract deleted. layout: PentaTileLayout lives directly on PentaTileMapLayer — no contract wrapper, no version: int speculative field. Per the no-forward-compat policy.
Phase 1's PentaTileLayoutPentaHorizontal + PentaTileLayoutPentaVertical merged into a single PentaTileLayoutPenta class with two enums:
axis: Axis { HORIZONTAL, VERTICAL }tile_count: TileCountMode { AUTO, AUTO_STRIP, ONE, TWO, THREE, FOUR, FIVE }— five progressive synthesis modes per strip plus AUTO (dimension-only detection) and AUTO_STRIP (per-strip detection).
New slot ordering for the 5 Penta archetypes: 0=IsolatedCell, 1=Fill, 2=Border, 3=InnerCorner, 4=OppositeCorners. OuterCorner is implicit — synthesized from slot 0 with rotation across all modes; never has a dedicated slot (Path B).
Runtime overlay layer DELETED entirely. All v0.2 layouts render via single-layer 5-archetype dispatch. Removed: PentaTileMapLayer._overlay_layer, _OVERLAY_LAYER_NAME, _paint_overlay_for_slot(), AtlasSlot.diagonal_complement_atlas_coords. PentaTileMapLayer now has exactly ONE child visual layer.
template_image renamed to bitmask_template on PentaTileLayout base class. Single image serves as inspector preview AND fallback TileSet source — no atlas/bitmask split. fallback_tile_set @export removed; replaced by get_fallback_tile_set() virtual method that builds a TileSet from bitmask_template at runtime. decoder_image deleted (was speculative).
Bundled bitmask PNGs co-located next to layout .gd files. The old templates/ folder is deleted entirely. Penta has 10 PNGs in addons/penta_tile/layouts/penta_tile_layout_penta/{one,two,three,four,five}_{horizontal,vertical}.png. Single-variant layouts use flat siblings: penta_tile_layout_dual_grid_16.png, penta_tile_layout_wang_2_edge.png, penta_tile_layout_wang_2_corner.png, penta_tile_layout_minimal_3x3.png. Original v0.1 penta_tile_template.png deleted.
Added — Phase 2: Native Layout Subclasses
Four hand-authored layouts ship in this milestone:
PentaTileLayoutDualGrid16— 4×4 atlas with 16 explicit tiles for every dual-grid corner mask (TL=1/TR=2/BL=4/BR=8). No rotation reuse; every state maps to a unique authored tile. Usesmask % 4 = col, mask / 4 = row.PentaTileLayoutWang2Edge— single-grid 4×4 atlas, edge mask N=1/E=2/S=4/W=8 (also known as Marching Squares / Cellular Automata 2-Edge). Edges form lines and paths.PentaTileLayoutWang2Corner— single-grid 4×4 atlas, corner mask sampling diagonal neighbors NE=1/SE=2/SW=4/NW=8. Samemask%4 / mask/4formula as DualGrid16 but semantically different bit-to-neighbor mapping.PentaTileLayoutMinimal3x3— single-grid 3×3 9-tile atlas with open-side collapse rule (col/row = 0 if that side is exclusively open, 2 if exclusively closed on opposite, 1 (center) otherwise). Masks 5 (T+B) and 10 (E+W) and all isolated-diagonal states collapse to the center tile (accepted visual loss for the 9-tile minimum).
Added — Phase 2: Synthesis Engine
PentaTileSynthesis(addons/penta_tile/penta_tile_synthesis.gd) — load-time synthesis engine that generates missing archetypes for ONE/TWO/THREE/FOUR modes from the explicit slots present in the source atlas. Includes:synthesize_strip()— main entry point; dispatches perTileCountMode.clip_polygon_to_subrect()— Sutherland-Hodgman polygon clipper for collision/occlusion/navigation polygon transfer to synthesized sub-region tiles.transform_vertex()— locked Gate 2 transform order:TRANSPOSE → FLIP_H → FLIP_V.build_tile_set_from_synthesis()— wires synthesized slots to aTileSetAtlasSourcefor the layer to consume.- Signature-based idempotence — synthesis re-runs only when
(instance_id, axis, tile_count, source_id, resolved_mode)changes;rebuild()is safe to call repeatedly. - Polygon transfer — collision/occlusion/navigation polygons are copied with appropriate transforms. Animation/custom-data/probability/y-sort are NOT copied (documented as a layout-choice tradeoff).
Added — Phase 2: Auto-Detection + Configuration Warnings
- AUTO mode —
PentaTileLayoutPenta.resolve_active_mode()reads atlas axis dimension (1/2/3/4/5 → ONE/TWO/THREE/FOUR/FIVE). Atlas axis size 0 or 6+ disables rendering and emits a configuration warning. - AUTO_STRIP mode —
PentaTileLayoutPenta.resolve_strip_modes()independently detects each strip's tile count viaTileSetAtlasSource.has_tile()checks. Different strips can use different modes within a single atlas. Per-strip dispatch wired in commit 29cba37 (post-Wave 6, retroactive): the layer's_ensure_synthesized_tile_setbranches onAUTO_STRIP, callsresolve_strip_modes, threadsstrip_originper strip, builds a 5×N synthesized atlas (one row per strip; gap strips render empty + emit warning C).mask_to_atlasand_make_slotacceptstrip_index: int = 0; new virtualPentaTileLayout.resolve_display_strip(coord, sample_atlas_fn)returns the strip index for a painted display cell — Penta override picks the first non-empty TL→TR→BL→BR neighbor's source-atlas-coord (HORIZONTAL →coords.y, VERTICAL →coords.x); non-Penta layouts inherit base default = 0. Spec correction landed alongside: Wave 2'ssynthesize_stripdocstring described Interpretation B (cumulative offset along slot axis) but Wave 6'sresolve_strip_modesimplemented Interpretation A (perpendicular strips); Interpretation A locked, defaultstrip_originsentinel formula corrected toVector2i(0, strip_index)HORIZONTAL /Vector2i(strip_index, 0)VERTICAL. Mixed-strip painting documented as v0.2 best-effort (first-non-empty-neighbor wins); proper terrain transitions remain MULTITERR-* in v2 backlog. get_configuration_warnings_for(layer)virtual onPentaTileLayoutPenta— duck-typed delegation fromPentaTileMapLayer._get_configuration_warnings()surfaces atlas-size / mode-mismatch warnings in the Godot inspector.
Added — Phase 2: Determinism Test Harness
addons/penta_tile/tests/determinism_test.gd— headless Godot regression script with 4 sub-tests:- Sub-test (a):
transform_vertexworked example (all 8 flag combinations against locked Gate 2 truth table). - Sub-test (b):
clip_polygon_to_subrecthash determinism (10 invocations). - Main test: 11
rebuild()runs; asserts all hashes identical AND matchBASELINE_HASH=2986698704. - Sub-test (c): VERTICAL-axis structural coverage (WR-07 regression net) — asserts cell count matches
BASELINE_CELLS=46from HORIZONTAL AND every painted atlas coord resolves in the synthesized atlas viasource.has_tile().
- Sub-test (a):
addons/penta_tile/tests/_capture_baseline.gd— baseline capture utility with optional--layout-path=<res_path>CLI flag for capturing baselines against alternative layouts (e.g.,penta_layout_four_vertical.tres).
Run via:
Godot_v4.6.2-stable_win64_console.exe --headless --path . --script addons/penta_tile/tests/determinism_test.gd
Phase 2 UAT bug-fix sweep (2026-04-28)
Closed 7 bug classes surfaced by user UAT against the demo scene with custom artist tile_set artwork. Commits 6553380 through 205fb67.
Bugs fixed:
-
Bundled bitmask greyboxes (
addons/penta_tile/_generate_bitmasks.py) iterated through 4 silhouette designs before settling on the right shape per layout. Single-grid edge-mask layouts (Wang2Edge, Min3x3) now ship solid 32×32 atlases — partial-quadrant fills don't compose without dual-grid's half-tile offset, so single-grid uses solid silhouettes (artist's per-tile artwork carries the visual variation). Wang2Corner gained its own solid 32×32 atlas instead of reusing DualGrid16's partial-quadrant atlas (Wang2Corner is single-grid; DualGrid16's atlas is for dual-grid composition). Penta dual-grid layouts keep per-archetype shapes for slots 0–4 (slot 0 = BL quadrant only, slot 1 = full, slot 2 = bottom half, slot 3 = L-shape, slot 4 = TL+BR diagonal). -
PentaTileMapLayer._paint_via_layout— single-grid logic-painted gate. Previously, marking cardinal neighbors as affected caused them to also paint their own visual tile, extending the painted region by a full cell. Single-grid layouts now skip painting non-logic-painted cells (cardinal neighbors still trigger re-renders of their painted neighbors w...