Optimize Maps in Kirin: one tileset per map, not one giant tileset for everything
An RPG Maker XP map may use only a fraction of a giant tileset. Optimize Maps finds what each map needs, creates a compact variant and prevents Android from paying for thousands of tiles that never appear on screen.

RPG Maker XP lets many maps share one tileset. For small projects, this is simple and effective. Trouble begins when that tileset grows for years into an enormous image containing thousands of tiles, while any one map uses only a small fraction.
This pattern is particularly common in large Pokémon Essentials projects. The developer keeps adding towns, trees, roads, buildings, props and decoration to one resource. To RPG Maker XP it remains a single file; to an Android phone it may become unnecessarily large, slow to load or impossible to represent in the traditional way.
The problem with giant tilesets
Imagine this file:
Graphics/Tilesets/Exterior.png
256 × 20000 px
At eight 32×32 tiles per row, a tileset 20,000 pixels tall can contain roughly five thousand tiles in its main block. A small town, however, may use only one or two hundred.
The traditional model still connects the map to the entire resource:
Kirin changes the perspective. Instead of asking how Android can load the giant image more efficiently, it asks which parts the map actually needs.
Optimize Maps analyzes real project data
Optimization starts by reading the RPG Maker XP structure. Kirin examines Data/Tilesets.rxdata to associate each tileset_id with its name, then walks the MapXXX.rxdata files.
For every map it determines:
- which tileset it uses;
- which tile IDs appear in its layers;
- which tiles are used as event graphics;
- the complete set needed to keep the map visually identical.
A tile absent from the terrain may still be used by an event. The optimizer therefore considers event tiles before deciding what can be removed.
The philosophy: one tileset per map
Conceptually, the original system looks like this:
GIANT TILESET
↑
┌────────────┼────────────┐
│ │ │
Map001 Map002 Map003
Optimize Maps aims for something closer to:
Map001 → compact tileset A
Map002 → compact tileset B
Map003 → compact tileset C
This does not necessarily mean writing one distinct file for every map. Each map gets a variant tailored to its real needs; maps with exactly the same tile set can share a variant.
From five thousand tiles to the ones that matter
Suppose the original contains:
5000 available tileswhile a map uses:
120 tiles
Kirin can rebuild a compact atlas containing those 120 tiles. RMXP uses eight tiles per row, so 120 tiles require fifteen rows:
120 / 8 = 15 rows
15 × 32 = 480 px
Original
- 256 × 20000 px
- ~5000 tiles
- A large surface to decode and manage
Compact variant
- 256 × 480 px
- 120 used tiles
- Far less work to load the map
As 32-bit RGBA, 256×20000 is about 19.5 MiB uncompressed; 256×480 is under half a MiB. The difference is substantial even before runtime-format compression.
The atlas is built tile by tile
Once Kirin knows the used set, it creates a new 256-pixel-wide surface and places each 32×32 tile inside. The tiles are compacted without changing what the map believes it is using.
ORIGINAL TILESET
#384 #385 #386 ... #1401 ... #2988 ...
MAP USES
#420 #853 #1401 #2988
COMPACT ATLAS
#0 #1 #2 #3
This requires a mapping between historical map indices and their new physical positions.
The original map is not rewritten
Optimize Maps does not need to convert every MapXXX.rxdata to a new numbering scheme. The map can keep requesting:
tile #1401
while Kirin knows where that tile lives in this map's compact atlas.
RPG Maker XP semantics stay intact while the graphics representation beneath them changes completely.
If two maps use the same set, do not duplicate it
The current implementation computes a SHA-256 signature from each map's used tile set. Two maps using the same original tileset and exactly the same tiles produce the same logical variant.
Map012 ─┐
├──→ variant 7f31...
Map013 ─┘
Map014 ───→ variant b824...
This prevents per-map specialization from producing hundreds of identical copies. Each map still receives an appropriate tileset, while storage reuses variants whenever safe.
index.kgi: connecting map, variant and tile
Kirin stores optimization metadata at:
Graphics/Tilesets/KirinOptimized/index.kgi
The index contains what the runtime needs to resolve the right resource:
- original tileset;
- logical dimensions;
- tile size;
- map-to-variant table;
- each variant's file;
- original-tile-to-compact-index table.
The runtime does not have to derive this every time it enters a map. Expensive work happens during optimization; play consumes a prepared structure.
Optimize Maps focuses on genuinely problematic cases
The current implementation does not compact every tileset. It targets 256-pixel-wide RMXP tilesets taller than 4096 pixels:
width = 256 px
height > 4096 px
→ Optimize Maps candidate
A small tileset does not need the extra infrastructure. The goal is to address resources that have become real memory, loading or graphics-compatibility problems.
Why it can eliminate black maps
The GL_MAX_TEXTURE_SIZE article explains that every GPU has a maximum dimension for one texture. A phone may accept 8192 or 16384 pixels where a desktop GPU allows considerably more.
tileset = 256 × 20000
mobile GPU: GL_MAX_TEXTURE_SIZE = 16384
The original cannot exist as a single conventional texture on that GPU. Depending on the graphics path, the map may fail to appear or show the familiar black-map symptom.
Optimize Maps does not raise the GPU limit. It removes the need to reach it.
256 × 20000 original
↓
Map023 needs only 120 tiles
↓
256 × 480 compact
↓
the giant texture no longer exists for this map
The best way to handle a giant texture may be not to load it.
Faster loading because there is less to load
Without optimization:
open giant tileset
→ read
→ decode
→ prepare
→ transfer / manage
→ render map
With a prepared variant:
identify Map023
→ query index.kgi
→ open compact atlas
→ load less data
→ render map
The exact gain depends on project and device, but the principle is direct: a smaller surface means less data to read, decode, retain and transfer.
Pay the cost during optimization, not every time the map opens
Optimize Maps moves work rather than making it vanish. During preparation, Kirin reads every map and Tilesets.rxdata, detects used tiles, includes event tiles, decodes source tilesets, builds new atlases, creates remapping tables and writes index.kgi.
That work is deliberately performed once to avoid a larger repeated cost during play—the same philosophy as Kirin's path cache:
Compact atlases are written as KAL4
The current implementation produces compact atlases through KAL4 v2. KAL4 stores ASTC 4×4 GPU blocks and uses LZ4 for their physical representation.
giant tileset
↓
analyze MapXXX.rxdata
↓
select used tiles
↓
build compact atlas
↓
KAL4 v2
↓
runtime / GPU
Optimize Maps and full graphics optimization therefore share a physical resource technology even though their goals differ.
Optimize Maps is not Optimize Graphics
Optimize Maps
- Analyzes MapXXX.rxdata
- Compacts giant tilesets by real use
- Generates variants
- Creates index.kgi
- Need not convert every game PNG
Optimize Graphics
- Covers the entire graphics set
- Converts eligible resources to the KAL4 pipeline
- Has a broader scope than maps
Optimize Maps is a specialized map-system optimization, not another name for converting every graphic.
The optimization modifies the selected game
The current system works in place; it does not create a complete second copy of the game inside another Kirin folder.
Graphics/
└── Tilesets/
└── KirinOptimized/
├── index.kgi
├── Exterior_a1b2....png
├── Exterior_c3d4....png
└── ...
The optimization manifest also records how many maps and tilesets were processed and whether the latest run finished successfully.
Compatibility before incomplete optimization
Kirin does not optimize at any cost. If a map cannot be read, it is skipped. If a tileset is missing or cannot be decoded, its group stays out of the optimized index and the runtime can use its normal path.
This prevents one of the worst possible outcomes:
The game must keep working. Optimization is used only when Kirin can build a consistent representation.
Even a map with no tiles gets a valid variant
If a map uses no tile from the main block, Kirin can create a minimal transparent 256×32 atlas. The runtime still receives a direct:
map_id → variantwithout adding a special exception for the empty case.
From one global resource to specialized resources
RPG Maker XP exposes a logical structure:
Map → tileset → tileKirin preserves it while changing the physical representation:
Map
↓
index.kgi
↓
compact variant
↓
remapped tile
↓
GPU
The game does not need to know that the giant original tileset is no longer the resource used by the GPU for that map.
Do not adapt the problem—remove it
A traditional port might ask:
How can we load this 20,000-pixel tileset on Android?
Optimize Maps asks a better question:
Why should this map load 20,000 pixels when it uses only 480?
That is the difference between reproducing a historical limitation and evolving the architecture.
One tileset per map
RPG MAKER XP
5000 available tiles
↓
giant tileset
↓
Map001 uses 120
but the resource contains 5000
KIRIN
5000 original tiles
↓
analyze Map001
↓
120 used tiles
↓
compact atlas
↓
120 tiles
A map needing 85 tiles receives another variant. Maps needing exactly the same set can share one. Three benefits follow from one decision:
Optimize Maps does not try to make Android cope better with everything RPG Maker XP accumulated. It makes each map pay only for what it actually uses.
Optimization shaped by real content
Kirin preserves the original map logic while offering the GPU a much smaller, device-appropriate representation.
See more technical articles