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.
The Indirect Link System
Porytiles uses an indirect link mechanism to align palette slots. Rather than dictating “put this color at slot 5,” the system creates links between corresponding colors: “this red in palette 0 and this blue in palette 1 should share the same slot index.” The actual slot number is determined later during palette construction.
This indirection makes the system flexible. It doesn’t need to know final slot positions in advance.
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 2: Generate Indirect Links
For each group of color-isomorphic tiles that span multiple palettes, the system identifies corresponding colors and creates indirect links between them. One member of the group is chosen as the reference. Its palette slot layout becomes the target that other members align to.
The system picks the reference member that minimizes conflicts with prefilled (locked) palette slots in other palettes, since those slots cannot be moved.
In our example, Palette 0 is chosen as the reference. Three indirect links are created:
brown in Palette 1 → brown in Palette 0 ("put brown at whatever slot Palette 0's brown gets")
blue in Palette 1 → red in Palette 0 ("put blue at whatever slot Palette 0's red gets")
green in Palette 1 → green in Palette 0 ("put green at whatever slot Palette 0's green gets")
Links reference colors, not slot numbers. The links remain valid even if other (non-shared) colors shift around during final palette construction.
Stage 3: Rebuild Palettes with Links
Palettes are rebuilt from scratch with the indirect links applied:
Colors that are not involved in any sharing link get assigned slots sequentially (same as the draft).
Colors with indirect links are resolved: follow the link to find where the reference color landed, and place the linked color at the same slot.
If the target slot is already occupied by a non-locked color, the occupant is evicted to the next available slot.
In our example, Palette 0 is the reference palette, so it rebuilds identically. Its colors weren’t linked from anywhere. Palette 1 is rebuilt as follows:
Sequential fill for non-linked colors: pink is not involved in any link, so it fills sequentially → slot 1.
Resolve brown: brown in Palette 1 is linked to brown in Palette 0, which is at slot 1. But slot 1 is occupied by pink! Pink is evicted to the next free slot (slot 4). Brown is placed at slot 1.
Resolve blue: blue is linked to red in Palette 0, which is at slot 2. Slot 2 is free → blue is placed at slot 2.
Resolve green: green is linked to green in Palette 0, which is at slot 3. Slot 3 is free → green is placed at slot 3.
The final palettes:
Palette 00.pal (final): Palette 01.pal (final):
Slot 0: 0 128 0 (transparent) Slot 0: 0 128 0 (transparent)
Slot 1: 139 90 43 (brown) Slot 1: 139 90 43 (brown) ← moved here via link
Slot 2: 220 40 40 (red) Slot 2: 40 40 220 (blue) ← aligned with red's slot
Slot 3: 50 180 50 (green) Slot 3: 50 180 50 (green) ← aligned with green's slot
Slot 4: 255 255 0 (yellow) Slot 4: 255 105 180 (pink) ← evicted here from slot 1
... ...
Now brown, the accent color (red/blue), and green occupy the same slots (1, 2, 3) in both palettes.
This stage can also produce shared color conflicts and prefilled source conflicts. When multiple shape groups share a common color (e.g., brown) in the same palette, each group creates a link for that color pointing to a different reference palette. The system applies links in order: the first group’s link wins, and later groups’ links for the same color are silently dropped. The losing group’s alignment fails because the shared color ends up at a slot chosen by the winning group, not the slot the losing group needed. The diagnostic reports these as “Shared color conflict” failures under each affected group (see Shared Color Conflicts below). Additionally, if a link’s source color is prefilled (locked) in its palette, the link is dropped entirely (see Prefilled Source Conflicts).
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:
You are compiling a secondary tileset with a paired primary.
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.
biasedThe 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.
optimalHard 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.
greedyBest-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.
optimalGlobally optimal alignment using constraint satisfaction. Not yet implemented.
Recommended Configuration
For maximum tile sharing, use both options together:
tileset:
tiles:
sharing:
packing: biased
alignment: greedy
Or via CLI flags:
--tile-sharing-packing biased --tile-sharing-alignment greedy
Using biased packing is the single biggest improvement. It actively distributes color-isomorphic tiles across palettes, giving the alignment system more opportunities to work with.
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-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.
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_tileandget_ideal_flipcanonicalization into its ownShapeTile,ShapeMask, andCanonicalShapeTiletypes.Indirect position linking. Borytiles’
Position_in_paletteenum 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’IndirectPositionsystem.Greedy alignment algorithm. Borytiles’
account_for_palette_swapsfunction 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.