Abstract
Building 2D game levels from a tileset is slow and unintuitive. Existing tools demand that creators learn the vocabulary of tilemaps โ corners, caps, transitions, adjacency rules, Wang tiles, bitmasks โ before placing a single tile correctly. Mosaic removes that barrier. Instead of describing how tiles connect, the creator describes what each tile is ("this is ground surface," "this is a platform," "this is a background prop"). Mosaic infers the connection logic automatically and generates polished platformer and top-down levels, then exports them to mainstream engines including Godot, Unity, Phaser, and Tiled.
1Introduction
Mosaic is a browser-based tool for turning a tileset or spritesheet into finished 2D game levels. It pairs a visual labeling interface with a procedural generator and a multi-engine export layer. The entire workflow โ upload, label, generate, refine, export โ runs client-side in a single HTML application, with no installation and no server round-trips. Tilesets never leave the creator's machine.
A creator should be able to say what their art represents, not how it connects. The tool should be smart enough to do the rest.
2The Problem
A tileset is just an image cut into a grid of small squares (commonly 16ร16 pixels). Turning that grid into a coherent level is deceptively hard, because tiles are not interchangeable โ a "ground" tile that works in the middle of a hill looks broken on a left edge, a right edge, an inside corner, or a one-tile-wide pillar.
To place tiles correctly, traditional tools require the creator to encode the relationships between tiles:
- Edge and corner roles โ which tile is the top-left cap, the right edge, the inner corner, and so on.
- Adjacency / Wang rules โ which tiles are allowed to sit next to which.
- Bitmask autotiling โ a 4-bit or 8-bit code per neighborhood configuration, mapped to a specific tile.
- Transitions โ special tiles for where grass meets dirt, or stone meets water.
This is an upfront barrier. Learning how tiles connect is genuinely valuable craft โ but being required to master it before placing a single tile puts a wall between the creator and their intent. For hobbyists, students, jam participants, and artists, that wall is steep enough that many never finish a level. Even for professionals, re-encoding the same rules by hand is repetitive, error-prone busywork. The goal isn't to keep anyone from learning these concepts โ it's to remove them as a precondition for starting. Beyond rules, hand-authoring is slow: backgrounds must be tiled by hand, ground filled cell-by-cell to avoid gaps, props positioned so they don't float or sink, and platforms spaced so the level is playable. Mosaic targets all of these.
3Prior Art
| Tool | Approach | The catch |
|---|---|---|
| Tiled | Manual painting + optional Wang/terrain sets | Powerful, but the creator defines and maintains all terrain rules. |
| LDtk | Auto-layers driven by IntGrid rules | Excellent for rule-based detail, but still rule-authoring; learning curve. |
| Wave Function Collapse | Constraint-solving from an example | Emergent but hard to direct; can contradict/fail; non-deterministic. |
| Engine editors (Godot, Unity) | TileSet terrain + manual painting | Tied to one engine; still require terrain/bitmask setup. |
Each assumes the creator masters the underlying tile-relationship model before they can build. Mosaic's differentiator is that it doesn't require that upfront โ and because it generates correct, real examples, it doubles as a hands-on way to pick those concepts up by observation. It trades some unbounded flexibility for a dramatically gentler on-ramp and faster time-to-a-finished-level โ while still exporting into those same tools when deeper editing is desired.
4Design Philosophy
5System Architecture
Mosaic ships as a small set of cooperating parts, all client-side.
index.html (Mosaic landing site)
โ Launch App
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ tile_labeler.html โ
โ (the Mosaic application) โ
โ โโโโโโโโโ โโโโโโโโโ โโโโโโโโโโ โโโโโโโโโโโโ โ
โ โ Label โ โ Place โ โ Edit โ โ Generate โ โ
โ โโโโโฌโโโโ โโโโโฌโโโโ โโโโโฌโโโโโ โโโโโโฌโโโโโโ โ
โ โโโโโโโโโโโดโโโโโโโโโโดโโโโโโโโโโโโ โ
โ Shared state: labels ยท blocks ยท โ
โ placements ยท sheets โ
โ โ โ โ โ
โ Generation Save/Load Export โ
โ engine (JSON) (Phaser/TMX) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
generate_level.py (optional CLI generator)
godot_level_loader.gd (native Godot loader)The application (tile_labeler.html). A single self-contained HTML/CSS/JS file hosting four modes โ Label, Place, Edit Map, Generate โ over shared state. Rendering uses Canvas 2D with imageSmoothingEnabled = false for crisp pixel art. The base tile unit is 16ร16 px.
The generator. A native-JavaScript generator runs in-browser for instant results; a parallel Python generator (generate_level.py) serves batch/CLI workflows. Both read the same labeled JSON.
The landing site & export artifacts. index.html introduces the product; the export layer emits engine-native files, with godot_level_loader.gd as the zero-plugin Godot companion.
6The Labeling System
Labeling is the heart of Mosaic. The creator works on the tileset image directly:
- Click / drag labels individual tiles with the active category.
- Shift-click / drag selects a rectangular block, stored as one block definition rather than many tiles โ essential for skies, silhouettes, buildings, and large props.
- An Auto-Labeler performs a non-destructive heuristic first pass, proposing labels with confidence that the creator can accept or override.
Internally the system stores labels (a map of tile index โ category IDs) and blocks (rectangular definitions { category, startCol, startRow, w, h, sheet }).
6.1 The render stack
Mosaic composes a level back-to-front so layers read cleanly:
- Sky / Parallax Background
- Background Silhouette
- Ground Fill Backing
- Ground Surface
- Ground Body / Mid Fill
- Platforms
- Background Props / Buildings
- Small Decorative / Manually-Placed Props
A key invariant: the ground fill backing renders behind surface and body to eliminate transparency gaps, but never appears above a ground surface โ it starts at each column's own surface height and fills downward only.
7The Generation Engine
Mosaic supports three game-type modes โ Platformer, Top-Down, and High 3/4 (Zelda-style) โ each with its own generator reading the same labeled categories.
7.1 Platformer generation
- Terrain heightmap โ a seeded height per column defines the ground silhouette.
- Ground surface โ the top row uses automatic left/mid/right/solo edge selection inferred from labeled surface tiles.
- Ground body fill โ body/mid tiles fill to the bottom; no caps or corners inside; ground is fully solid.
- Ground fill backing โ a matched backing behind surface/body covers transparent pixels.
- Platforms โ top + base only, with enforced spacing and clearance, so the level stays playable.
- Props โ placed under strict support rules (ยง8).
7.2 Top-down generation
- Blob autotiling (
genTopdownBlob) โ organic landmasses where land meets background; edges/corners auto-selected from labeledfloor_edge_*/floor_corner_*/floor_inner_corner_*tiles. - Carved paths (
genTopdownCornerBlend) โ a corner-blend bitmask terrain where paths are carved through a background into connected walkways and clearings.
Both return a tile grid, a parallel category grid, and a prop overlay list.
7.3 Determinism
Every generator is seeded: same labels + same seed + same parameters always produce the same level, making results shareable and reproducible. Changing the seed re-rolls layout while keeping art and rules intact.
7.4 Natural-language steering
Generation can be nudged with a short design prompt. A lightweight keyword heuristic (no LLM required) maps descriptive words toward parameter adjustments โ an approachable way to bias output without exposing raw knobs.
8The Prop System
Props are the detail layer that makes a level feel alive โ and the easiest thing to get wrong. Props that float, sink, hang off ledges, or stack immediately break the illusion. Mosaic handles props two ways.
8.1 Automatic placement
- Surface validity โ a prop places only where its entire footprint rests on valid ground. In top-down maps, props go on the background terrain (e.g. grass) and never on the path. Large props require a flat run โฅ prop width.
- No overlap โ occupied cells are tracked; props never stack, and large props skip platform columns.
- Correct anchoring โ props sit on the ground, bottom-aligned to their support row.
- Transparency preserved โ props are collected as an overlay list and drawn atop fully-rendered terrain rather than stamped into the grid. Stamping exposed the canvas fill through transparent pixels (dark boxes); overlay rendering fixes it.
- Density control โ a prop-rate parameter governs how densely props populate eligible terrain.
8.2 Manual placement (Place mode)
- Label small props first.
- Switch to Place mode; the generated preview appears automatically.
- Pick a prop and click to stamp it exactly; erase and undo supported.
- Placements save into the same JSON.
- The generator stamps manual placements precisely on the next render.
Place mode renders pixel-perfectly and guards its handlers by mode, so labeling logic never fires during placement.
9The Dual-Sheet System
Real projects often keep terrain and props on separate spritesheets. Mosaic supports two: Sheet 0 (main tileset) and Sheet 1 (optional props sheet). Tile identity is unified by a simple encoding: indices โฅ 100000 (SHEET2_OFFSET) belong to the props sheet, with local index = index โ 100000. A decode helper resolves any index to { sourceImage, columns, localIndex }. This single convention flows through labeling, blocks, generation, rendering, placement, and export.
Label data is sheet-aware: saving records which sheets a file covers, and loading replaces only those sheets โ so a terrain-label file and a props-label file load together without overwriting each other. Game type is applied only from a file covering the main sheet, preventing a props file from silently switching the project's mode.
10The Export Pipeline
A finished Mosaic level is, at its core, a 2D grid of tile indices plus a parallel category grid and a prop overlay list. Because that grid is the universal currency of every tile engine, using a Mosaic level in an engine reduces to reformatting the grid and shipping the tileset image.
10.1 Shared Tiled model
Exports are built from one shared Tiled-format model: one tileset entry per spritesheet (with correct dimensions, columns, tile counts, and firstgid offsets), a ground tile layer mapping each cell to a GID (index + 1, 0 = empty), and a props tile layer where each placed prop block is expanded back into its constituent tiles.
10.2 Phaser
A Tiled-format JSON (level_phaser.json) consumed directly by Phaser's load.tilemapTiledJSON. Both sheets become tilesets; ground and props become layers.
10.3 Tiled .tmx
A standard Tiled XML map (level.tmx) with CSV layer data โ the universal bridge: Unity via SuperTiled2Unity, Godot via the Tiled importer, and the Tiled editor itself.
10.4 Native Godot
For a zero-plugin path, godot_level_loader.gd reads the level JSON, converts each index to atlas coordinates (index % columns, index / columns), routes any index โฅ 100000 to the props sheet, and rebuilds the level at runtime.
10.5 Raw data
A raw level-data JSON and rendered PNG are always available for custom engines.
11Data Formats
Mosaic uses three human-readable JSON documents (full schemas in Appendix B): a label file, the generated level data, and the engine export. The parallel categories grid is significant โ by recording which category placed each tile, Mosaic preserves semantic meaning that a flat index grid would lose, enabling reliable layer splitting and planned features like automatic collision flagging.
12Use Cases
- Game jams โ a complete, exportable level in minutes, so time goes to gameplay.
- Prototyping โ many seeded variations to evaluate pacing and composition.
- Education โ introduce level composition without requiring tilemap internals upfront, and let students explore those internals through the correct maps they generate.
- Artists & designers โ non-programmers turn their own tilesets into playable space.
- Content pipelines โ the Python generator + raw JSON for batch production.
13Roadmap
Near term
- Polish manual prop placement and small-prop aesthetics.
- Automatic collision export from the
categoriesgrid. - Density slider and richer in-panel generation controls.
Medium term
- LDtk export.
- Granular layer hierarchy in Edit Map with per-layer visibility and manual cell reassignment.
- Expanded Auto-Labeler accuracy.
- API connections โ integrate external services so Mosaic can pull and push assets without leaving the tool:
- AI tileset & sprite generation โ connect to pixel-art generation APIs (e.g. PixelLab-style services) to create or extend tilesets on demand, then label and generate immediately.
- Asset libraries โ fetch tilesets and props from online sources (itch.io, OpenGameArt, Kenney) straight into the labeler.
- Cloud sync โ optional save/load of label sets and generated levels to a hosted account.
Long term
- A hosted, web-based Mosaic.
- A public Mosaic API / SDK โ a documented REST endpoint and client library to generate levels programmatically from labeled JSON, enabling CI pipelines, server-side batch generation, and third-party integrations.
- More engine targets (GameMaker, Defold, Construct).
- Sharing of tilesets and label presets.
14Conclusion
Mosaic reframes level creation around intent instead of mechanics. By asking creators only to label what their tiles represent โ and inferring edges, fill, backing, spacing, and prop placement automatically โ it collapses the distance between a raw spritesheet and a polished, exportable level. The output uses only the creator's own art, stays under their control through editing and manual placement, and lands cleanly in the engines they already use.
Label what pieces are. Let Mosaic make the smart placement decisions.
AAppendix โ Category Reference
| Category | Purpose | Key rules |
|---|---|---|
| Sky / Parallax Background | Full-image backdrop behind everything | Composed as one block image; repeats horizontally (never stretched); never scattered. |
| BG Silhouette | Mid-background strip between sky and ground | Blocks join into one wide image; repeats; bottom-anchored to lowest ground; never overpowers foreground. |
| Ground Surface | Top walkable row | Automatic left/mid/right/solo edges; sits on ground body; creator never labels corners. |
| Ground Body / Mid Fill | Solid fill below the surface | Mid/fill tiles only; fills to the bottom; no caps/corners inside. |
| Ground Fill Backing | Backing to prevent transparency gaps | Matches body; behind surface/body; starts at each column's surface and fills downward only. |
| Platform Surface / Base | Floating platforms | Top + base only; minimum spacing; ground clearance; not bunched. |
| Large Background Prop | Big structures (ruins, buildings) | Flat run โฅ prop width; never hang off ledges or sink; never stack; avoid platform columns. |
| Small Decorative Prop | Detail sprites (rocks, signs, plants) | Auto-placed on valid background (off paths), or hand-placed; stamped exactly; transparent overlay. |
| Top-down floor / edges / corners | floor_mid, floor_edge_*, floor_corner_*, floor_inner_corner_* | Drive blob / corner-blend autotiling so landmasses and paths tile seamlessly. |
BAppendix โ JSON Schemas
B.1 Label file
{
"game_type": "platformer | topdown | hightopdown",
"cols2": 32, // columns in the props sheet (if used)
"tiles": { // category -> [tile indices]
"sky_bg": [32],
"floor_mid": [8],
"floor_edge_top": [2]
},
"blocks": { // category -> [rectangular blocks]
"bg_prop": [
{ "start": 128, "w": 3, "h": 4, "sheet": 1 }
]
},
"placements": [ // manual prop placements (Place mode)
{ "idx": 100128, "x": 24, "y": 17 }
]
}
B.2 Generated level data
{
"seed": 12345,
"width": 64, "height": 24, "scale": 3,
"game_type": "topdown",
"shape": "blob | paths",
"ground_y": [ /* per-column ground heights (platformer) */ ],
"tiles": [ [ /* tile index per cell, -1 = empty */ ] ],
"categories": [ [ /* category string per cell, "" = empty */ ] ],
"props": [
{ "startIdx": 100128, "bw": 3, "bh": 4, "x0": 12, "y0": 9 }
]
}
B.3 Tile-index conventions
-1(or any negative) โ empty cell.0 .. 99999โ a tile on the main sheet (sheet 0).>= 100000(SHEET2_OFFSET) โ a tile on the props sheet (sheet 1); local index =index โ 100000.- Tiled export GID =
localIndex + firstgid, with0reserved for empty.