Skip to content

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.