Files
Emberwild/docs/WORLDLETS_DESIGN.md

17 KiB

Worldlets design

Status: proposal. Nothing here is built yet, apart from the first piece of groundwork (world.water_at() and data/fish.json, see §6).

1. What a worldlet is

A worldlet is a small, complete world type: a ~320 m playable area with its own shape, water, climate, wildlife and features, generated from a seed like today's island. The current map becomes the first worldlet, "Valley": a mountain ring, a lake and a sea cliff. Others can differ in kind, not just in seed:

Worldlet Shape Water Twist
Valley (today) mountain ring + sea cliff lake wolves' den in the rim
Cove low hills down to a sandy bay sea + harbour, a stream sea fishing, a pier, tides later
Highlands plateau, cliffs, a tarn small cold lake snow most days, harsher nights
Marsh flat, reedy, pools many shallow pools fog, insects, bog iron later
Tutorial small (~160 m), calm, fixed seed one pond guided steps, a fenced rabbit meadow, named spots (docs/TUTORIAL_DESIGN.md)

A worldlet is data plus a few generator building blocks, not a new scene with its own code. That keeps the game's systems shared (building, survival, creatures, saving) and makes worldlets addable by addons later (§7).

2. The big decision: one worldlet per save, or linked worldlets?

A. One worldlet per save. New World asks for a name, a seed and a worldlet type. Simple, and it fits everything already built.

B. Linked worldlets in one save. Several worldlets form one world, and you travel between them (a mountain pass, a cave tunnel, a boat). Each keeps its own state. More exciting, but much more work, especially in M2: a server would have to simulate several places at once, or keep all players together.

Recommendation: build A first, with a save layout that allows B later. Each worldlet's state goes in its own file from day one (worlds/<id>/worldlets/<worldlet>.json), and the player's own state moves to worlds/<id>/player.json. Then B is "add travel and a second file", not "migrate every save".

3. Definition format

data/worldlets/<id>.json. Every number that's an @export on terrain.gd today moves here, plus the lists that are hard-coded:

{
  "id": "cove",
  "name": "Cove",
  "description": "Low hills running down to a sheltered bay.",
  "size": 320,
  "terrain": {
    "base": {"height_scale": 7.0, "octaves": 4},
    "boundary": [
      {"type": "mountains", "from_deg": 60, "to_deg": 300, "height": 50},
      {"type": "beach", "from_deg": 300, "to_deg": 60, "slope": 0.08}
    ],
    "features": [
      {"type": "cove", "angle_deg": 0, "width": 60},
      {"type": "stream", "from": "mountains", "to": "cove"}
    ]
  },
  "water": {"sea_level": -1.5, "lakes": []},
  "climate": {"cold_day_chance": 0.1, "snowline": 45, "day_length": 1200},
  "wildlife": {
    "budget": {"rabbit": 5, "deer": 3, "boar": 2},
    "wolves": {"den": "rim", "den_day": 3, "hunt_day": 5}
  },
  "places": [{"type": "pier", "at": "cove"}],
  "spawn": {"near": "cove", "facing": "sea"}
}

Generator building blocks (GDScript, shared by all worldlets): base hills, mountain rim, sea cliff, beach slope, cove/bay, lake basin, stream bed, plateau. A worldlet only picks and parameterises them, so it can't do anything a block can't.

4. What changes in the code

Most game code already asks the terrain generic questions: sample_height, sample_normal, inside_boundary and find_spawn. The work is in the places that know about this island's single lake and cliff. Today:

Assumption Used in Becomes
one lake at water_level player (swimming), creature, spawner, den, build_system, props, lake_fish, world world.water_at(x, z) (done for fishing)
lake_center / lake_radius lake_fish, world (views) the water bodies list
sea_angle_deg, coast_point props (sea stacks, cliff-top strip), world (views) the boundary segment list
snow_height props, spawner, weather climate.snowline
play_radius props, den boundary queries

The steps:

  1. Terrain pipeline. terrain.gd becomes a runner that stacks the chosen building blocks into the height grid. The grid, collision, sample_height and the boundary wall stay exactly as they are.
  2. A WorldInfo API on the world node, for everything outside terrain: water_at, water_bodies(), boundary_at(angle), features(type), climate. No system reads terrain exports directly any more.
  3. Water bodies. Each lake, pond and the sea is a body with a surface, shape and type. The water mesh is built per body. Swimming, the "too deep" check, animal steering and fishing all go through water_at.
  4. Props and places by feature. Scenery reads densities from the definition. The den, the sea stacks and (in Cove) the pier are "places" positioned by features, not hard-coded angles.
  5. Saves. meta.json gains "worldlet": "valley". Older saves default to Valley, so nothing breaks.
  6. New World screen. Pick a worldlet type (with a short description and a preview render), then name and seed as now.

5. The Cove and sea fishing

  • Beach boundary. Sand slopes gently into shallow water. You can wade and swim out until the water is deep, and then a current carries you back (the beach equivalent of the invisible wall, with a toast).
  • Harbour. A sheltered bay with a generated wooden pier. The pier is built from the building pieces, so it's "placed by the world" and can't be dismantled.
  • Sea fish in data/fish.json under "sea": mackerel (common), cod (bigger, deeper water), and maybe crab pots later. Casting from the pier reaches deep water. The fishing code needs no changes: it already asks water_at what it landed in and looks up that water's fish.
  • Later: tides (sea level shifting slowly), a raft or rowing boat, and driftwood on the beach.

6. Done already (groundwork)

  • world.water_at(x, z) returns {type, surface, depth} for the lake and the sea, and fishing uses it instead of terrain.water_level.
  • data/fish.json holds the fish per water type, bite timings and dawn/dusk weights. ItemDB.define_water() registers a water's fish, so an addon or a worldlet can add "sea" without code.

7. How it fits the roadmap

  • Before M2 (recommended): steps 1-6 for Valley alone, with no visible change, then the Cove. Doing the refactor before multiplayer means M2 replicates "worldlet id + seed + edits", not one hard-coded island.
  • M2: the server loads one worldlet per save (option A). Clients regenerate it from id + seed, which is the same determinism we rely on now.
  • M3 (addons): define_worldlet() takes the same JSON. Addons can only combine building blocks, so a worldlet addon can't run arbitrary code in the generator.
  • Option B (linked worldlets) fits after M2, once we know how the server handles players in different places.

8. Phasing and tests

Phase Delivers Done when
W0 terrain pipeline + WorldInfo API; Valley defined in JSON seed 1337 generates the same height grid as today (a hash test), and all 9 suites still pass
W1 worldlet picked on New World and saved in meta; old saves = Valley load an old save and a new Valley save; both play
W2 water bodies (several per world), beach boundary block, swim-back current a test worldlet with two ponds and a beach passes the boundary and terrain-edge tests
W3 Cove worldlet: bay, stream, pier place, sea fish you catch a mackerel off the pier
W4 (optional) Highlands / Marsh each passes the shared suites
W5 (after M2, optional) linked worldlets and travel walk through a pass from Valley into Cove and back

Every existing suite runs per worldlet (boundary, terrain edges, survival, building, wildlife, fishing), so a new worldlet can't ship with a hole in its edge or animals that spawn in the sea.

9. Questions for you

  1. A or B? One worldlet per save to start, or do you want travel between them early? (Recommendation: A, with the save layout ready for B.)
  2. Worldlet size: keep ~320 m, or let some be bigger?
  3. Difficulty per worldlet: should harsher worldlets (Highlands) bring wolves sooner, or keep the same grace period everywhere?
  4. Which second worldlet first: Cove (sea fishing, the beach you mentioned) is my suggestion.

10. The journey: tunnelling out (proposal, Nov 2026)

A goal for the game, from Paul: once you have a good enough pick, you tunnel through the mountain rim into the next, slightly harder worldlet, and now and then go back to earlier ones for what only they have. This is option B (linked worldlets) from §2, reached by digging rather than a path.

The shape of it

  • A chain of worldlets, each a little harder: Valley (today's island) → Cove (sea, salt, mackerel) → Highlands (cold, deep snow, bigger packs) → Marsh (bog iron, sickness, mists) → the Ember, the volcanic heart the game is named for: the end of the journey.
  • The goal, something to tell players on day 1: Old Tam talks of the land beyond the mountains, where the fire in the earth still burns. Reach the Ember and light the old beacon there. (Whether there's an ending or it's open-ended is a question below.)
  • Gates: each tunnel needs the best tool from the worldlet you're in. The antler pickaxe (from the Old Stag) opens the Valley's tunnel; the Highlands need a pick made from something found in the Cove, and so on. Each worldlet has its own boss or trial, like the Old Stag.

A different crossing each time (Paul, Nov 2026)

Players have to work out how to get from each worldlet to the next, and it's a different way every time: tunnelling, climbing, burrowing, sailing. Each crossing is that worldlet's puzzle, and what it needs comes from what you've learned and gathered there:

From → to Crossing Needs (a first idea)
Valley → Cove Tunnel through the rim at a seam the antler pickaxe, timber props, light
Cove → Highlands Sail out of the bay and round the headland a boat (planks, nails, a hide sail, rope), calm weather, food that keeps
Highlands → Marsh Climb a frozen cliff climbing picks and rope, warmth to last the climb (bear cloak, pemmican)
Marsh → Ember Burrow under the old lava crust a digging tool from bog iron, and a way through the heat and gas
  • The hint, not the answer: each worldlet shows its way out without explaining it: a seam in the rock with old pick marks, a wrecked boat on the beach, rope frayed on a cliff ledge. Old Tam (and his tips) nudge, and the guide can keep it vague.
  • Coming back uses the same crossing in reverse (the tunnel stays dug, the boat stays moored, the rope stays fixed), so supply runs get easier once you've made the way.
  • New systems this needs, one per crossing: digging into a rim seam (tunnel and burrow share most of it), a boat you build and steer on the water, and climbing a marked face. Each is a phase of its own (J1 tunnel; later the sail, the climb, the burrow).

Tunnelling (the first crossing)

  • Where: each worldlet's rim has one or two seams: cracked, darker rock faces with a few loose stones at the foot, findable by exploring (and Old Tam's tips). The rim stays a wall everywhere else.
  • How: hit a seam with a good enough pick, many times. Each stretch dug needs timber props (wood) or it falls back in, and it's dark in there: candles, torch stands or a lantern. The tunnel is a short, built-for-it cave (a few bends, maybe a side chamber with ore or a hazard), so it stays hand-made rather than a hole through the terrain mesh, which the heightmap can't do.
  • Through: the far end opens onto the next worldlet. Walking out loads it (a short fade, "The Cove"), with your bag, cloak and stats as they are.

Why go back

  • Each worldlet has things the others lack. Clay and salt only in the Valley and Cove; little wood in the Highlands; no bears outside the Valley (so bear fat for candles and pemmican is a supply run); a fish or herb only one place has. Food that keeps (pemmican, salted and smoked meat, clay jars) becomes what you carry between them.
  • Your bases stay where you built them: each worldlet keeps its own save file, so your Valley hut, chests and jars are waiting.
  • Time: a worldlet you're not in stands still (as a server does with nobody on), so a base you left doesn't starve or get raided.

Building it (phases)

Phase Delivers Done when
W0-W2 the worldlet groundwork above (terrain pipeline, worldlet in the save, several water bodies) as §8
J1 seams in the rim, digging with props and light, a tunnel scene, travel between two worldlets in single-player, per-worldlet save files dig through from the Valley, walk to the Cove, walk back to your hut
W3 the Cove (§5) as the second worldlet its suites pass, and its tunnel back works
J2 gates and supply needs: tool tiers per tunnel, resources only some worldlets have a test bot can't open the second tunnel without the Cove's pick
J3 multiplayer travel (see below) two players in different worldlets on one server, or the rule we choose
J4 Highlands, Marsh, the Ember and its ending the journey can be finished

Multiplayer: the hard part

A server today runs one island. Options:

  • Everyone together: travel needs every player at the tunnel (like the sleep rule). Simplest; one worldlet runs at a time.
  • Spread out: the server runs each worldlet that has someone in it. The most freedom, and the most work and CPU.
  • Party leader: whoever goes through brings the others with a vote.

Decisions (Paul, Nov 2026)

  • Extendable, not finished: the Ember has a last boss, and after it you keep playing. The chain is built so more worldlets (and crossings) can be added after the Ember later: each worldlet names the next, nothing assumes the Ember is the end.
  • The crossings: tunnel, sail, climb, burrow, in that order.
  • Harder by biome, not just by numbers: each worldlet has its own, stronger animals and its own hazards. The Marsh, for one: mosquitoes whose bites can make you sick, and bog, mist and the like. Others in the same spirit per biome (to design with each worldlet).
  • Multiplayer: spread out. Each player goes where they like; the server runs every worldlet someone is in, and the rest stand still. Built in J3, after single-player travel works. What that means for the code:
    • one server process holds several worldlet scenes, each with its own clock, weather, creatures and save file;
    • every player belongs to one worldlet at a time, and the server only sends them that worldlet's world, creatures, sounds and chat-free events (chat and the player list stay server-wide);
    • a crossing moves a player from one worldlet to another (save their place, load or wake the other worldlet, send them its world);
    • sleep: "everyone in bed" counts the players in that worldlet only;
    • the Old Stag and other bosses scale with the players in their worldlet.
  • Boats: one each, no passengers. A boat belongs to whoever built it (like a bed): only the owner, and their friends under the server's locks rule, can sail it, and nobody else can dismantle it. It stays moored where it was last left (the Cove end or the Highlands end), so nobody is stranded by someone else taking the only boat. Lose or wreck it and you build another, which is harder where wood is scarce (the Highlands), so plan the trip back. Each player has to earn their own crossing by boat.
  • The other crossings are shared: a dug tunnel, a fixed climbing rope and a dug burrow are changes to the world, there for everyone once one player has made them (like a door or a bridge).

Server cost, measured (0.3.1)

One worldlet, a dedicated server on the M5 Pro, never pausing: about 12% of one CPU core and 270 MB of memory, with nobody on. Players add creatures, weather and pickups around them. So with spread out, the worst case of 8 players in all 5 worldlets is about five of today's servers in one process: roughly 1.4 GB and a little over half a core before players, and worldlets with nobody in them stand still and cost next to nothing. Comfortable on the home Mac; a small cloud server (2 GB) would be tight.

(Answered) questions

  1. The ending: a real ending at the Ember (light the beacon, a last boss, the credits) or open-ended (the journey is the goal, keep living)?
  2. The crossings: do the four above fit (tunnel, sail, climb, burrow), and in that order? Should a worldlet have more than one way out (a hard one and a long one)? And for the tunnel: only at marked seams, or anywhere in the rim (more freedom, harder to make good tunnels)?
  3. Multiplayer: everyone together, spread out, or a party vote?
  4. How much harder each worldlet: stronger animals and weather only, or new rules too (thirst in the Ember, sickness in the Marsh)?
  5. Order: Cove second (sea fishing, gentle) as planned, or straight into the Highlands for a harder step?