Dream World IX 1.0.0b17 — The moogle letter network, experimental co-op, and living overworld terrain
Dream World IX is a toolkit for building brand-new playable Final Fantasy IX content — and faithfully forking the real game — for the Memoria engine (Steam/GOG FF9). This release gives a custom save moogle a real place in FF9's Mognet letter network (and gives every custom save point the game's full moogle menu), ships the kit's first experimental co-op multiplayer suite — proven on a real two-machine link — grows custom overworld islands real interior terrain and a biome palette, adds a story-event director plus the first fully authored multi-field story campaign, and lands two critical data-safety fixes with a kit-wide hardening pass behind them — 547 commits and 3993 passing tests since b15, plus the first refresh of the public engine bundle since b12 (it now carries the full patch stack, experimental co-op included). Every stable pillar still runs DLL-free or on the older bundle.
Hand your save moogle a mailbag and let it trade letters with the other 41, wipe in a dungeon and wake up at the last camp you passed, carve a real mountain — waterfalls and all — onto an island you minted, and (experimentally) explore, solve puzzles, and fight side by side with a friend over the internet.
Breaking for existing mods: the story-flag safe band moved (8512 → 8712) and cutscene casts now use a single actors = [...] key — details in the sections below.
What's new since v1.0.0b15
✉️ Save points & Mognet — the moogle letter network comes alive
A custom save moogle can now join FF9's real Mognet network as a genuine 42nd identity — giving, accepting, and re-reading letters exactly like the other 41 — while every [[savepoint]] runs the game's own full six-row menu with faithful dialogue instead of a bare save prompt.
- The full six-row moogle menu —
[[savepoint]]now offers FF9's real menu, in FF9's own order: Save / Tent / Mognet / Mogshop / Switch party members / Cancel.tent = trueheals half of max HP and MP (rounded up) per living member for one Tent;shop = <id>opens an in-field Mogshop;party = trueadds Switch party members, andparty_minnow defaults to a runtime clamp on the live party size instead of a hardcoded4— the old fixed value could softlock the switch screen with fewer than four characters available. - The moogle's act, on by default — every save moogle now performs the real save choreography (
act = true): it hops, its book and feather snap into place and open, it writes while the save runs, then everything reverses — one invariant template synthesized from a census of FF9's 55 real save-point fields (57 moogle instances).act_textsets the line,act_hop_toan optional landing spot,act = falsekeeps the old still moogle. [savepoint.mognet]— a new 42nd network identity — give the save moogle a real Mognet rosternameand it speaks the actual give/accept protocol against FF9's own mailbox bytes:accept = [...]for letters delivered to it (shown on the real full-screen letter, moogle portrait and all, with the faithful bottom-left mail-status box announcing who has a letter for whom),give = { variant, to }for its own outbound letter (delivered by any stock moogle — no engine change needed), andreceived = [...]for story-triggered arrivals gated byrequires_flag/requires_scenario. Needs a mintedtext_block(register_text_block = true) and your own FF9 install at build time to source the roster.- Read-mail — the moogle re-displays its own given or received letters on demand, matching stock's third Mognet mechanic, through a faithful 3-row Give / Read / Cancel submenu and the real fixed 16-row which-letter list. Built against a 58-field census of stock's own read-mail screens, verified offline against live save data, and in-game proven (2026-07-19).
- Faithful dialogue everywhere — every attributed line the kit authors (
[[npc]],[[event]],[[choice]], cutscenesaysteps, and the savepoint's own prompts) now renders in FF9's real attribution shape: the speaker's name on its own line, the line itself in literal curly quotes, never"Name:"— derived from a census of 12,711 stock dialogue entries. - Save-moogle fixes — forking a save point on 7 of FF9's 55 real save points (Gizamaluke's Grotto, Mountain Path, Red Rose, and others) used to silently produce no moogle at all, because they use a second model variant (
GEO_NPC_F1_MOG) the kit didn't recognize; a fork whose puppeteer logic can't be carried now warns instead of shipping silent; the save moogle speaks under its own roster name instead of a hardcoded one; and moogle menu windows are pinned top-left exactly like stock, fixing a clipped MOGNET caption. - Hardening — a 21-agent adversarial review confirmed 17 findings: the which-letter window now matches stock's fixed 16-row shape exactly, and the Mognet/flag validators reject out-of-range scenario ids, writes into reserved flag bands, oversized letter payloads, and blank letter fields at build time instead of failing later. A new
tools/mognet_dump.pydecodes a save's live Mognet mailbox — slots, delivered counter, per-letter give/read locks — for offline verification, with--jsonsnapshotting and--compare BEFORE AFTER. - In-game proven (2026-07-18) — the six-row save menu, the moogle's act, the Mognet identity (give and accept, on the user's own save), the kit-wide speaker form, and the party-switch/roster-name/menu-placement fixes; read-mail proven the next day (2026-07-19); the mailbox decode confirmed separately against live save data. See SAVEPOINT.md.
⚠️ Two critical data-safety fixes
A byte-level census of stock gEventGlobal usage — and a matching one of the engine's text-merge behavior — found two ways ordinary play could quietly corrupt a mod's story state or the base game's own dialogue. Both are fixed; one changes the default safe-flag band and needs a rebuild.
- The story-flag safe band moved 8512 → 8712 (BREAKING). The read-mail decode census found the old
FIRST_SAFE_FLAG = 8512sat onByte[1064]— the first byte of stock Mognet's own read-mail payload (variantsByte[1064-1073], sendersByte[1079-1088]), written on every ordinary Mognet visit. Every campaign/journey flag at the default base was living where real play scribbled over it. The safe band now starts at byte 1089 (bit 8712); a[deathrules]outpost word that had landed inside the Mognet read-lock band moved off it too. In-progress campaign saves built against the old band lose custom-flag story state when the mod is rebuilt — it was corruptible where it sat, so this is a correctness fix, not a regression. A flag you placed by hand below 8712 is now a hard build/lint error, and you bump it yourself — the kit's own Stolen Ember example moved its flag base 8600 → 8800 the same way. - The reservation formerly labeled "treasure-chest bitfield" (8376–8511) is the Mognet give/read lock band, byte-identical across ~58 fields. Regions, messages,
SaveReport(chests_opened→mognet_locks), and the docs are all re-attributed to match. text_blockno longer defaults onto a real field's dialogue. The engine merges every field's text into ONE global table with the base game applied first — so a custom field left on a real block (the kit's own default was1073, Black Mage Village; the World Hub's was8, Ice Cavern) silently overwrote that location's shipping dialogue, no stacked mod folder required.text_blocknow defaults to the field's own id and auto-emits itsMessageFileregistration; a fork still keeps its donor's block, since voice acting and the dual-language remap key off it.ff9mapkit lintnow catches a vanilla-block squat offline (lint_text_block, wired intolint_all) — no game install needed, and it flags a fork parked on any real block other than its own donor's.- A journey that would place a text window past the engine's Int16 mesID ceiling now fails at build time with a clear error instead of silently rendering no dialogue for every campaign past the wrap point.
- All twelve shipped kit examples that had been hand-pinned onto real blocks (the waystation, both hub fields, both journey examples, and seven others) were re-pointed to derive their own id. (The Vivi-hut build oracle deliberately keeps its pinned block — it exists to be byte-exact.)
🎮 Co-op & multiplayer suite
⚠️ Experimental — exploration ghost sync, battle co-op, and the declarative puzzle-gate vocabulary all work, and several pillars are proven on a real two-machine link, but the Workspace Co-op tab is only smoke-tested end to end, the network protocol is still changing release to release, and none of it runs without the engine patch set called out below.
Two players can now explore, solve puzzles, and fight side by side on the same field over direct LAN or the internet — each save stays fully independent underneath, but the guest can see, fight alongside, and even watch the host's battles live.
- Ghost sync, on any field you both stand on — the other player appears as a live-synced "ghost": their own current character model, tinted to match the local field's lighting, walking wherever you both happen to be (co-op is no longer locked to one hangout room;
--field Nrestricts it back to a single field if you want that). - One-command session setup —
ff9mapkit coop host|joinfinds or builds the shared co-op room, writes[Netsync]intoMemoria.ini, and mints a session code, over direct LAN (--lan) or an internet relay through a bundled TLS bridge (ff9mapkit coop bridge) gated by that code so only your intended peer connects. An in-game overlay shows the code and pairing state, and[Netsync]edits from the CLI, the Workspace Co-op tab, or a hand edit hot-reload into a running game within a couple of seconds. The relay bridge and invite-code path were hardened against a batch of real network bugs (an unbounded thread spawn that could kill the bridge under a connection flood, a ~41-hour slow-loris handshake window, sessions hanging forever after a silent peer, request-line header injection, and a crafted invite code that could splice extra lines into your ownMemoria.ini), and the direct-LAN setup guidance now scopes its Windows firewall rule to the Private network profile only. - Battle co-op and Visitor Mode — a connected peer can spectate a fight or take full networked control of party slot 2 (Attack/Defend, abilities, items, targeting, reviving a downed ally with Phoenix Down), and
--ghost-as(Visitor Mode) dresses their ghost as an actual playable party member instead of a generic silhouette, with a follow-host warp and an encounter pause while paired. - The Battle Diorama — when the host enters a fight, a following guest's own screen boots and renders that same battle live, seated with the host's party: HP, deaths, revives, damage numbers, flinches, and Trance gauges all mirror in real time. On by default (
[Netsync] Diorama); toggle withcoop host|join --diorama on|offor the Co-op tab's Play-style panel. - Declarative
[[coop]]puzzle gates — build symmetric two-player rooms with zero engine code:plate_a/plate_bpressure-plate triggers,zone=gather areas,requires_flagsequencing, andmode = "hold"held-plate doors that stay open only while both players stand on their plate. Pair with[[prop]] collision = falsefor a walk-through plate marker. Ships with Twin Altar, a fully playable co-op puzzle room built on an Esto Gaza fork. - A following guest now plays an honest copy of the host's game — the host's story flags, full party, key items, and item bag mirror onto a following guest's own fields, closing a live exploit where a guest could repeatedly loot a chest the host hadn't opened yet and bank real items into their own save (a following guest's menu access, achievement unlocks, and encounters are suppressed too). Guests never autosave while following a host, and leaving a session reloads the guest's own last autosave instead of stranding their story at the host's location.
- In-game proven, in stages — ghost sync over LAN (2026-07-08) and over the internet relay (2026-07-11); battle co-op and Visitor Mode on the single-machine test tier (2026-07-12); a real two-machine session closing every box — follow-warp, the
[[coop]]held-door gates, battle co-op, the party/inventory mirror, and the full save-safety suite (2026-07-15); and the Battle Diorama, two-machine (2026-07-16). - Needs the b17 engine bundle, on BOTH machines — everything above depends on the netsync engine patches (s36–s38 and s40–s41), which ship in this release's refreshed engine bundle for the first time; see Engine bundle below. Both machines must run the same engine build — mixed netsync versions deliberately refuse to pair. Co-op is OFF by default: nothing activates unless
[Netsync]is configured.
🏔️ Overworld terrain synthesis: forests, hills, mountains & new ground biomes
⚠️ Experimental — the terrain-carving verbs and the desert/snow ground families are in-game proven on deployed test islands, but this is frontier tooling: canyon islands were ruled off-language by their own new guard, beach-minting on custom islands was tried and retired, mixed-biome interiors are still open research, and the vocabulary is still moving release to release.
Custom overworld islands went from bare grass landmasses to real interior terrain and a real biome palette this release: three new verbs carve donor terrain onto a deployed island, and island/mountain mints can wear one of seven measured ground families instead of just grass.
- Three new terrain-carving verbs —
world-forest --mod-folder M --near WX,WZ(or--center) carries a real canopy blob onto a deployed island;world-hill --near WX,WZ [--height 4.2] [--radius 18]raises a raised-cosine grass hill by pure-Y displacement;world-mountain --near WX,WZ --donor BX,BY [--ground FAMILY]carries a whole real rock massif — rock stays rigid, the grass apron rises to meet it.--donoralso takes a multi-block rect for a massif that straddles a border, and where the donor has one, the massif's whole water system (waterfalls, rivers, flooded bowls) rides along with it, not just bare rock. - Six new ground families join grass —
world-island --ground {grass|desert|snow|canyon|scrub|brush|dunes}andworld-mountain --groundmint a bench in that family (each family's ground tiles and cliff-wall band sit at a fixed, measured shift from grass's own atlas position);world-transplant --groundre-textures an already-carried real island into a different family — ground and walls across the palette, shoreline sand currently grass/desert only — even across multiple map blocks. (--flatonworld-islandhas always meant "skip the verbatim meadow stamps," not "flatten a rolling interior"; a dead ambient-relief option that never actually applied was retired outright.) world-mirror --mod-folder Mcopies a mod folder's custom overworld land onto FF9's separate disc-4 asset tree — the game ships only two overworld trees, and any custom island left un-mirrored simply vanishes once the story (or an F6 disc switch) crosses into disc 4. Run it after every custom-ocean deploy, then relaunch. A same-window fix closed a gap where its cross-disc safety check silently compared the wrong level-of-detail and so never actually validated a non-default one.- New guardrails, minted by a real rejection — a map-wide survey of FF9's rock walls (655 canyon-wall triangles across 48 faces) found the red-rock face is never used as an open-sea cliff, so
world-island/world-transplant --groundnow refuse to build a coastal island from a ground family whose wall isn't measured-coastal. The law came from playtest: a deployed canyon island read wrong ("no real canyon cliffs this high") and both canyon test deploys were removed. A companion--allow-mod-overwritegate stopsworld-transplantfrom silently overwriting a different mod's land already sitting in the same map cell, and the custom engine now self-heals the "no controlled character" black screen that bad overworld geometry under a saved spawn point used to bake into a save. - The research frontier, honestly — minting a beach directly on a custom island's coastline (
world-island --beach) went through four playtest rounds and was closed as a falsified approach (the synthetic tapers kept reading as seams); the shore path that works is carrying one of FF9's real beach islands verbatim and re-texturing it (world-transplant --ground desert— in-game proven "looks verbatim," single- and multi-block). A first mixed-biome interior carry (a real scrub strip inside a fresh desert islet) survived several fix rounds but ended on a finding that resets the approach: FF9 never places interior families as clean patches in open ground — the faithful unit is the whole interlocked ensemble, which is where this arc goes next. - In-game proven through July 2026 — island E, the archipelago's first fully from-scratch grass landmass, renders with its whole cliff loop walkable (2026-07-12); the disc-4 mirror confirmed live — the archipelago exists on disc 4 and shows on the big map (2026-07-13); the Uaho mountain carry ("the cliff is great … the object patch looks amazing," 2026-07-13); the two-block crag carry and the Daguerreo horseshoe's full water system — falls animating, the river bowl reachable (2026-07-15); and desert and snow benches reading correctly on deployed islets the same day. See OVERWORLD_ENGINE.md for the full recipe.
🗺️ Overworld world-map: continents, entrances & navigation
⚠️ Experimental — the continent entrance loop (nameplate, confirm prompt, deterministic arrival, real zone-in fade) is proven end-to-end on the shipped waystation showcase; the minimap draws land but has no custom markers/labels yet, and a silent cell-tag bug just found and fixed in the entrance plumbing means this surface should still be treated as hardening, not finished.
b15 shipped the first hand-built continent; this release wires it into the real overworld — entrances that look and behave like native FF9 doors, exits that return the player exactly where they left, and custom land that finally shows up on the map players already use.
- The continent entrance/exit pair —
world-entrance --field-direct <id>wires a custom field into all 9 free-roam overworld dispatchers, and[[gateway]] to = "worldmap"gives a field a faithful walk-out carrying FF9's own shared exit cascade verbatim (prefixed by the field'sregion_keywrite, default 62). Shipped as the worked exampleexamples/continent-v1/waystation.field.toml(field 6500).- In-game proven (2026-07-12) — the waystation's full enter-and-exit loop closes on island C.
- Deterministic arrival — new
[[gateway]] arrive=[x,z](+arrive_face) presets the world player's position on exit instead of relying on a racy position mirror, so returning from a field lands at a fixed spot 8 units clear of its own trigger and facing away — stepping back doesn't instantly re-trigger the door. The entrance itself moved off a too-small showcase cay onto the real beach on island C. - The real zone-in —
world-entrance --field-directwarps used to drop the player in with a stuck camera and no transition; the trigger now runs FF9's own choreography (disable move/menu, a 24-frame fade-to-black, the worldmap-arrival sentinel) beforeField(), and--trigger-onlyrefreshes just an entrance's dispatcher triggers without re-stamping terrain or the building hull. Leaving a field back to the overworld now fades to black too, instead of hard-cutting.- In-game proven (2026-07-13) — on the waystation.
- A genuine native nameplate —
--nameplate-name "Name"repoints a dead AREA-switch case so a custom entrance gets FF9's real native entry flow: the approach nameplate with "Enter with [X]," shown as "?" until first visited and the name thereafter, persisting through saves.- In-game proven (2026-07-14) — "success! ? at first, then Waystation after entering. persists through saves."
- A separate
--action-promptflag raises the "!" confirm-to-enter bubble on a plain entrance (stock town/dungeon entry turns out to be confirm-gated, not walk-on); the nameplate surgery above supersedes the older self-timed nameplate path that flag was first built toward.
- Beach-minting closes out —
world-transplant --virgin-mintsynthesizes an entirely new beach's sand/foam/water bands directly on a bare grass coastline with no donor beach to copy, and a newbank_lowertweak sinks a mesa/cliff bank to a beach-capable profile at full world scale before minting a shore on it (the pair that built island B of the first custom continent). Both are now declarable straight in aworld-fuselayout ([placement.bank_lower]/[placement.virgin_mint]) or viaworld-transplant --bank-lower/--virgin-mint— no hand-written script required.- In-game proven (2026-07-11) — the cliff wall's lip-anchor fix ("looks good now, no more gashes"); the declarative TOML wiring itself was checked byte-identical against the already-deployed island B rather than re-playtested.
world-minimap— draws a mod folder's deployed overworld land onto FF9's own in-game world-map image with no engine DLL changes, using the game's real projection so custom land lines up with the true coastline; composes with Moguri's HD map when layered above it inFolderNames. Custom markers/labels at non-real coordinates remain out of reach — a known engine-side gap.- A silent entrance bug, fixed —
pack_cell_tagwas packing an entrance trigger's world-cell Z with a 7-bit mask when the engine's own dispatcher masks with only 6 bits; a Z coordinate of 64 or higher quietly set a bit no trigger ever reads, making that entrance permanently unreachable with no error at all. It now validates the range and raises instead of silently aliasing.
📜 Field scripting, forking & campaign authoring
Field scripts and forks both got more expressive this release: a single field can now stage several story-gated cutscenes across its own timeline, a forked field carries more of its donor's real behavior faithfully — story-gated doors, engine-level actor gates, NPC visibility — and the first fully authored multi-field story campaign shipped as a worked example.
- The story-event director —
[[cutscene]]is now a plural, array-style block like every other field-content block, so one field can stage several story-gated scenes across its timeline.requires_scenario/requires_flag/requires_flag_cleargate a scene to a beat,set_scenario/set_flagsadvance the story when it plays, composing with rotating-cast[[npc]]and story-gated gateways into a full director loop — a field re-stages itself as the story advances. Breaking change: every cast scene now runs through the unifiedactors = [...]key (actor = "vivi"→actors = ["vivi"]), replacing the old three-way overloadedactor. Also fixed here: a tick-order bug that left every scripted cutscene idling for about 8 seconds before the first move. - Forks now carry player-call story-gated doors — the doors that puppet the player through a walk-through animation as part of opening — instead of dropping them to an ungated seam; a tick-order fix arms the carried door after the player object, matching the donor's real init order, so sprinting through a carried door plays the real slow walk-out instead of clipping past the walkmesh boundary at run speed.
fork-reportgets sharper about what a fork will cost you — a new Party need line flags which party members a field's scenes are gated on, so you know what[party]to seed for a beat to actually fire; a new Story writes line lists which story flags a field sets on exit, so a downstream fork can seed the right[startup]state; and Lost on mint now also covers 14 real fields' hardcoded per-actor camera/geometry-attach/shadow engine tweaks that no.ebscript can reproduce.importseparately warns when a name pick resolves to a background-art folder shared by another field (about 142 of FF9's 818 fields share art with a story-beat sibling) instead of silently forking the wrong beat's script — and the engine itself now logs a clear warning naming both fork ids when two stacked mod folders fork the same donor field ambiguously.- The Stolen Ember: the first fully authored multi-field story campaign —
examples/stolen-embercomposes three forked fields (Lindblum Inn, Evil Forest, Lindblum Church) into one 4-beat narrative arc using the story-flag, cutscene-dispatch, rotating-cast NPC, gated-gateway, and save-point vocabulary together. Building it hardened the cutscene engine itself: a control watchdog now stops a late engine control-grant from handing the player control mid-scene (a player walking into an actor's path could previously stall the scene forever), and a follow-walk cutscene step now ends the instant it contacts its target instead of being blockable by standing in its way. - Fixed invisible custom NPCs (the "invisible innkeeper" bug chain) — three stacked bugs could make an
[[npc]]render invisible while staying fully interactive: a duplicate-row stand-animation id resolving to the wrong clip, an explicitanims=override silently dropped when combined witharchetype=, and — the root cause — a story-flag gate on an NPC's or chest's appearance compiling as an early-return insideInitinstead of a guard around theInitObjectcall site, which the engine renders as permanently hidden but still fully clickable. - 18 named story flags join the registry, curated from a flag-lore research pass over field scripts (
gen_flag_lore.py) — documented per-bit meanings (e.g.mognet_central_quest,evil_forest_swamp_door) instead of bare bit numbers to gate content on. - Field entry now dispatches per door — new
[player] faceand[[player.arrival]]TOML blocks let a synthesized field spawn the player with different position/facing per entrance, mirroring FF9's own destination-side arrival tables; forking a field now carries its donor's real per-door arrival table automatically; andentry_settle = "auto"computes the camera's black-hold-on-entry duration from the engine's own warp-in physics instead of a hand-tuned frame count. - In-game proven — the cutscene director's per-beat re-staging, the door-carry fix, and the invisible-NPC fix (all 2026-07-12, the last closing all three tested door carries — fields 254, 553, and 1904's full reach-gesture-and-swing choreography); the Stolen Ember end-to-end including save persistence and sabotage-proof cutscenes (2026-07-12); the arrival dispatch and
entry_settle = "auto"(2026-07-13); and a regression in the engine's fork-compatibility gates, restored and confirmed via a verbatim fork of Alexandria's Alley (field 105) that softlocked before the fix and played correctly after (2026-07-18).
⚔️ Battle balance: death rules & low-HP tuning
The Overload battle-balance hub grew a full roguelike death-rules suite this release — a party wipe can warp back to camp instead of ending the game, second wind gained a lighter revive option, and the Low HP threshold is now an author's choice.
[deathrules] on_defeatturns a party wipe into a warp instead of the Game Over screen —on_defeat = { warp_to = <field id>, hp = 0.2, gil_loss = 0.1, flag = ... }quietly revives the party athp× max HP, optionally docksgil_loss× party gil, and sends the field's own after-battle handler towarp_to: the "wake up back at camp" flow. Composes withsecond_wind— the wind fires first, and a spent wind or a failedchanceroll falls through to the warp.- The outpost system — mark any field
[field] outpost = trueand every entry records it as the last safe point visited (a save-backedgEventGlobalvar, last-write-wins); a wipe then warps to whichever outpost the player most recently entered, withwarp_todemoted to the fallback for a wipe before any outpost is ever reached. - Verbatim forks get full coverage too — both
on_defeatand outpost registration ride a fork's own after-battle handler and[startup]injection, so a forked field's real random battles wipe-warp exactly like a novel field's. - The defeat sequence went quiet — no stand-up animation, no run-away slide; the battle closes on an instant fade, with a double-dock guard so a re-kill during the exit fade can't re-charge
gil_loss, re-set the wipe marker, or re-roll a fresh second wind. second_windgainedanimation = "short"— a lighter alternative to the full Rebirth Flame summon: the fallen party simply stands back up, no choreography, reviving at a configurablerevive_hpfraction of max HP (default0.2).animation = "full"(unchanged) stays the default Phoenix-summon revive.[lowhp]lets an author move the HP fraction that counts as "Low HP" (vanilla is 1/6 of max) viathreshold = "N/D"or a decimal fraction — it drives the yellow HP number and the engine'sLowHPstatus that abilities and AI key off, with an optionalflaggate that toggles live.- In-game proven (2026-07-11) — both
on_defeatbranches (a wipe warping to a self-registered outpost, and thewarp_tofallback before any outpost), the instant no-slide exit, the short-animation second wind standing the party up at the set fraction, and[lowhp]at threshold 1/2 turning the HP number yellow on the hit that drops a character below half max.
🖥️ Workspace GUI overhaul
Eight playtested polish rounds (plus an adversarial-review pass) rebuilt the Workspace app's visual foundation, onboarding, accessibility, and theming, and shipped it a brand-new Co-op tab — the same DLL-free desktop app, made legible top to bottom.
- One shared card component, everywhere — all 27 boxed panels across Co-op/Import/Build/Battle/Models/Save (and the shell itself) moved off Qt's stock
QGroupBoxontowidgets.section(), a real card with a proper title and screen-reader-friendly labels; buttons, cards, text inputs, and log panels gained subtle lit-top/cut-in shading so they read as controls instead of flat rectangles; and a new automated contrast-auditing tool found and fixed sub-AA text, invisible hover states, and an entirely unfilled primary button across all 8 color themes. - A real newcomer path — Home now leads with a live "Get started" checklist (point the kit at your FF9 install → extract the base templates → fork your first field from your own game) since the kit ships zero FF9 content, and a mode-aware "next action" strip below the breadcrumb tells you what to do next at every stage, right down to "press F9 to Deploy" and "press F6 → Reload field in-game."
- In-place jargon help — about 26 plain-language concept cards are now answerable as fuzzy-searchable "What is X?" rows in Ctrl-K, an "About this…" link in the Inspector, and a "?" badge on jargon form fields, so terms like
gEventGlobalor walkmesh don't send you hunting through docs. - Reorganized navigation and a Guided/Full mode — the app's 10 tabs now group into a segmented Home/Author/Assets/State/Ship rail so the tab strip never overflows into a chevron, Ctrl-K rows show a category prefix and keybinding hint, and a global Guided/Full toggle (Guided by default) tucks expert-only fields — like Battle's donor-site and AI-fact panels — behind an Advanced drawer until you ask for them.
- Accessibility hardening — a new "Text size" preference (100/110/125/150%) seeds itself from Windows' own accessibility text-size slider on first launch (an explicit in-app choice always wins over it); every actionable control now has a screen-reader name; checked checkboxes draw a tick and checked radios a dot instead of an identical solid swatch; status rows are distinguished by icon shape, not color alone; checkboxes/radios/badges hit a 24px minimum touch target; and all animation is gated behind a reduced-motion setting.
- New theming and a self-drawn icon set — the FF9-flavored "Mist" theme is now the default (Dark/Light/Nord/Dracula/Solarized/Gruvbox remain a Preferences — or Ctrl-K — switch away), and emoji throughout the toolbar, breadcrumb, tree, and sidebar were replaced with a self-authored 26-icon SVG set that tints correctly in every theme.
- A real Co-op tab, and sharper Deploy options — Co-op shipped in full (status warnings that link to Setup, an unclipped ghost-appearance picker for which character the peer's ghost dresses as, an aligned key/value status panel); Build & Deploy gained a "Deploy at its own id — reversible" option filling the gap between the test-slot deploy and the non-reversible "Install to game"; a campaign deploy now lands in the mod folder the campaign actually declares (e.g.
FF9CustomMap-ow) instead of silently falling back to the default; and the shared "Pick FF9 regions" dialog gained Select all/none. - Playtested (2026-07-15) — the section-card redesign, after user feedback that "the cards were nice logical section indicators, they just looked ugly": the card concept was kept and its rough edges fixed.
- Playtested (2026-07-16) — the reading-measure/type-scale pass ("looks good", "playtested, all good"); a follow-up adversarial review then caught and fixed real interaction bugs (dead-looking clicks on the search pill, nav rail, and disclosure controls; missing focus rings; several sub-AA color combinations); and a narrow-window bug that permanently shrank the saved left/right panels on every future launch was found and fixed, verified across all five trigger cases.
🎥 Blender add-on: camera & scene-import fidelity
A census projected all 741 real FF9 field-cameras through both the kit's in-game-proven to_canvas oracle and a bpy-free replica of exactly what the add-on poses in Blender, catching two systematic import bugs that no single test field would have surfaced.
- The vertical-focal fix — the census found 738 of 741 cameras misframing by ~7–20px (40 by 50px+) because FF9's vertical focal is 14/15 of its horizontal and a Blender camera lens is isotropic.
_apply_canvas_resolutionnow setsrender.pixel_aspect_y = 15/14(also routed through View Camera), and the re-run census lands 740/741 cameras sub-2px. - The no-art reframe is gone — the importer used to silently slide an artless fork's camera so the walkmesh centroid sat under the view axis; that reframe's floor-aim ray flips sign on an up-pitched camera, which was teleporting the pose to nonsense on native/logic forks. An imported camera now always keeps its faithful position and rotation — the viewport, not the camera object, is the navigation surface.
- The scroll-canvas minimum clamp is dropped —
canvas_h(andcanvas_w) had amin=448property clamp, so a real, smaller scroll canvas (field 2301's 512×256) silently clamped to 512×448 on import, corrupting the panel's displayed canvas and every downstream camera/paint-guide sizing. Both mins now drop to 128; defaults are unchanged. - In-game proven (2026-07-13) — field 2301 (Esto Gaza/Altar) re-imported and confirmed "a nice letterboxed shot of the map," matching the oracle's predicted framing pixel-for-pixel.
- The census script ships as a permanent regression harness at
studies/blender-camera-fidelity/census.py— re-run it against any future change to the add-on's camera decompose/pose math. - Blender add-on 0.9.24 → 0.9.28 across this arc.
🔧 Fixes & hardening
The two critical fixes called out above headline an unusually large fix load; the rest of it, condensed:
- A 24-reviewer adversarial pass across the whole kit confirmed 48 defects (47 fixed). Worst first: a non-manifold walkmesh edge could silently mislink a shipped
.bgi's neighbor data into corruption; a faileddeploy-campaignused to leave a half-installed mod folder behind with no revert path (it now restores the pre-install snapshot); a zero-span[[platform]]land=carry could divide by zero at runtime; and an.ebjump-straddle check now readsJMP_IFNOTunsigned the way the engine does, closing a path that could silently corrupt a field's bytecode. - Every load-bearing file writer is now atomic. A new shared
fsutil.atomic_write_bytes/texthelper (sibling.tmp+os.replace) now backs the save container,Memoria.ini,DictionaryPatch.txt, the form editor'sfield.tomlsave, engine-DLL installs, and the sound manifest — a crash mid-write can no longer leave a half-written file on disk. - Deploy registration no longer silently black-screens a shared mod folder. A minted text block's registration line could get dropped from the live
DictionaryPatch.txt, and runningbuild --outor "Install to game" from one checkout could silently unregister another session's field in the same folder. A new deploy ledger now explains any missing registration instead of leaving you to guess, and build/deploy share one ownership rule for what a run is allowed to overwrite. Memoria.inimod-order edits used to get silently reverted. The Memoria Launcher treats[Mod] Prioritiesas the master order and rewritesFolderNamesfrom it on every Play click, so editingFolderNamesalone — by hand or by tool — got clobbered at next launch. Deploy tooling now updates both fields together, and every kit-printed "edit Memoria.ini" instruction documents the rule.- Retired the undocumented
[[ladder]]arc_from/arc_tokeys — never in FORMAT.md, zero real users;validatenow steers you to the documentednavigable/top/bottomladder keys instead of silently building against a dead path. - Plus:
world-islandno longer ships a one-triangle grass hole at concave coastline dents;morph_in_placeno longer drops beach-foam emissions into a part the target cell doesn't carry; and the overworld placement simulator dropped a walking-ray limit the engine never actually reads, fixing a class of false "stuck" reports on forest canopies.
Engine bundle
The engine bundle is REFRESHED this release — the first new build since b12. dwix-custom-memoria-1.0.0b17.zip is a from-source rebuild of the complete Dream World IX patch stack on the same pinned Memoria base (6b8bb2d5): the fork-fidelity set (s23–s33), the custom-overworld pair (s34 loose mesh/texture override + s39, a self-heal for the "no controlled character" black screen), the overlay-texture cache (s35 — smoother field fades on re-entry and battle-return), the F6 in-game debug menu (s22, now with a World tab on the overworld: teleport, vehicle swap, disc switch), and — new — the experimental co-op netsync suite (s36–s38, s40–s41).
- Co-op requires this bundle on BOTH machines — netsync versions must match, and older/mixed builds deliberately refuse to pair. The suite is OFF by default: nothing activates unless
[Netsync]is configured (easiest viaff9mapkit coop host|join). - Not using co-op? The b12–b15 bundle still runs everything stable in this release — every stable pillar is either DLL-free (save points, Mognet, death rules, field scripting, the GUI, the Blender add-on) or runs on the already-shipped s34 override (the overworld synthesis/reshape verbs). Upgrading anyway gets you the smoother fades (s35) and the overworld self-heal (s39).
- The bundle is built from a clean checkout of the pinned base plus the committed patch stack — reproducible by anyone from
memoria-patches/(that folder's README is the per-file status map; ENGINE.md has the build how-to). - A novel (from-scratch / BG-borrow) field still runs on stock Memoria; a forked field and the overworld commands need the s23–s34 suite.
Getting started
- Windows: grab the installer
.exefrom the release assets — it bootstraps everything, and can optionally install the engine patches (backed-up, version-aware). - From PyPI:
uv tool install "ff9mapkit[gui,assets,save]"thenff9mapkit setup(detects your FF9 install, saves config, extracts base templates, and reports Memoria status). - Already installed?
uv tool upgrade ff9mapkit. - Full walkthrough, prerequisites, and the CLI reference: SETUP.md.
Provenance
Dream World IX ships NO FINAL FANTASY IX game data. It operates only on a copy of the game you already own, reading and patching your local install at build time. Base templates are regenerated from your own install via ff9mapkit extract-templates; no Square-Enix bytes are distributed. This is an unofficial, fan-made toolkit and is not affiliated with, endorsed by, or associated with Square Enix.
License
Dream World IX / ff9mapkit is released under the MIT License (© 2026 GameJawnsInc). The bundled engine patches modify Memoria, which is likewise MIT-licensed (© Albeoris).