Tile Sharing

Tile sharing is an optimization that reduces the number of unique 8x8 tiles in your compiled tileset. When two metatile subtiles have the same pixel shape but different colors (for example, a red roof and a blue roof with identical geometry), Porytiles can store a single tile image and render it with different palettes. This frees up tile slots in the GBA’s limited tile memory (primary tilesets have a hard cap of ~512 tiles).

How It Works

GBA Tiles Are Indexed

On the GBA, each 8x8 tile in tiles.png is a 4-bit indexed image. Each pixel is not a color, but a slot number (0–15) that points into one of the hardware palettes. When the GBA renders a tile, it looks up each pixel’s slot number in the assigned palette to get the actual color.

This means the same tile image can produce completely different colors depending on which palette is assigned to it. If two palettes have different colors at the same slot positions, one tile image renders correctly with both.

Color-Isomorphic Tiles

Porytiles identifies color-isomorphic tiles: tiles that share the same pixel shape (which pixels form which regions) but assign different colors to those regions. For example:

  • A tile with a red triangle on a green background

  • A tile with a blue triangle on a green background

These two tiles have identical geometry (the triangle occupies the same pixels), just with different colors in some regions. They are candidates for tile sharing.

For secondary tilesets compiled with a paired primary, color-isomorphic detection extends across the tileset boundary. Tiles in the secondary can share tile images with tiles already present in the primary. See Cross-Tileset Sharing for details.

Slot Alignment

For sharing to work, corresponding colors must occupy the same slot index in their respective palettes. Consider:

Palette 0:  [ transparent | green | brown | RED   | ... ]
Palette 1:  [ transparent | green | brown | BLUE  | ... ]
              slot 0        slot 1  slot 2  slot 3

With this layout, an indexed tile where the triangle pixels are 3 renders as red with Palette 0 and blue with Palette 1, using a single tile image. But if blue ended up at slot 7 instead of slot 3, you would need two separate tile images. That wastes a tile slot.

How Alignment Works

The greedy alignment pipeline has four stages. Understanding these helps explain the diagnostic messages you may see. We will walk through a concrete example to illustrate each stage. (The optimal alignment mode, not yet implemented, will use a different approach based on constraint satisfaction rather than this staged pipeline.)

Example setup: Suppose you have two color-isomorphic tiles, a red house and a blue house with the same pixel shape. Both use transparent, brown, and green, but differ in their accent color (red vs. blue). After packing, the red house lands in Palette 0 and the blue house lands in Palette 1.

Stage 1: Build Draft Palettes

Colors are assigned to palette slots sequentially, with no sharing considerations. This produces a “draft” palette layout that the system uses to map each tile to its hardware palette.

In our example, the draft palettes look like this (JASC .pal format, each line is one slot):

Palette 00.pal (draft):               Palette 01.pal (draft):
  Slot 0:  0 128 0     (transparent)      Slot 0:  0 128 0     (transparent)
  Slot 1:  139 90 43   (brown)            Slot 1:  50 180 50   (green)
  Slot 2:  220 40 40   (red)              Slot 2:  139 90 43   (brown)
  Slot 3:  50 180 50   (green)            Slot 3:  40 40 220   (blue)
  Slot 4:  255 255 0   (yellow)           Slot 4:  255 105 180 (pink)
  ...                                     ...

Notice that the non-shared colors (yellow, pink) are mixed in, and the corresponding colors land at different slots: brown is at slot 1 in Palette 0 but slot 2 in Palette 1. This means the red house tile indexes as {1, 2, 3} (brown, red, green) while the blue house indexes as {2, 3, 1} (brown, blue, green) — different indexed tiles, so they can’t share yet.

Stage 4: Verify Alignment

Each color-isomorphic group is checked against the final palettes. The system indexes each member’s tile against its assigned palette and checks whether all members produce identical indexed output. Groups where every member matches are confirmed as sharing-aligned, and the duplicate tiles are removed.

Verification can be partial. Not all members of a group may produce matching indexed output. If some members’ indexed tiles diverge from the reference (e.g., due to link resolution failures affecting only some palettes), those members are dropped from the sharing result. The group can still succeed with the remaining subset, as long as at least two members across at least two palettes still align. In this case, the group is reported as “partially succeeded” rather than “succeeded.”

In our example, both the red house and blue house now index as {1, 2, 3}: identical indexed tiles. One tile image is stored in tiles.png, and the GBA renders it with Palette 0 (showing red) or Palette 1 (showing blue) as needed. One tile slot saved.

Cross-Tileset Sharing

When compiling a secondary tileset with a paired primary, Porytiles extends shape group analysis across the tileset boundary. Tiles in the secondary that are color-isomorphic with tiles already present in the compiled primary can share tile images, freeing slots in the secondary’s tile memory.

How It Works

The compiler reconstructs the primary’s tiles from its compiled tiles.png and palette data, converting them back to RGBA pixel tiles. These reconstructed tiles are then included alongside the secondary’s own tiles during shape group analysis. If a secondary tile and a primary tile share the same pixel shape (differing only in color), they form a color-isomorphic group that spans the tileset boundary.

Primary tiles have fixed palette assignments. They are already compiled and their palette slots cannot be moved. During alignment, primary tile members act as immovable references: the secondary’s palette slots are adjusted to match the primary’s existing layout, never the other way around.

Why It Matters

Secondary tilesets have their own tile slot budget. Without cross-tileset sharing, the secondary must store its own copy of every tile image, even if an identical shape already exists in the primary. Cross-tileset sharing lets the secondary reuse tile images from the primary, freeing those slots for other tiles.

Automatic Behavior

No additional configuration is required. Cross-tileset sharing happens automatically whenever:

  1. You are compiling a secondary tileset with a paired primary.

  2. Tile sharing settings (packing and/or alignment) are active.

If tile sharing is disabled entirely (both packing and alignment set to off), cross-tileset analysis is still performed for diagnostic purposes but has no effect on the output.

Diagnostics

Cross-tileset groups appear in the standard tile sharing diagnostics (see Reading the Diagnostics). When primary tiles are part of a shape group, the diagnostics include additional annotations:

  • Phase 1 (tile-sharing-shareable-tiles-<N>): Shows the count of color versions originating from the paired primary, e.g., “‘2’ color version(s) from paired primary.” Primary tile art is displayed alongside the secondary’s color versions.

  • Phase 2 (tile-sharing-palette-partition-<N>): Notes “Includes cross-tileset member(s) from paired primary.” when primary tiles are present in the partition group.

  • Phase 3 (tile-sharing-result-<N>): Notes “Includes cross-tileset member(s) from paired primary.” when aligned groups include primary tile members. Representative tiles per palette are shown for both secondary and primary members.

Configuration

Tile sharing is controlled by two configuration options that work together: one controls how tiles are distributed across palettes during packing, and the other controls whether palette slot alignment is attempted.

Packing

CLI: --tile-sharing-packing <mode> YAML: tileset.tiles.sharing.packing

Packing

off (default)

Packing ignores tile sharing entirely. Color-isomorphic tiles may end up in the same palette or different palettes by chance. Any sharing that occurs is coincidental.

biased

The packer applies a soft cost penalty that steers color-isomorphic tiles toward different palettes. This creates more sharing opportunities by ensuring tile variants are spread across palettes rather than clustered in one.

optimal

Hard constraint that rejects placing color-isomorphic tiles in the same palette. Not yet implemented.

Alignment

CLI: --tile-sharing-alignment <mode> YAML: tileset.tiles.sharing.alignment

Alignment

off (default)

Palettes are filled sequentially with no sharing alignment. Any slot alignment that occurs is coincidental.

greedy

Best-effort alignment via indirect links. The system attempts to align palette slots for all detected color-isomorphic groups. Recommended for most users who want tile sharing.

optimal

Globally optimal alignment using constraint satisfaction. Not yet implemented.

Reading the Diagnostics

Porytiles emits several diagnostic remarks during tile sharing analysis. Each is tagged with a name you can use to filter output (see Filtering Diagnostics).

tile-sharing-shareable-tiles-<N>

Shows all detected color-isomorphic tile groups. For each group, you will see:

  • ASCII art of each distinct color version (the different color variants of the same shape)

  • Which metatile entries reference these tiles

This remark is always emitted when shape groups are detected, regardless of your configuration. It tells you: “these tiles could share if alignment succeeds.”

When compiling a secondary tileset with a paired primary, groups that include primary tiles show an additional count line: “‘N’ color version(s) from paired primary.” The primary tile art is displayed after the secondary’s color versions.

tile-sharing-shareable-tiles-summary

Emitted after all Phase 1 per-group remarks. Shows how many shareable shape groups were detected in total:

Note

TODO: insert screencap of the tile-sharing-shareable-tiles-summary remark showing the total count of shareable shape groups detected.

tile-sharing-palette-partition-<N>

After palette packing, shows which palettes each color version landed in. Groups where members span two or more palettes are eligible for sharing (a tile can only be shared across palettes, not within the same one).

If your packing is off, this remark includes a caveat explaining that the palette distribution is coincidental and may improve with biased packing.

When primary tiles are present in the partition group, the remark includes the annotation “Includes cross-tileset member(s) from paired primary.” Representative tile art is shown for both secondary and primary members.

tile-sharing-palette-partition-summary

Emitted after all Phase 2 per-group remarks. Shows how many of the detected groups survived palette packing, i.e., ended up spanning multiple palettes:

Note

TODO: insert screencap of the tile-sharing-palette-partition-summary remark showing how many detected groups survived palette packing.

tile-sharing-result-<N>

Shows groups that were successfully aligned, i.e., tiles that will actually be deduplicated in the output. For each group, you will see a representative tile per palette and the metatile entries that reference it.

There are two outcomes:

  • Full success (“Tile sharing succeeded”): All eligible members produced matching indexed tiles and will share a single tile image.

  • Partial success (“Tile sharing partially succeeded”): Some members aligned, but others diverged and were dropped. The remark shows how many color versions diverged, separated by a -------- divider. The group still shares tiles among the aligned subset.

If your alignment is off, this remark includes a caveat explaining that any alignment is coincidental and may improve with greedy alignment.

When aligned groups include primary tile members, the remark includes the annotation “Includes cross-tileset member(s) from paired primary.” Representative tiles per palette are shown for both secondary and primary members.

tile-sharing-result-summary

The aggregate summary, showing the full pipeline funnel: how many groups were detected, how many were eligible after packing, and how many were ultimately aligned.

Note

TODO: insert screencap of the tile-sharing-result-summary header line showing the pipeline funnel (detected -> eligible after packing -> aligned).

When any groups are partially aligned, the aligned count includes a breakdown of how many groups were fully aligned vs. partially aligned.

Note

TODO: insert screencap of the tile-sharing-result-summary header line when partial alignments are present, showing the “N aligned (X fully, Y partially)” breakdown.

A “Partially aligned groups” sub-section appears before any unaligned groups, showing per-group dropped color version counts.

Note

TODO: insert screencap of the “Partially aligned groups” sub-section listing per-group dropped color version counts.

If any groups failed to align entirely, the summary provides additional detail:

  • Unaligned group listing with per-group failures. Each unaligned group is shown with its color versions, which palette(s) each version ended up in (e.g., “Version ‘1’ assigned to palette(s): ‘03.pal’.”), and an inline failure breakdown showing why that specific group failed to align.

    Note

    TODO: insert screencap of an unaligned-group entry showing the group id, the color-version tile art, the per-version palette assignments, and the inline link-failure breakdown (prefilled destination conflicts, shared color conflicts, etc.).

  • Aggregate total. After all unaligned groups, a one-line summary of total link resolution failures across all groups, followed by actionable suggestions. See Understanding Alignment Failures for what each failure type means.

Understanding Alignment Failures

When alignment is active, each failure is attributed to the shape group that caused it. Failures appear inline under each unaligned group in the tile-sharing-result-summary remark, with counts and detail lines specific to that group.

Shared Color Conflicts

A very common type of alignment failure. Under each affected group, the diagnostic will show a count followed by per-record detail lines.

Note

TODO: insert screencap of a “Shared color conflict” failure entry showing the count line and a per-record “linked to” detail line that names the winning group and reference palette alongside the losing group’s desired reference.

Each detail line shows both sides of the conflict: which group won the color’s link (and to which reference palette), and which reference the losing group wanted instead.

This happens when multiple shape groups share a common color in the same palette. Common colors like brown, green, or gray often appear in many tile groups. Each group creates an indirect link for the shared color pointing to a different reference palette and target slot. Since a color can only have one link, the system applies the first link it encounters and silently drops later ones (first-writer-wins).

The losing group’s alignment fails because the shared color lands at the slot chosen by the winning group, not the slot the losing group needed.

Note

When two groups both link the same color to the same reference (same palette, same color), the links are compatible. The existing link already satisfies both groups. Compatible links are automatically detected and are not reported as conflicts. Only genuinely incompatible links (different reference palette or different reference color) appear in the diagnostic output.

In practice, color variants of the same visual element (for example, a red building and a blue building with identical tile geometry) naturally produce compatible links. Their shape groups all create links pointing in the same direction for any shared colors, so no conflicts arise. Conflicts typically occur between shape groups from different visual elements that happen to share a common color in the same palette. For example, a building’s shape group might link a shared green to a brown in another palette (because green and brown are corresponding colors in the building’s two variants), while a tree’s shape group links that same green to a yellow in a third palette (because green and yellow correspond in the tree’s variants). Same source color, different destinations. That is a conflict.

Greedy alignment cannot resolve this. The optimal alignment (not yet implemented) would resolve these conflicts globally using constraint satisfaction, finding slot assignments that satisfy all groups simultaneously rather than processing them one at a time.

Prefilled Source Conflicts

A color that needs to be linked to a reference in another palette is itself prefilled (locked) in its source palette. Under each affected group, the diagnostic shows which palette and colors are involved.

Note

TODO: insert screencap of a “Prefilled source conflict” failure entry showing the count line and the detail line naming the locked source color, its source palette, and the reference color and palette the link targeted.

Because the color is locked at its current slot, the alignment system cannot reassign it to follow the reference color’s slot position. The link is dropped entirely and the shape group’s alignment fails.

This is distinct from a prefilled destination conflict, where the destination slot is blocked by a locked color. Here, the source color itself is locked and cannot participate in linking at all.

If the prefilled color is not critical at its current position, rearranging or wildcarding it in your palette files may allow the link to be applied.

Prefilled Destination Conflicts

A color needs to go at a specific slot to match another palette, but that destination slot is already occupied by a prefilled (locked) color from your input palettes. Under each affected group, the diagnostic shows exactly which palette, slot, and colors are involved.

Note

TODO: insert screencap of a “Prefilled destination conflict” failure entry showing the count line and the detail line naming the blocked palette, slot index, incoming color, and the locked color occupying the slot.

Prefilled slots are colors you explicitly placed in your palette files. They cannot be moved by the alignment system. If these slots are blocking alignment, you may be able to rearrange them (see Improving Tile Sharing Results).

Post-Resolution Slot Mismatch

All indirect links for this group were applied and resolved successfully, but the final slot positions don’t match. This happens when a later resolution step (for a different group or different palette) evicts a color from the slot it was placed at, displacing it to a different position. Under each affected group, the diagnostic shows where each color ended up versus where it should have been.

Note

TODO: insert screencap of a “Post-resolution slot mismatch” failure entry showing the count line and the detail line comparing where each color actually ended up versus its intended reference slot in the other palette.

The greedy alignment processes palettes sequentially (palette 0 first, then 1, 2, …). When palette 1 resolves its links against palette 3’s current state, and palette 3 is later processed and its own resolutions evict colors to different slots, palette 1’s already-resolved positions become stale. The optimal alignment (not yet implemented) would resolve all palettes globally and avoid these cascading evictions.

Improving Tile Sharing Results

Tile sharing is best-effort. Not every color-isomorphic group will align successfully, and that is normal. Individual groups can also partially succeed: some members share a tile while others fall back to separate tiles due to alignment divergence. Even partial sharing can significantly reduce your tile count. Here are ways to improve results:

Use biased packing. This is the single most impactful change. It actively distributes color-isomorphic tiles across different palettes, giving the alignment system more groups to work with. Without it, tiles may cluster in the same palette where sharing is impossible.

If you see prefilled destination conflicts, the diagnostic tells you exactly which palette slots are blocking alignment and which colors are involved. If those prefilled colors are not critical to your tileset layout, rearranging them to different slot positions may allow more groups to align.

If you see prefilled source conflicts, a color that needs to be linked is itself locked in its palette. The diagnostic shows which prefilled color is blocking the link. If that color is not critical at its current position, rearranging or wildcarding it may allow the link to be applied.

If you see shared color conflicts, the detail lines show which groups are competing and what reference each wanted. Multiple shape groups are competing for the same color’s slot position. Greedy alignment cannot resolve these conflicts. The optimal alignment (not yet implemented) would handle them globally. In the meantime, shared color conflicts are expected and usually harmless; the groups that did align represent genuine savings. Note that compatible links (where two groups want the same reference) are automatically detected and not reported as conflicts.

If you see post-resolution slot mismatches, colors were displaced by eviction after they were already successfully placed. The detail lines show the final slot positions for each mismatched color pair. The sequential greedy approach cannot prevent these cascading evictions. The optimal alignment (not yet implemented) would avoid them by considering all palette constraints simultaneously.

Design tiles with sharing in mind. Tiles that share the same geometric pattern (identical pixel layout, different colors) are sharing candidates. When creating color variants of buildings, terrain, or other elements, keeping the pixel shape exactly the same maximizes sharing potential. Even a single pixel difference breaks the color-isomorphic relationship.

Filtering Diagnostics

You can control which tile sharing diagnostics appear in the output using the --diagnostic-remarks-exclude and --diagnostic-remarks-include CLI flags (or their YAML equivalents). These accept regex patterns matched against the remark tag names.

Both flags are list-valued. Repeat them on the CLI to specify multiple patterns:

--diagnostic-remarks-exclude 'pattern1' --diagnostic-remarks-exclude 'pattern2'

Or in YAML:

diagnostic:
  remarks:
    exclude:
      - 'pattern1'
      - 'pattern2'

Suppress all tile sharing remarks:

--diagnostic-remarks-exclude 'tile-sharing-.*'

Show only the final summary:

--diagnostic-remarks-exclude 'tile-sharing-.*' --diagnostic-remarks-include 'tile-sharing-result-summary'

Show only the per-phase summaries (skip per-group details):

--diagnostic-remarks-exclude 'tile-sharing-.*' \
  --diagnostic-remarks-include 'tile-sharing-shareable-tiles-summary' \
  --diagnostic-remarks-include 'tile-sharing-palette-partition-summary' \
  --diagnostic-remarks-include 'tile-sharing-result-summary'

Or expressed more succinctly:

--diagnostic-remarks-exclude 'tile-sharing-.*' --diagnostic-remarks-include 'tile-sharing-.*-summary'

Include patterns override exclude patterns, so you can exclude broadly and then selectively re-enable specific tags.

Acknowledgments

Porytiles’ tile sharing system draws significant design inspiration from borytiles by ishax-kos, a Rust-based tileset compiler for GBA Pokémon decomp projects. Three core ideas in Porytiles’ tile sharing pipeline originated in borytiles:

  • Color isomorphism detection. Borytiles introduced the concept of representing tiles as shape-indexed structures (mapping pixel regions to color indices) and grouping them by canonical shape. Porytiles adapts borytiles’ Shape_indexable_tile and get_ideal_flip canonicalization into its own ShapeTile, ShapeMask, and CanonicalShapeTile types.

  • Indirect position linking. Borytiles’ Position_in_palette enum demonstrated that palette slot assignment can be decoupled from palette construction by linking colors to other colors rather than to absolute slot indices. The key insight is that links referencing colors remain valid even when non-shared colors shift around. This is the foundation of Porytiles’ IndirectPosition system.

  • Greedy alignment algorithm. Borytiles’ account_for_palette_swaps function showed how to detect same-shape tiles across different palettes and emit indirect links pairing their corresponding colors. Porytiles extends this approach with a conflict-minimization heuristic for reference member selection and integrates the link generation into a multi-phase pipeline with diagnostic reporting.

Thank you to ishax-kos for the creative and well-engineered work in borytiles that made Porytiles’ tile sharing possible.