World Structure
Part of The design hub. Drafted 2026-08-30. Locks in with the era master clock (The design hub) and the raid system (Raid System (both directions)).
Shape: one contiguous map — a home valley with era wedges
- One seamless overworld, not disconnected regions. The map is a central home valley (era 1) with ~4 wedge zones radiating off it, one per material era 2–5. Zone count maps 1:1 onto eras — the world IS the master clock made geography.
- Wedges, not concentric rings: every zone borders the valley, so the trek home never compounds across zones — the home-base pull stays strong all game without travel becoming a chore.
- Outer edges are hard borders (cliffs, sea, aether-storm wall) — scope containment, and room for expansion content later.
Zone access = infrastructure projects (the signature mechanic)
Each wedge sits behind a chokepoint: a collapsed bridge, a rockslide-buried pass, a ruined gatehouse, a silted ferry crossing. Opening a zone is a city project — materials + the right building tier + active-play time (e.g. the era-3 pass needs the Stone Worker and Engineer; the era-4 crossing needs a rebuilt ferry).
- Progression gates are diegetic: the world opens because your city grew, not because a number went up. Rebuilding the world and unlocking it are the same act.
- Soft pre-entry: each chokepoint has a sneakable gap (cliff scramble, swim) for underequipped scouting/stealth trips — you can peek and poach, but you can't haul carts of ore home until the route is open. (Hauling capacity through gaps is tiny; the project unlocks bulk flow.)
- Once open, a route also flows the OTHER way: raid parties from that zone's rival city can now reach you. Opening a zone raises your Prosperity exposure — expansion is a choice with teeth.
Zone anatomy (authoring template, per wedge)
Each wedge contains, tuned to its era: 1. One rival city (era-matched; grows/rebuilds within its zone's era band — it does NOT chase the player's era, so cleared zones stay cleared-feeling and each new wedge is the fresh threat). 2. Resource clusters — the era's ore/wood/herbs; visible gathering nodes; later partially automated via city-level gathering outposts? (TBD — outpost building parked.) 3. One set-piece dungeon — mine, ruin, or vault; primary home of tomes, essences, unique found-gear, and a mid-zone boss. 4. Camps & wanderers — humanoid camps (lootable remains, rescue leads), wandering recruits/traders (feed the Inn pipeline). 5. The chokepoint to the valley + one lateral shortcut to an adjacent wedge (unlockable from the far side — Metroidvania-style loop-opening, cuts late-game travel).
Prototype (v0.1.14): the Town scene is the fixed city site; the old arena is the valley's first hunting ground, reached by the road east (a gap in the forest ring). Two-scene travel with persistent hero + city data is the seed of the wedge/chokepoint structure below.
Home valley (era 1)
- Fixed city site (pre-chosen plot) — scope decision: hand-design the city surroundings for raid approaches and camera work rather than supporting free placement. Free/multi-site placement parked.
- Valley holds the tutorial arc: first animals, first humanoid camp, first recruit, copper deposits, and ONE weak rival settlement (the tutorial raid target/aggressor).
Travel & knowledge
- Roads: Stone Worker project per zone; move-speed buff on roads, and road quality gates cart/bulk hauling (ties into chokepoint rule).
- Fast travel: Inn stable unlocks waypoint travel between the city and discovered zone waystations (repair each waystation = small project). Late-game aether waygate as an era-5 capstone (instant, any waystation).
- Map & fog: map uncovers by exploration (fog-of-war on unexplored land); the Inn rumor board pins POIs you haven't found — intel says "a Master smith is held in the eastern ruin," the map shows roughly where, your legs do the rest.
What this answers from the parking lot
- Exploration pacing vs home pull: wedges keep home central; projects meter expansion; raids pull you home; rumor board pushes you out.
- Rival-city scaling: era-banded per zone (answered — they don't chase).
- World rebuilding: chokepoints, roads, waystations, ferries — the Aether Shard "rebuild the world" DNA carried forward as the world's unlock mechanism.
Open questions
- Gathering outposts (city-level remote gathering) — building or cut?
- Zone visual identities (biome per wedge: forest/marsh/highlands/ ashlands?) — art budget question.
- Lateral shortcuts: how many, and are any stealth-gadget-gated?
- Does the era-5 wedge need a rival city, or is its "city" the final set-piece (a capital worthy of the raze decision)?
- Underground layer (connected mine network) — expansion parking lot.
Prototype v0.1.45 (2026-09-06) — the wilds scale with the era:World.wild_bandit_hp/dmg/loot(hp ×(1 + 0.25·(era−1)), stab 12 + 2·(era−1), gold on remains 1 + (era−1)) andwild_boar_hp/gore/ extra_cuts(hp ×(1 + 0.25·(era−1)), gore 10 + 2·(era−1), +1 meat from Era 3), applied by the Arena at spawn (Enemy.loot_drop,Boar.gore_dmg,Boar.extra_cuts). Together with camps (v0.1.42), Ironhold (v0.1.43) and raids (v0.1.44) the whole valley scales withcity.era.
Prototype v0.1.46 (2026-09-06) — deposits scale with the era:GatherNode.yield_for(kind)= base yield + (era − 1), used at spawn and regrow;bonus_for(kind)= the KINDS "bonus" {item, era, text} oncecity.era≥ era, handed over on the last unit (deadfall → hide E2, stone pile → cut stone E2, herb patch → sunroot E2, berry bush → grain E2, copper vein → rough gem E3). Prompt shows "Gather X ×n".
Built 2026-09-12 (v0.1.63): the vast wilds, fog of war, keepers indoors
Eric: "I like the phasing into camps/enemy towns. I just thought the overworld would be vast like Aether Shard. I like the fog of war idea. Shouldn't the NPCs be inside the buildings once they are built?"
- One vast valley.
scripts/arena.gdnow builds the wilds withGround.new({ext 76, ring_x 72, ring_y 56, ...})— ~150 tiles across.HOME_X -2260/EAST_X 2240, camera limits ±2432,INTRO_SPAWN (-2080, 10), the ambush on the west road at (-2000/-1940, ±), Ansel's stone at (520,-900). Camp sites (inWorld.fresh_camps): Bandit (1400,520), Wolfhead (400,-1500), Thornback (-700,1550), Greywater (-1800,800). 21 deposits, 8 bandits, 6 boars, 8 sacks; ten meadows, two ponds, eighteen copses. Town gate/resume spawn (-2200,-20); Ironhold's "leave" spawn (2140,-20). Camps and Ironhold stay phased. - Fog of war
scripts/fog.gd(classFog, group "fog"): 64 px cells, sight radius 5 cells (~320 px), revealed on the PHYSICS clock (a fresh scene lights on its first tick), drawn as black rects over the camera view at z 20 (above canopy; the HUD is a CanvasLayer). Persistence:World.city["fog"][area]= {"cx,cy": true};fresh_cityseeds"fog": {}; old saves get it on first entry. Only the wilds are fogged (camps/rival are small phased rooms). - Keepers work inside their building once it stands.
Npc.inside+set_inside(v)(hidden, collision off, out of "interactable").Town.sync_keepers()runs each second from_recruits_frameand at once whenWorld.advancefinishes a build (call_group("town", "sync_keepers")).Building._markshows the keeper's "!"/"?" over the roof (bobbing; faded "?" in progress);Building.keeper_node().ForgeDialogBUILT page gets a first button "TALK TO <KEEPER> — has an ask / turn in" that closes the page and openshud.open_dialog. A wrecked/plot building puts the keeper back at their NPC_SPOT. World.recruit_all()(debug--built) now seats innkeeper/smith/ priest too and callsintro_done().- Smoke: 470 checks (fog reveal + persistence, keeper indoors, TALK TO MARLA opens her quest talk).
- Open: fog edges are hard cells (AS-style); a soft edge / dithered border is a later polish. No fog on the town (it's home).
Built 2026-09-13 (v0.1.64): rooms you walk into, the valley map, mid-screen messages
Eric: "Game messages should appear in the middle of the screen. I meant go inside the buildings like we do in Aether Shard. Also, the overworld is small, but having a map would help."
- Interiors.
scripts/interior.gd(classInterior, scenescenes/interior.tscn,World.INTERIOR,World.pending_interior). Reuses Aether Shard's winlu rooms verbatim:data/interiors/winlu_*.json(backdrop, '#'/'.' grid, exits, npcs[0].tile = the counter spot) +assets/sprites/interiors/*32.png;sunhall_hallfor the town hall.Interior.ROOMSmaps building → room; yards/fields/mine/store/drill yard have none (keepers stay outside). Walls = one StaticBody rect per '#' run; door = Area2D on the exit tiles →leave()→World.travel(TOWN, home_spot)(the plot's feet + (0,34)). A hiddenBuildingnode for the plot is parked at (-4000,-4000) so the page logic works indoors. KeeperNpc.at_counter+counter→ interact opensopen_forge(building), whose page shows TALK TO <keeper> first. Camera holds on the room centre when it fits under the HUD strip, else follows with limits.Player.in_town()counts Interior (half hunger).World.save_gamewrites the door spot when indoors.Building.has_room(); prompt "Enter the X";Town.sync_keepershides only roofed keepers. Debug:--interior=<plot>(+--built). - Map.
Fognow storesGround.Codeper revealed cell (viaGround.code_at; oldtrue= grass);scripts/map_view.gdpaints 76×76 cells at 6 px with home (HOME_X), Ironhold (EAST_X), camps once their cell is seen (grey when broken) and you (or home when in town). Pause menu MAP button +M;open_page("map");--menu-map. - Messages (
Hud._toast_panel) sit at view centre, 60 px up. - Smoke: 478 checks (interior round trip, counter page, map page).
- Open: no chests/flavor spots inside yet (the JSON carries them); the hall has no keeper so it's an empty grand room; a joypad button for the map (BACK is quests); mobile touch button for the map.
- 2026-09-14 (v0.1.65), Eric: "Swap map and quests." TAB / pad BACK / the touch button beside the menu open THE VALLEY; quests are on M, a second touch "?" button, and the pause menu (MAP listed above QUESTS). Tracker overflow hint says "M" / "menu"; CONTROLS table has both rows.
Built 2026-09-15 (v0.1.66): the valley on Aether Shard's world engine
Eric: "In pause menu, the UI should be seen. Exiting the UI shouldn't use abilities. The town needs to be bigger, the overworld needs to look and act more like Aether Shard's overworld. It feels too small. The fog of war also needs to operate like Aether Shard, getting the cart from Ironhold disappears when you leave Ironhold."
- World engine = Aether Shard's, ported verbatim:
scripts/world_data.gd(macro map → per-vertex terrain, roads, stamps, walkable),chunk_manager.gd(32-tile chunks, row-sliced generation),fog_of_war.gd(Aether Sight levels per chunk, shader gradient; the Achievements hook removed). The autotiler/tileset/tileset_data.json were already shared. - The map:
tools/generate_valley_map.py→data/valley_map.txt(ASMAP, 128×96 chars, seed 20260915) +valley_map_preview.png. An elliptical bowl: mountains at the rim fading to mist, inner ridges, forests/meadows by moisture noise, one river N→S, home 'T' west (14,48), Ironhold 'T' east (113,46), four camp clearings, two ruins, A* roads (home↔Ironhold, trails to camps/ruins). EVERY entity is a landmark line:@ambush,@ansel,@<id>_camp,@dep_<kind>_N(30),@tome_<id>_1(6),@sack_N(8),@bandit_N(16),@boar_N(12),@open_N(4, open ground the smoke test stands on). Self-check: all reachable. scripts/arena.gd(class Arena, node still "Arena"): staticdata()/spot(name)/home_exit()/ironhold_exit()/tile_of(); layers under/ground/decor/objects(z 3 canopy); FogOfWar z 50 in group "fog", SIGHT 9 tiles; per-chunkStaticBody2Dwith a rect per run of non-walkable tiles (rock, water, house blocks, mist);walkable_near()for spawns; town cells are 128 px Area2D triggers, armed only after you leave them; fog levels ↔city.fog.valleyas base64 per chunk (store_fog()called byWorld.travel/save_game/_exit_tree).World.intro_spawn()(ambush − 130 px),World.camp_site(id)(landmark, else the record's site),CampSitepositions from it,Camp.leave()too.scripts/map_view.gd: macro cells at 4 px masked by fog level (live fog or decoded record), home/Ironhold always, camps once seen, you.- Also this build:
Player.lock_input()after every menu/dialog close (0.3 s); pause dim/panel under the HUD strip (bars visible), skills/hints hidden while paused;World.cart_home()— the cart rideshero.cart, follows across the wilds, delivers at the town, drops as a sack where you fell; town ×1.5 (PLOTS/NPC_SPOTS/GATE 800/EXIT_X 900, ground ext 40, camera ±1280; raiders route via the gate post, waypoint reach 24). - Smoke: 481 checks. Old
scripts/fog.gdremoved. - Open: chunk collision is built in one go per chunk (a hitch on weak handhelds is possible — slice it like generation if it shows); roads are boxy A* like AS's; no per-chunk wandering spawns yet (AS's enemy/gather spawners are the next port if the fixed sixteen feel thin); the ruins are decoration only.
Built 2026-09-18 (v0.1.67): the Town Hall room, furnished
Eric: "Finish the town hall." The hall has no keeper, so its room (sunhall_hall) gets two working spots, scripts/hall_prop.gd (class HallProp, group "hall_props", interactable reach 64): the ledger on the dais (winlu32_banner; opens the hall's whole ForgeDialog page — ledger line, bounties, appointment, charter, modules) and the notice board by the west wall (winlu32_notice_board; Hud.open_board → ForgeDialog.open_board = _mode "board": bounties only, or "bare boards" until the Notice Board module stands). _bounty_rows() is shared by both pages and now lists done bounties too. Interior._furnish_hall(); --built in the Interior also grants every module. Smoke: 486 checks. Decision: the mayor stays at their own trade counter; the hall is the ledger, not a second copy of the person.
Built 2026-09-18 (v0.1.68): the chunk spawner
Eric: "Start the spawners." scripts/chunk_spawner.gd (class ChunkSpawner) folds Aether Shard's enemy and gather spawners into one: on ChunkManager.chunk_generated it rolls up to 2 deposits and 2 enemies per chunk from data/wild_spawns.json (tables keyed by macro char: . , # ^; 3 attempts, chance 0.45), never on a road, in rock, within 12 tiles of the hero, or (enemies) within 8 macro cells of home — the calm ring keeps the opening's ambush the only fight on that road. Spawns carry group "spawned"; on chunk_freed deposits and idle enemies go, a bandit in CHASE/WINDUP/LUNGE/RECOVER stays. Era stats via World.wild_*. Landmark spawns from valley_map.txt sit on top. Arena.spawn_seed / --spawn-seed=N pin the dice (the smoke uses 7, then disables the spawner for the rest of the run). Smoke: 488 checks. Open: no weights per era yet (an Era-3 valley could roll more bandits); no rare spawns (a champion, a merchant cart).
Built 2026-09-18 (v0.1.69): the ruins have a purpose; rare spawns
Eric: "Give the ruins a purpose" / "Let's add rare spawns too."
- Ruins = camp records of kind "ruin" (
World.fresh_campsruin_north"the Broken Keep" → Hesk the drillmaster;ruin_south"the Old Watchtower" → Nell the herbalist; 3 squatters + a chief 80/70 hp; hoardCAMP_HOARD.ruin= 2 tome fragments, 3 cut stone, a potion, 4 gold + era extras).INN_ARRIVALSis now weaver/miller/fisher only; the once-broken softener applies to ruins like camps.World.camp_site()puts a ruin's site 56 px below the cell centre (the castle stamp blocks the centre); CampSite label says "squatters"; the Camp scene dresses a ruin with a stone ring, banners and rubble instead of hay; MapView paints ruins pale. Arena places CampSites forruin_*landmarks. - Rare spawns (
data/wild_spawns.json"rare": chance 0.06 per chunk, table champion 2 / boar_herd 3 / wrecked_cart 3, each on its own terrain):ChunkSpawner.spawn_rare(kind, pos)— a wanderingChampion(wanderer = true, era-scaled, prize sack: 2 tome fragments, 2 raw essence, potion, 10 gold), a herd of 3–4 era boars in a ring, a pedlar's cart (cart sprite + sack: cloth, grain, smoked fish, 8 gold, +charcoal from Era 2) within a cell of a road. Group "rare_spawn". Smoke: 497.
Built 2026-09-18 (v0.1.70): the skill diamond
Eric: "The abilities at the bottom. It feels weird. Can we have an assignment screen that is like this: X / X X / X. Then for controllers the top is always up, bottom is down, etc. For keyboards/touchscreens you can keep the horizontal layout." Done: PauseMenu.DIAMOND lays the EQUIPPED cells on a 3×3 board (slot 1 top = ↑/1, 2 right, 3 bottom, 4 left) with a caption naming the directions (pad) or keys; badges show ↑→↓← on a pad. Hud.layout_slots(pad) moves the four HUD slots into the same diamond at 0.8 scale bottom-centre when a pad is connected (_pad_frame calls it on every pad/keys switch), and back to the row otherwise; touch keeps its own row. Hud.pad_connected() is static now. Smoke: 499 checks.
Built 2026-09-18 (v0.1.71): text inside the panels
Eric: "The text needs to land inside of the panels on the menus." Every PauseMenu._page() box now sits in a ScrollContainer (vertical, follow focus; _show_box() / _hide_pages() toggle the frame); caption_label no longer clips (grows, the page scrolls); the ABILITIES board is one HBox — diamond block left, LEARNED/PASSIVES with headers above their grids right — so it fits 940×538 without scrolling; QuestDialog._fit() sizes the panel to the wrapped text (min 150) after every refresh. Note for future pages: the panel's usable height is ~460 px at 1024×600 (the HUD strip takes 54); phones keep the same height (expand widens). Smoke: 499.