API Reference¶
Auto-generated from Godot ## doc-comments in addons/penta_tile/**/*.gd. Do not edit by hand — modify the source comments and rebuild docs. Regeneration is wired via tools/mkdocs_hooks.py (on_pre_build).
Only documented public methods (no leading underscore) and documented @export properties are included. Undocumented members are intentionally omitted — add a ## block above the member to surface it here.
PentaTileMapLayer extends TileMapLayer¶
Source: addons/penta_tile/penta_tile_map_layer.gd
Dual-grid autotiling TileMapLayer subclass driven by a PentaTileLayout resource.
Users paint with inherited TileMapLayer.set_cell and TileMapLayer.erase_cell;
this node dispatches those logic cells into visual cells through _update_cells.
The layout resource defines the mask topology and slot resolution.
Contract invariants:
- When tile_set is null and layout is not null,
the layer auto-fills tile_set from PentaTileLayout.get_fallback_tile_set
(PREVIEW-03).
- User-supplied tile_set is never overwritten; _set flips
_tile_set_is_fallback to false on direct assignment (PREVIEW-04).
- logic_layer_opacity = 0.0 hides the parent's raw cells via
CanvasItem.self_modulate.a, not CanvasItem.visible; see
Critical Pitfall #7.
See: - .planning/research/PITFALLS.md - .planning/ROADMAP.md Phase 04 Fallback Routing
Exports¶
atlas_source_id¶
@export var atlas_source_id: int = -1
Source TileSetAtlasSource ID for atlas reads. -1 means
"use the first source discovered in tile_set." Set explicitly only
when the user's TileSet has multiple sources.
layout¶
@export var layout: PentaTileLayout = _DEFAULT_LAYOUT_SCRIPT.new()
The PentaTileLayout resource that defines this layer's mask topology and
slot resolution. Setting this auto-fills tile_set from
PentaTileLayout.get_fallback_tile_set if no user-supplied TileSet is
bound (PREVIEW-03). Default value is a fresh PentaTileLayoutPenta per node;
clear to null for native TileMapLayer passthrough.
logic_layer_opacity¶
@export var logic_layer_opacity: float = 0.0
Visibility of the parent's raw non-dispatched cells, applied via
CanvasItem.self_modulate.a. Defaults to 0.0 so only the
dispatched _PentaTileVisual child renders. Using modulation
instead of CanvasItem.visible avoids Critical Pitfall #7.
visual_z_index_offset¶
@export var visual_z_index_offset: int = 0
Z-index offset applied to the generated visual layer relative to this logic layer.
generated_collision_enabled¶
@export var generated_collision_enabled: bool = true
Enables collision on the generated visual layer that carries dispatched tiles.
logic_collision_enabled¶
@export var logic_collision_enabled: bool = false
Enables collision on this hidden logic layer when callers need raw-cell collision.
Methods¶
rebuild()¶
func rebuild() -> void
Force a full re-dispatch of all painted cells. Useful after external changes
to the source tile_set that Resource.changed did not fire on,
such as direct TileSet.add_source mutation.
PentaTileAtlasSlot extends Resource¶
Source: addons/penta_tile/penta_tile_atlas_slot.gd
A single atlas-slot record. Returned by PentaTileLayout.mask_to_atlas and consumed by PentaTileMapLayer._paint_with_slot.
Exports¶
atlas_coords¶
@export var atlas_coords: Vector2i = Vector2i.ZERO
The (x, y) coords of the slot in the TileSetAtlasSource grid.
Read by PentaTileMapLayer._paint_with_slot.
transform_flags¶
@export var transform_flags: int = 0
Render-time transforms applied via TileSetAtlasSource's
TileSetAtlasSource.TRANSFORM_FLIP_H,
TileSetAtlasSource.TRANSFORM_FLIP_V, and
TileSetAtlasSource.TRANSFORM_TRANSPOSE flags OR'd together. Shares
one int with alternative_tile per Critical Pitfall #1; combine
via PentaTileLayout._pack_alternative.
alternative_tile¶
@export var alternative_tile: int = 0
Alt-tile id in the source's alternative grid. MUST be < 4096; the upper bits
are reserved for transform_flags per Critical Pitfall #1.
PentaTileLayout extends Resource¶
Source: addons/penta_tile/layouts/penta_tile_layout.gd
Abstract base for all PentaTile layout topologies.
Subclasses implement compute_mask + mask_to_atlas + is_dual_grid. Each layout owns its mask topology (which neighbors / corners / edges feed bits) and its slot resolution (mask -> AtlasSlot).
See: - .planning/research/layouts/MASK_UNIFICATION.md §3 (Approach B selection) - .planning/research/layouts/TEMPLATE_CONVENTIONS.md §5 (dual-grid declaration) - .planning/research/PITFALLS.md §3 (_pack_alternative recipe)
@experimental
Exports¶
bitmask_template¶
@export var bitmask_template: Texture2D
Single PNG that serves as both the inspector preview and the source pixels
for get_fallback_tile_set. Renamed from Phase 1's
template_image per LAYOUT-03.
description¶
@export var description: String = ""
Multiline description of the layout's mask topology, atlas grid shape, and intended use case. Surfaces in inspector help.
Methods¶
compute_mask()¶
func compute_mask(_coord: Vector2i, _sample_fn: Callable) -> int
Compute the layout-specific mask for _coord using _sample_fn
as the neighbor-presence query.
Returns the mask integer the layout's mask_to_atlas consumes.
Subclasses must override this abstract base implementation.
is_dual_grid()¶
func is_dual_grid() -> bool
Return true if this layout paints at the dual-grid half-cell
offset, or false if it paints directly at the logic cell. See
Critical Pitfall #8 for the single-grid logic-painted gate.
PentaTileLayoutBlob47Godot extends PentaTileLayout¶
Source: addons/penta_tile/layouts/penta_tile_layout_blob_47_godot.gd
Blob47Godot — 47-tile blob layout, 8-bit Moore mask, single-grid.
Mask convention (D-76): N=1, E=2, S=4, W=8, NE=16, SE=32, SW=64, NW=128. NOT the canonical CR31 clockwise ordering — see _collapse_8bit_moore for the algorithm. The 256→47 collapse rule (D-78) per BorisTheBrave's reference (https://www.boristhebrave.com/permanent/24/06/cr31/stagecast/wang/blob.html): "A corner bit only matters if both adjacent edges are set."
Atlas: 7×7 (Caeles-canonical packing — 47 used cells + 2 unused; the unused cells stay transparent in the bundled bitmask PNG. Atlas slot coords are computed by sorting the 47 reachable masks ascending and packing row-major: index → (col=index%7, row=index/7).) Single-grid: yes — paints directly at the logic cell.
Slot table sourced from: BorisTheBrave's published 47-blob reference (D-74). The 47-mask list is what the collapse rule emits when fed all 256 raw inputs (verified via blob_47_collapse_test).
Variation banks: not supported — this is a 1-cell-per-mask layout. (Variation deferred to v2 backlog VAR-PIXEL-01.)
Methods¶
is_dual_grid()¶
func is_dual_grid() -> bool
Blob47Godot is single-grid: it paints directly on logic-painted cells.
compute_mask()¶
func compute_mask(coord: Vector2i, sample_fn: Callable) -> int
Compute the raw 8-bit Moore-neighborhood mask for coord.
sample_fn reports painted logic cells for N/E/S/W and diagonals; the
raw mask is collapsed by _collapse_8bit_moore before atlas dispatch.
mask_to_atlas()¶
func mask_to_atlas(mask: int, _strip_index: int = 0) -> PentaTileAtlasSlot
Look up mask in the 47-entry BorisTheBrave atlas convention.
The raw 8-bit mask collapses first; unmapped masks defensively fall through to slot (0, 0). Mask 0 is a valid single-grid isolated-cell dispatch per Critical Pitfall #9.
PentaTileLayoutDualGrid16 extends PentaTileLayout¶
Source: addons/penta_tile/layouts/penta_tile_layout_dual_grid_16.gd
DualGrid16 layout — 4×4 atlas, 16 unique tiles, 4-bit corner mask.
Mask convention: TL=1, TR=2, BL=4, BR=8 (corner mask matching Godot's stock
dual-grid template + the dandeliondino addon's tile_map_dual convention).
NO rotation reuse — every one of the 16 mask states has a dedicated authored
tile in a 4-column × 4-row grid. Use this layout when your atlas already
ships all 16 corner-mask variants and you want pixel-perfect control.
Atlas layout (mask = column + row * 4): Row 0: mask 0 | mask 1 | mask 2 | mask 3 Row 1: mask 4 | mask 5 | mask 6 | mask 7 Row 2: mask 8 | mask 9 | mask 10| mask 11 Row 3: mask 12| mask 13| mask 14| mask 15
Dual-grid: yes — paints at the half-tile-offset display cell.
Methods¶
is_dual_grid()¶
func is_dual_grid() -> bool
DualGrid16 paints on the dual-grid half-tile offset.
compute_mask()¶
func compute_mask(coord: Vector2i, sample_fn: Callable) -> int
Compute the 4-bit corner mask for coord using TL=1, TR=2, BL=4, BR=8.
sample_fn reports which logic cells are painted.
mask_to_atlas()¶
func mask_to_atlas(mask: int, _strip_index: int = 0) -> PentaTileAtlasSlot
Convert mask to its dedicated 4x4 atlas slot.
Mask 0 returns null because empty dual-grid display cells erase.
PentaTileLayoutMinimal3x3 extends PentaTileLayout¶
Source: addons/penta_tile/layouts/penta_tile_layout_minimal_3x3.gd
Minimal3x3 layout — 3×3 atlas, 9 unique tiles, 4-bit edge mask.
Mask convention: T=1, E=2, B=4, W=8 (cardinal-edge mask, matching Wang2Edge naming). Single-grid: yes.
This layout covers the 9-tile minimum for cardinal-edge autotiling: a 3×3 grid where the center tile is the "fully connected" interior, the corners are the four outer-corner tiles, and the edges are the four cardinal-edge tiles. With only 9 unique tiles, several mask states must reuse tiles — the "open-side" rule collapses all 16 mask states onto the 9-tile palette:
A tile lives at the atlas column matching open-W/open-E and atlas row matching open-T/open-B. "Open" means that cardinal neighbor is absent (bit NOT set). When both sides on an axis are open (or both closed), col/row = 1 (center). Diagonal-only collisions (e.g. both T and B absent → col/row both forced to 1) collapse to the center tile — accepted visual loss inherent to the 9-tile minimum.
Atlas layout (3 columns × 3 rows, row-major): Row 0 (open-T): NW corner | N edge | NE corner Row 1 (center): W edge | center fill | E edge Row 2 (open-B): SW corner | S edge | SE corner
Column assignment: open-W only → col 0 | neither/both → col 1 | open-E only → col 2 Row assignment: open-T only → row 0 | neither/both → row 1 | open-B only → row 2
Methods¶
is_dual_grid()¶
func is_dual_grid() -> bool
Minimal3x3 is single-grid: it paints directly on logic-painted cells.
compute_mask()¶
func compute_mask(coord: Vector2i, sample_fn: Callable) -> int
Compute the 4-bit cardinal-edge mask for coord using T=1, E=2, B=4, W=8.
sample_fn reports which neighboring logic cells are painted.
mask_to_atlas()¶
func mask_to_atlas(mask: int, _strip_index: int = 0) -> PentaTileAtlasSlot
Dispatch mask to the collapsed 3x3 atlas slot.
PentaTileLayoutPenta extends PentaTileLayout¶
Source: addons/penta_tile/layouts/penta_tile_layout_penta.gd
PentaTile Penta layout — 5-archetype dual-grid autotiling.
Slot ordering (LOCKED in Phase 2 architectural sweep): 0 = IsolatedCell (always present; synthesizes OuterCorner across all modes; feeds other archetypes when their slot is unfilled) 1 = Fill (added at TWO mode and above) 2 = Border (added at THREE mode and above; visual-frequency ordering puts Border before InnerCorner) 3 = InnerCorner (added at FOUR mode and above) 4 = OppositeCorners (added at FIVE mode)
OuterCorner is IMPLICIT — synthesized from slot 0 with rotation transforms across all modes. Never has a dedicated slot. (Acceptable per the user-confirmed design: an isolated cell visually IS four outer corners + edges + fill, so OuterCorner art is naturally expressed via slot 0.)
Mask convention: TL=1, TR=2, BL=4, BR=8 (corner mask).
ANCHORING NOTE (Excalibur.js cross-reference): PentaTile anchors mask 9 (TL+BR,
"\" diagonal) as the unrotated OppositeCorners case (_ROTATE_0). The Excalibur.js
dual-grid reference (https://excaliburjs.com/blog/Dual%20Tilemap%20Autotiling%20Technique/)
uses the opposite anchor (mask 6 = TR+BL = "/" diagonal). Both are valid conventions.
If you author your OppositeCorners tile against the Excalibur convention, mask 6 and
mask 9 will appear swapped — flip the sprite horizontally to match PentaTile's
anchoring. PentaTile picks mask 9 = _ROTATE_0 because it matches the project's
TL=1 lowest-bit-first ordering (also used in draw_corner_mask in the bitmask
generator script and across all corner-mask layouts in the project).
CODENAME DISCIPLINE: "Penta" is reserved exclusively for the 5-archetype tileset format. This file is the canonical home of that codename. See CLAUDE.md § Coined-Term Discipline.
Dual-grid: yes — paints at the half-tile-offset display cell. Synthesis: see PentaTileSynthesis (penta_tile_synthesis.gd).
Exports¶
axis¶
@export var axis: Axis = Axis.HORIZONTAL
Strip axis: Axis.HORIZONTAL reads authored slots along X, while
Axis.VERTICAL reads them along Y. The synthesized output atlas
remains axis-invariant at Vector2i(slot, strip_index).
tile_count¶
@export var tile_count: TileCountMode = TileCountMode.AUTO
Source tile count mode. TileCountMode.AUTO detects the strip size
from the active atlas dimension; TileCountMode.AUTO_STRIP detects
each strip independently; explicit ONE through FIVE skip detection.
Methods¶
is_dual_grid()¶
func is_dual_grid() -> bool
Penta is a dual-grid layout: it paints at the half-tile-offset display cell.
needs_synthesis()¶
func needs_synthesis() -> bool
Penta requires runtime synthesis so ONE through FOUR authored modes still expose the full five-archetype dispatch table.
compute_mask()¶
func compute_mask(coord: Vector2i, sample_fn: Callable) -> int
Compute the 4-bit corner mask for coord using TL=1, TR=2, BL=4, BR=8.
sample_fn is the logic-cell presence query supplied by
PentaTileMapLayer.
mask_to_atlas()¶
func mask_to_atlas(mask: int, strip_index: int = 0) -> PentaTileAtlasSlot
Dispatch mask to a Penta slot in strip_index.
resolve_display_strip()¶
func resolve_display_strip(coord: Vector2i, sample_atlas_fn: Callable) -> int
Return the selected source strip for coord.
PentaTileLayoutPixelLabSideScroller extends PentaTileLayout¶
Source: addons/penta_tile/layouts/penta_tile_layout_pixel_lab_side_scroller.gd
PixelLabSideScroller — 8x8 atlas, single-grid, 4-bit corner mask.
Mask convention (D-93): TL=1, TR=2, BL=4, BR=8 — same as top-down. Single-grid: yes. NO rotation reuse (D-90).
Atlas: 8 cols × 8 rows = 64 cells. Cell-to-role table comes from tileset_transform.lua's tileset_output_side variant; the role-to-mask bijection is IDENTICAL to top-down (D-94 — locked by spike 003). Both PIXLAB layouts duplicate _ROLE_TO_MASK per D-98 (GDScript 2 cannot parse cross-class const references).
Variation banks: NOT supported in v0.2 — first-cell row-major pick (D-89). Bank pick deferred to v2 backlog VAR-PIXEL-01.
Mask=0 dispatch (D-104, Pitfall #9): mask=0 → role 12 → cell (0, 0). Top-down has role 12 first appearing at (2, 2); side-scroller has it at (0, 0) (top-left corner of the side-scroller table). The cache returns the correct entry for each subclass without special-casing.
No pixellab_version: int field (D-92): per CLAUDE.md no-forward-compat
rule, future PixelLab plugin updates trigger a new release + CHANGELOG.
Source provenance:
- cell-to-role table: tileset_transform.lua:28-36 tileset_output_side
- role-to-mask bijection: spike 003 README + decode.py
Methods¶
is_dual_grid()¶
func is_dual_grid() -> bool
PixelLabSideScroller is single-grid: it paints directly on logic-painted cells.
compute_mask()¶
func compute_mask(coord: Vector2i, sample_fn: Callable) -> int
Compute the 4-bit corner mask for coord using TL=1, TR=2, BL=4, BR=8.
sample_fn reports which neighboring logic cells are painted.
mask_to_atlas()¶
func mask_to_atlas(mask: int, _strip_index: int = 0) -> PentaTileAtlasSlot
Return the cached row-major-first cell for mask.
For mask = 0, dispatches to role 12 -> cell (0, 0)
per D-104 for the side-scroller variant, unlike top-down's (2, 2).
PentaTileLayoutPixelLabTopDown extends PentaTileLayout¶
Source: addons/penta_tile/layouts/penta_tile_layout_pixel_lab_top_down.gd
PixelLabTopDown — 8x8 atlas, single-grid, 4-bit corner mask.
Mask convention (D-93): TL=1, TR=2, BL=4, BR=8 (same corner-mask convention as PentaTileLayoutDualGrid16 and PentaTileLayoutWang2Corner). Single-grid: yes — paints directly at the logic cell. NO rotation reuse (D-90): every dispatched slot uses transform_flags=0.
Atlas: 8 cols × 8 rows = 64 cells. Cells map to 16 "roles" via tileset_transform.lua's tileset_output table; roles map to 4-bit corner masks via the locked role-to-mask bijection (D-94, spike 003).
Variation banks: NOT supported in v0.2 — when multiple cells map to the same mask (e.g. mask 15 / role 6 has 28 cells), mask_to_atlas returns the row-major FIRST cell (D-89). Bank pick deferred to v2 backlog VAR-PIXEL-01 — design-coupled with VAR-01 + MULTITERR-01.
Mask=0 dispatch (D-104, Pitfall #9): mask=0 → role 12 → cell (2, 2). Single-grid mask=0 is NOT erase; the cached first-cell entry returns a valid render target so isolated cells render.
No pixellab_version: int field (D-92): per CLAUDE.md no-forward-compat
rule, future PixelLab plugin updates trigger a new release + CHANGELOG.
Source provenance:
- cell-to-role table: tileset_transform.lua:17-26 tileset_output
- role-to-mask bijection: spike 003 README + decode.py (12/16 PASS)
Methods¶
is_dual_grid()¶
func is_dual_grid() -> bool
PixelLabTopDown is single-grid: it paints directly on logic-painted cells.
compute_mask()¶
func compute_mask(coord: Vector2i, sample_fn: Callable) -> int
Compute the 4-bit corner mask for coord using TL=1, TR=2, BL=4, BR=8.
sample_fn reports which neighboring logic cells are painted.
mask_to_atlas()¶
func mask_to_atlas(mask: int, _strip_index: int = 0) -> PentaTileAtlasSlot
Return the cached row-major-first cell for mask (D-89).
For mask = 0 (isolated cell), dispatches to role 12 -> cell
(2, 2) per D-104 for the top-down variant.
PentaTileLayoutWang2Corner extends PentaTileLayout¶
Source: addons/penta_tile/layouts/penta_tile_layout_wang_2_corner.gd
Wang2Corner layout — 4×4 atlas, 16 unique tiles, 4-bit corner mask in CR31 cardinal naming.
Mask convention: CR31 NE=1, SE=2, SW=4, NW=8 (corner mask in compass terms). Single-grid: yes — paints directly at the logic cell. NO rotation reuse.
Visually identical to PentaTileLayoutDualGrid16 on the same atlas data — same silhouettes, different bit-naming convention. Use this layout if your atlas was authored against CR31 corner-naming docs; use DualGrid16 if your atlas was authored against Godot's TL/TR/BL/BR convention. Both will paint correctly; the difference is which mask bit semantically corresponds to which logic-cell quadrant in the artist's head.
NE corresponds to TR neighbor (i.e. coord + Vector2i(1, -1)). SE → BR neighbor (Vector2i(1, 1)). SW → BL neighbor (Vector2i(-1, 1)). NW → TL neighbor (Vector2i(-1, -1)).
(Note: Wang2Corner samples DIAGONAL neighbors — NE/SE/SW/NW corner cells — NOT the 2×2 corner-quadrant scheme that Penta's dual-grid uses. This is single-grid: we want to know "is the diagonal neighbor present" to decide the corner appearance.)
Methods¶
is_dual_grid()¶
func is_dual_grid() -> bool
Wang2Corner is single-grid: it paints directly on logic-painted cells.
compute_mask()¶
func compute_mask(coord: Vector2i, sample_fn: Callable) -> int
Compute the 4-bit diagonal-corner mask for coord using NE=1, SE=2, SW=4, NW=8.
sample_fn reports which diagonal logic cells are painted.
mask_to_atlas()¶
func mask_to_atlas(mask: int, _strip_index: int = 0) -> PentaTileAtlasSlot
Convert mask to its dedicated 4x4 atlas slot.
Mask 0 is a valid single-grid isolated-cell dispatch to atlas (0, 0), not an erase; see Critical Pitfall #9.
PentaTileLayoutWang2Edge extends PentaTileLayout¶
Source: addons/penta_tile/layouts/penta_tile_layout_wang_2_edge.gd
Wang2Edge layout — 4×4 atlas, 16 unique tiles, 4-bit edge mask.
Mask convention: CR31 N=1, E=2, S=4, W=8 (cardinal-edge mask). Single-grid: yes — paints directly at the logic cell (no half-tile offset). NO rotation reuse — every one of the 16 mask states has a dedicated authored tile.
Also known as 'Marching Squares' in algorithm-centric writeups (e.g., the Excalibur.js dual-grid article); same atlas, different vocabulary. Helps users arriving via marching-squares search terms find the right layout.
Atlas layout (mask = column + row * 4): Row 0: mask 0 | mask 1 | mask 2 | mask 3 Row 1: mask 4 | mask 5 | mask 6 | mask 7 Row 2: mask 8 | mask 9 | mask 10| mask 11 Row 3: mask 12| mask 13| mask 14| mask 15
Methods¶
is_dual_grid()¶
func is_dual_grid() -> bool
Wang2Edge is single-grid: it paints directly on logic-painted cells.
compute_mask()¶
func compute_mask(coord: Vector2i, sample_fn: Callable) -> int
Compute the 4-bit cardinal-edge mask for coord using N=1, E=2, S=4, W=8.
sample_fn reports which neighboring logic cells are painted.
mask_to_atlas()¶
func mask_to_atlas(mask: int, _strip_index: int = 0) -> PentaTileAtlasSlot
Convert mask to its dedicated 4x4 atlas slot.
Mask 0 is a valid single-grid isolated-cell dispatch to atlas (0, 0), not an erase; see Critical Pitfall #9.
PentaTileSynthesis extends RefCounted¶
Source: addons/penta_tile/penta_tile_synthesis.gd
Synthesis machinery for PentaTileLayoutPenta. Builds runtime TileSets from a single source TileSet by extracting sub-regions of slot 0 (IsolatedCell) and assembling synthesized archetypes per the locked anchoring spec (see .planning/phases/02-native-layouts/02-02-PLAN.md Gate 1 / Gate 2).
Determinism invariant: same (source_tile_set, axis, tile_count) → bit-identical output (PENTA-SYNTH-06). Re-runs only when these inputs change.
Slot ordering (LOCKED — Phase 2 architectural sweep): 0 = IsolatedCell (always authored; source of OuterCorner render-time rotation) 1 = Fill (synthesized from slot 0 in ONE mode; authored in TWO..FIVE) 2 = Border (synthesized from slot 0 in ONE/TWO modes; authored in THREE..FIVE) 3 = InnerCorner (synthesized from slot 0 in ONE/TWO/THREE modes; authored in FOUR/FIVE) 4 = OppositeCorners (synthesized from slot 0 in ONE..FOUR modes; authored in FIVE)
OuterCorner has NO synthesized output slot. mask_to_atlas returns slot 0 with rotation flags (ROTATE_90/180/270) at render time — Path B per Gate 1.