An interactive map atlas for OMORI — explore Faraway Town and Headspace, find watermelons, NPC rewards, and never get lost again.
- Stitched outdoor world maps for FARAWAY TOWN and HEADSPACE
- Click-to-enter interiors (independent rooms shown side-by-side, not force-fit into the world map)
- Markers for collectibles (watermelons), NPC rewards, badge triggers
- Filterable by type / region / availability
WIP. All 11 regions have extracted data; stitched world maps exist for 4 of them.
| Region | Maps | Exits | 🍉 | Stitched |
|---|---|---|---|---|
faraway |
82 | 778 | 0 | day / sunset / night |
vast_forest |
29 | 99 | 28 | — |
orange_oasis |
29 | 90 | 52 | — |
otherworld |
41 | 128 | 46 | — |
junkyard |
15 | 50 | 24 | ✅ |
pyrefly_forest |
25 | 96 | 16 | ✅ |
sweethearts_castle |
55 | 139 | 75 | ✅ |
deep_well |
24 | 49 | 21 | — |
deeper_well |
30 | 126 | 51 | — |
humphrey |
35 | 120 | 31 | — |
last_resort |
15 | 69 | 13 | — |
forest_playground |
1 | 1 | 0 | — |
Every map listed above has an image in data/raw_pngs/ (360/360). Editor-only
folder nodes in the map tree (-- PINWHEEL FOREST, -- MINIGAMES, …) are
dropped during extraction — see 01_extract_adjacency.js.
Some maps are room atlases: several unconnected rooms sharing one canvas,
with impassable filler between them. Those are sliced into per-room sub-maps
(subMaps in 01, cut by 05) under whichever region's door leads into each
room. Map453 CLUB SANDWICH also holds one room that nothing in the game can
reach, kept and flagged [UNUSED].
Skills viewer covers vanilla + the Broken Dreams mod.
# 1. Extract map metadata + adjacency for a region (regenerate when game data changes)
node scripts/01_extract_adjacency.js <region> # e.g. junkyard
# 2. Download that region's map PNGs from goats.dev (skips existing + 404s)
./scripts/02_download_goats_pngs.sh <region>
# 3. If the region has sliced rooms: cut them, then anchor their exits
python3 scripts/05_cut_submaps.py <region>
python3 scripts/14_add_exit_anchors.py <region> # re-run after any new slice
# 4. Markers: extract them, cut their sprites, then measure where those sprites
# really are in the map art. 29 must follow 22/28 — it corrects what they
# predict against the shipped PNGs.
python3 scripts/22_extract_conveyors.py
node scripts/23_extract_collectibles.js
python3 scripts/28_extract_point_sprites.py
python3 scripts/29_measure_point_art.py
# 30 and 31 also write collectibles.json and 23 overwrites it, so the whole
# chain has to be re-run in order — stopping after 29 silently drops the five
# HANGMAN keys only 31 can place, and every "requires" note.
python3 scripts/30_extract_requirements.py
python3 scripts/31_blackletter_clues.py
# 5. The Chinese half of the interface. Reads the *installed* (Simplified
# Chinese) build and pairs its names to the English dump's by row id.
python3 scripts/37_extract_zh_names.py
# 6. Side quests: who asks, where it sends you, what it pays.
node scripts/39_extract_quests.js
# 7. Serve the repo root (the web pages fetch ../data/*)
python3 -m http.server 8000
# then open http://localhost:8000/web/Maps that goats.dev doesn't host can be rendered straight from a local Steam install instead:
python3 scripts/13_render_tiled_map.py <map_id> [<map_id> ...]13 composites the map's parallax under the tile layers, and 16 can draw the
event sprites a render is missing — SOLAR SYSTEM is both: its tile layers hold
only white specks, its starfield is the parallax and its planets are events, so
a tiles-only render came out 95% black and looked like BLACKSPACE.
python3 scripts/13_render_tiled_map.py 127
python3 scripts/16_render_event_sprites.py 127 --no-clip \
--events 2,3,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30Which events, exactly, matters. 31 EPILOGUE PLUTO and 32 THE EARTH EPILOGUE
are left out: Pluto's every drawn page is behind an epilogue switch, so the map
should show only the hole it left, and the epilogue Earth is a second copy of
the one already on the road. --no-clip is needed because the headroom
clipping the flag turns off is for two-tile warp pads — it took Jupiter down to
a single tile.
Both refresh data/raw_pngs/manifest.json, which is each PNG's mtime; the web
pages stamp it onto the image URLs so a re-rendered map is picked up without a
hard reload. Run python3 scripts/35_png_manifest.py by hand if you replace a
PNG some other way.
data/
<region>_maps.json map id → name, width/height (tiles), parallax, bgm
<region>_edges.json { internal: [ {from:{mapId,evId,evName,x,y,page,hitbox}, to:{mapId,x,y}, kind} ] }
<region>_highlights.json { watermelons: [ {mapId,evId,name,x,y,color,prize} ] }
skills_vanilla.json skill tables for the skills viewer
skills_brokendreams.json
raw_pngs/ per-map PNGs (gitignored, ~580 files)
stitched/ stitched world PNGs + layout JSON (gitignored)
scripts/ extraction + rendering pipeline (numbered by run order)
web/ static site — no build step
Coordinates are in tiles in the JSON, pixels (tile × 32) in the stitched
layouts. Leaflet uses CRS.Simple with the y-axis flipped (lat = heightPx - y).
| Script | What it does |
|---|---|
01_extract_adjacency.js <region> |
Walks the mapInfo parent/child tree from each region's roots and scans every event page for transfers — RPG Maker code 201, reserveTransfer(...), and OMORI's setValue(9=mapId, 10=x, 11=y) fade pattern. Writes <region>_maps.json + <region>_edges.json. Region roots / exclusions / extra rooms are configured in the REGIONS table at the top. |
02_download_goats_pngs.sh <region> |
Pulls pre-rendered map PNGs from goats.dev into data/raw_pngs/. |
03_extract_skills.js |
Vanilla skill tables → skills_vanilla.json. |
04_extract_bd_skills.js |
Broken Dreams skill tables (incl. Energy cost) → skills_brokendreams.json. |
05_cut_submaps.py |
Cuts oversized source PNGs into sub-maps. |
06_restitch.py |
Rebuilds a stitched PNG from a layout JSON. |
07_patch_renders.py |
Patches individual map renders into an existing stitch. |
08_render_aubrey.py |
Renders maps from decrypted .AUBREY data. |
12_slice_houses.py |
Slices Faraway house interiors → faraway_houses_sliced.json. |
38_add_arrival_anchors.py [<region>…] |
Puts a snap anchor on a tile the game sends you to but never from. A room entered only by a transfer somewhere else has no FROM side of its own, so the stitcher has nothing there to snap a route to — SWEETHEART DUNGEON 1's top-left cell holds twelve dressmole events and not one transfer, yet map171 ev76 drops you at (7, 8) inside it. Hand-written list; each entry is checked against the edges and reported rather than placed if nothing actually arrives there. Re-running is safe. |
34_add_side_anchors.py <region> <mapId>… |
Adds one anchor on the left and right edge of a map, for open maps that run into their neighbours along a whole side with no door event anywhere near it — FROZEN FOREST (II) and (VII). The anchor's hitbox is vertical, which pins X and lets Y slide, so one point stands for the entire side. |
14_add_exit_anchors.py <region> |
Most sliced rooms leave through a narrow path stub at the bottom that carries no event, so the stitcher had nothing to snap a route to there. Measures each cut PNG and appends a synthetic anchor edge on the stub. Run after 01 and 05. |
13_render_tiled_map.py <id>... |
Renders a map from a local Steam install: decrypts .AUBREY (AES-256-CTR) and .rpgmvp tilesets (header XOR), composites the visible Tiled layers. |
39_extract_quests.js |
Writes data/quests.json. Nothing in the map data says which quest an event belongs to, but every line of dialogue lives in a file named after its quest (sidequest_dreamworld_ghostgathering) and events cite it by key — so a quest's events are the events that quote its file, and the message number they quote is the order you do them in. message_0 is the giver in every quest checked by hand. 67 quests, 35 of them named by the game's own log (data/Quests.PLUTO). |
37_extract_zh_names.py |
Writes data/names_zh.json: every item, weapon and charm name in the installed Simplified Chinese build, keyed by the English dump's name for the same row id. The two builds number their rows identically, so the pairing is exact. index.html swaps names at render time — collectibles.json stays English. |
36_bake_deco_clusters.py |
Composites each contiguous run of decorations into one PNG under web/assets/decorations/baked/, plus data/stitched/deco_clusters.json. The trees bridge holes in the layout, so what is behind them is #map's background; Leaflet rounds each of the 179 overlays separately and lets it through the hairline cracks as a black outline round every tree. One image cannot crack against itself. Run after every layout import, next to 33_recolor_close_routes.py, with the same flag each time — index.html checks the manifest against the layout and falls back to per-sprite drawing when it is stale. --all-below puts every cluster under every map instead of honouring the layout's z-order, which makes each spatial cluster exactly one file and survives maps moving; this atlas uses it, because nearly every tree hangs over a hole and has no map to be in front of anyway. |
01 reads decrypted Map*.json from a path hardcoded at the top of the file;
13 reads the Steam copy of OMORI directly.
index.html— the viewer. Region picker, per-map mode (map image + clickable exit markers that jump to the destination) and stitched mode (world PNG + route overlay with dot/line size sliders). Watermelon markers with prize tooltips.stitcher_all.html— all-region stitcher: drag/snap maps, group by imported layout, z-order, undo/redo, marquee select, route drawing, export PNG + layout JSON.stitcher_<region>.html— the same tool scoped to one region.skills.html— skill table browser, switchable between vanilla and Broken Dreams.
- Static HTML site, no build step
- Leaflet for the map viewer (zoom + pan + markers)
- Map images courtesy of goats.dev
- Hosted on GitHub Pages
- Game event data: extracted from decrypted vanilla
Map*.jsonfiles - Map images: goats.dev pre-rendered PNGs, plus local renders from the game's own encrypted Tiled data
- OMORI is © OMOCAT, LLC. This is a non-commercial fan project with no affiliation to, or endorsement by, OMOCAT. All original art, sprites, fonts and audio belong to them.
- Map renders are from goats.dev, with a few rendered here from the game's own Tiled data where goats.dev has no image.
- Chinese text — region names, item names and the HANGMAN clues — is the
game's own Simplified Chinese translation, read out of an installed copy
rather than written by hand. See
scripts/37_extract_zh_names.py.
The code in this repository is released under the MIT License.
The assets are not. OMORI's map images and sprites belong to OMOCAT, LLC. They are not covered by the code licence and may not be redistributed.
Most of the extraction scripts and most of the code in web/index.html were
written by Claude, across a very long back-and-forth.
The world itself is mine: all 253 maps placed by hand in the stitcher, along with the 226 routes and the trees and pinwheels — and the game knowledge behind every correction that made the data right.