Files
Emberwild/docs/WORLDLETS_DESIGN.md

592 lines
34 KiB
Markdown

# Worldlets design
Status: **W0-W3, J1 and J3a built**: the Cove (with its stream), the cliff
tunnel, and the journey on a server (a server per land; chat and the
player list across lands are J3b). The rest is planned.
*As built in J3a (a server per land):*
- `scripts/net/lands.gd` (autoload `Lands`): the server players join is the
hub. `Lands.ensure(land)` starts a land server (`--server --land cove
--port <hub+1..> --hub-port <hub>`, same config and world folder, output
to `land-<land>.log` beside server.json) and waits for it to say it's
listening. Hub and land servers talk over TCP on 127.0.0.1, port hub +
100, one JSON object per line: hello/ready, the player count, the clock
(the hub's, every 10 s: one clock everywhere), how far the tunnel's dug,
one-time passes, "stop", "host_left". A land server stops after 2 minutes
empty, when the hub stops, or if it loses the hub.
- A land server's saves: `Saves.land` makes it load and save its own
worldlet's file (`worldlets/cove.json`) in the hub's world folder.
Characters (`players/<id>.json`) are shared, and say which land they're
in (`land`) and, mid-crossing, where they'll come out (`arrive`).
- Crossing (`PlayerNet.cross`): the server saves the character as heading
there, makes sure the other server's up, and sends the game
`_s2c_travel {land, port, pass}`; the game drops the connection quietly
and joins that port with the pass (`Net.join(address, "", pass)`), checked
after the identity proof: a land server lets nobody in without one. The
world reloads on `Net.arrived`. Joining the hub when your character's in
another land sends you on there; a crossing that fails goes back to the
title. A hosted game: the host crossing doesn't stop the hub; the host
leaving the game from another land stops everything.
- The tunnel online: built on the server and in every game. The server
decides blows on its faces (`"hit"` finds them by uid), props
(`"tunnel_prop"`), going in and out (`"tunnel_go"`: a server teleport;
the move check allows the tunnel only from inside it) and the crossings
(`"cross"`), and tells everyone how it stands (`_s2c_tunnel`; it's in the
shared state a joining player gets, and saved with the Valley).
- `tests/test_net_lands.gd`: Alice digs and props online, lights it,
crosses to the Cove (bag and day kept) while Bob stays in the Valley,
comes back up, then leaves from the Cove and rejoining sends her there.
*As built in J3b (everyone, everywhere):*
- Chat and server messages go to every land (`Chat._all` relays through
the hub, `Lands.relay_line`).
- The hub keeps the **roster** (everyone on every land, with their land);
every land and every player's game gets it (`Net.roster`,
`Net.everyone()`): the pause menu's player list and `/players` show
where each player is.
- Commands typed in another land run **on the hub** (it alone keeps
server.json) and answer back there; after one that changes server.json
(`/set`, `/ban`, `/admin`, `/friend`...) every land reloads it.
`/kick` and `/ban` reach the land the player's in; `/weather`, `/save`
and `/backup` reach every land.
- **Sleep needs everyone in every land:** land servers report their beds,
the hub decides, and every land passes the night together. Getting into
bed while others are up: "You're in bed: waiting for everyone else to go
to bed (1 of 2 asleep)", and every land hears "Bob is in bed (1 of 2
asleep)" in chat.
- `tests/test_net_lands.gd` covers it all, Alice in the Cove and Bob in the
Valley.
*As built in J3c:*
- A land server that stops (crashed, or killed) tells its players "Lost the
connection to the Cove. Join the server again to carry on there."; the
hub notices it's gone (even mid-join) and joining again starts it afresh
and sends them back (their characters were saved by its autosave).
- The extra ports: `Lands.extra_ports()` / `ports_text()` ("UDP 24242 and
24243 (the Cove)"), on the Host page, in the "You're hosting" message, in
a hub's start-up log, and in docs/HOSTING.md.
- Tested in `test_net_lands.gd` (the Cove's server killed, Alice rejoins)
and `test_worldlet_pick.gd` (the Host page).
*As built in J1:*
- `scripts/world/tunnel.gd`: the seam (an outcrop on the clifftop strip,
7 m in from the edge, its angle from the seed) and the tunnel space, out
of sight 260 m under the island: a CSG rock block with the landings and
six sloping stretches cut out (6 m on, 4 m down each, switching back on
two lanes: 24 m down), then a sea cave. Faces (`tunnel_face.gd`) answer
like boulders, so the pick works on them: tier 3 only ("Too hard: you
need a better pick"), 10 blows for the seam and 15 per stretch. Each
stretch must be propped (2 wood, [E] at its mouth) before the next face
takes a blow ("It's loose..."): 10 wood all the way down.
- Underground: no sun, moon, sky reflections or sky-tinted fog
(`day_night.underground`), no weather, no lake water, sheltered. Candles
and torch stands place anywhere on its floor and save as ordinary pieces.
- Walking out of the sea cave's mouth travels (`world.travel()`): the
worldlet you leave is saved, your character and the clock go with you,
and the other loads, arriving at its sea cave (`sea_cave.gd`, on the
Cove's beach where the headland starts). [E] at the cave goes back up the
same tunnel to its bottom, once it's dug through.
- Saves: `world.json` is the home worldlet's state, as before;
others are `worldlets/<id>.json`; meta.json's `worldlet` is where you
are, `home` where you began, `arrive` where the next load puts you.
- `tests/test_tunnel.gd`: dig through, walk every stretch down and back
up, light it, reload, out to the Cove, back up, out at the top, and the
chest at home is still there.
- Not yet: Old Tam's tip about the seam, the Guide page, and travel on a
server (J3: the tunnel isn't built online). The journey between
worldlets is §10, and the two ways to play (the journey, or one land) are in
its decisions.
*As built in W3:*
- `data/worldlets/cove.json`: low hills, mountains behind, a wide sandy
bay (a `beach` block) and a small pond. Not a start; its `next` is back
to the Valley (tunnel, J1) and on to the Highlands (sail).
- The pier (`scripts/world/pier.gd`), a world "place" (`"places":
[{"type": "pier"}]`): planks on posts from where the sand is at deck
height to 24 m past the waterline. Worked out from the terrain, so it's
the same for everyone and never saved or dismantled; scenery keeps clear
of it. ~1.9 m of sea at its end, ~2.7 m a cast beyond.
- Sea fish in `data/fish.json`: mackerel (common) and cod, which only bites
in water 2.2 m or deeper (a per-fish `min_depth`). Grilled at a campfire,
salted, or smoked, like the lake's.
- New World has no "Where": the journey always begins in the Valley (the
Host page keeps it, for One land mode). Started with
`godot --path . -- --dev-worldlets`, New World and Host offer every
worldlet, the others marked "(testing)", to try one directly.
- No sea stacks without a sea cliff.
- Tests: `test_cove` (walk onto the pier, catch a mackerel off its end, cod
only in deep water), and the Cove in `test_terrain_edges` and
`test_boundary`.
- The stream (after 0.4.0): a `stream` block, a line of points from a
spring under the mountains to the bay. Its water surface is the running
minimum of the ground along it (so it only ever runs downhill, down to
sea level), the bed is cut 0.6 m under that and the banks rise a little
above it; its water is its own ribbon mesh (`terrain._stream_mesh()`),
murkier than the lake. `water_at()` answers "stream" (trout bite there,
`data/fish.json`), the map shows it, and scenery, the den, the barrow and
the stones keep clear of it (`terrain.near_stream()`).
*As built in W2:*
- `lake` blocks can repeat: every lake and pond is its own body
(`terrain.lakes`, `world.water_bodies()`), all at one surface
(`water_level`, set by the first). The first is still "the lake" to the
older code (ice, its fish, the tutorial).
- A `beach` block (angle, width, `shore_offset`, `slope`, `current_out`,
`wall_out`): the land eases to a waterline and shelves gently into the
sea, which is then the same surface as the ponds (one ocean sheet, no
inland disc). The invisible wall stands well out in the water, and before
it a current (`terrain.current_at()`, `world._current()`) carries
swimmers back faster than they can swim, with a toast.
- `water_at()` tells the sea off a beach from a pond.
- Test-only worldlets live in `tests/worldlets/` (never listed or offered).
`testbay.json` (two ponds and a beach) runs through
`test_water_bodies`, `test_terrain_edges` (8 seeds) and `test_boundary`
(swimming out across the beach).
*As built in W1:* a "Where" picker on the New World and Host Game pages
(worldlets with `"start": true`; only the Valley, so it's greyed out), the
worldlet in the Load list, and a hosted island keeps its worldlet like its
seed.
*As built in W0:*
- `data/worldlets/valley.json`: the Valley as data, its terrain as five
building blocks in order (`hills`, `rim`, `sea_cliff`, `lake`,
`ocean_edge`) with every number that used to be hard-coded.
- `terrain.gd` runs a worldlet's blocks (`use_worldlet()`); each block is
the old code's arithmetic, unchanged. `tests/test_worldlets.gd` checks
four seeds give exactly the same height grid as before (SHA-256 of the
grid, taken from the old code).
- The worldlet is chosen like the seed: a server's (it sends its real one),
else the save's `meta.json` "worldlet", else the Valley (worlds from
before W0). New worlds and servers record it.
- The WorldInfo API on the world: `worldlet_id()`, `water_bodies()`,
`climate()`, `crossings()` (the planned ways on), beside `water_at()`.
Systems still read the terrain's numbers directly; they move over to
these as each new worldlet needs them to differ (W1-W3).
## 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:
```json
{
"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.
### The Cove's own dangers and the pirate treasure (Paul, Nov 2026, for 0.6.0)
Paused at the Cove for a playtest before the Highlands. The Cove gets its
own set pieces instead of copies of the Valley's:
- **Set pieces per land**, listed in each worldlet's data (`"places"`). The
Valley keeps the wolf den, the barrow, the standing stones (the Old Stag)
and the bear; the Cove keeps the wolves only, and gets the wreck and the
treasure.
- **The wreck:** a beached ship on the rocks at the far end of the bay, its
timbers along the sand (driftwood). Its captain's sea chest: some nails,
rope, coins and a **torn treasure map**.
- **The map:** reading it puts an X on your N map: a mound of loose earth
by a marker stone in the Cove's hills (your own spot: see below).
- **The dig:** a pickaxe, many blows, turns up the **pirate chest**: a
**ship's compass** (what the boat will need to sail to the Highlands: the
next crossing's gate, J2), a **spyglass** (zooms your view while held),
**pirate gold**, and a **cutlass** (quick, harder-hitting than the iron
spear).
- **One treasure each, each in its own place** (online too): the wreck's
chest gives each player their own map, once, and each map's X is
somewhere different: picked from the world's seed and the player's id
(so it's fixed for them), among dry, gentle spots in the Cove's hills
away from the other places. You only see your own mound; the server knows
every player's spot and what each has found, and checks the right one
when you dig.
- **Gold** is a **trophy you can place** in your hut (a heap of coins) for
now; what else it's for waits until there's a trader.
- **The drowned crew:** the treasure's guards (the Cove's barrow guards).
Two or three rise from the shallows round the dig at night, or as the
chest comes up, for whoever's digging. Slow, hit hard, unafraid of fire,
they drag at you in the water.
- **Crabs** on the beach at dusk (pinch if trodden on; food), and **gulls**,
thieves like the crows (fish off drying racks, things dropped on sand).
- **The boat** (J2's first part): driftwood, seagrass rope and pitch (pine
resin and salt), at a workbench, placed at the pier. One each, moored,
yours and your friends'. Setting sail says the Highlands are uncharted
for now.
- **Report a bug** in the pause menu, and a short playtest guide.
- Order: set pieces per land, the wreck and map, the dig and the drowned
crew, crabs and gulls, the boat, report a bug; then 0.6.0 (protocol 18).
## 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.
### The first tunnel: down the sea cliff to the Cove
The Valley's sea side ends at a sheer cliff, its top about level with the
valley and the sea some 25 m below. The Cove is down there, so the first
tunnel goes **down**, inside the cliff, from the clifftop to the shore.
- **The seam:** a cracked outcrop on the clifftop strip near the edge (the
sea_cliff block marks where), with old pick marks and loose stones at its
foot. It can't be dug until you have the antler pickaxe (tier 3); a lesser
pick just chips it ("Too hard: you need a better pick").
- **Digging down:** the tunnel is about six stretches, each a few metres on
and a few down: a sloping passage that turns back on itself like a
stair, so the 25 m drop fits inside the cliff. A stretch takes many pick
blows (stamina, and wear on the pick) to open.
- **Props:** each new stretch needs a **timber prop** (2 wood) set at its
mouth before you can dig the next. Leave one out and the face won't take
another blow ("It's loose: prop it first"); nothing caves in on you, so
it's a cost, not a trap. That's 10 wood all the way down.
- **Light:** the tunnel has no daylight at all. Every stretch is pitch dark
until you bring light: a torch in your hand works, but your hands are full
with the pick, so the way to do it is to set **candles and torch stands**
as you go (tallow candles from bear fat, torch stands from wood). Placed
lights stay, so the way is lit for the trip back and for friends.
- **The bottom:** the last stretch breaks out into a sea cave, its mouth
opening on the Cove's shingle beach. Walking out of the mouth is the
crossing: a fade, then the Cove, arriving at the cave's Cove end. The way
back starts there and climbs the same tunnel.
- **What's saved:** the tunnel belongs to the Valley's save: how far it's
dug, the props, the lights. On a server it's shared: anyone can dig on
from where someone stopped, and the lights are everyone's.
- **How it's built:** the tunnel is its own small space (a "between" scene
next to the worldlet), made from a kit of rock pieces (straight, turn,
slope down, the cave mouth) laid out from the seed, with box-shaped
colliders. The heightmap can't have holes or overhangs, so the passage is
never cut into the terrain itself: you walk into the seam and you're in
the tunnel scene.
### 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:** there's one clock for every land (see the decisions below). A
worldlet you're not in doesn't run, but catches up on the time when you
come back, as after a night's sleep; nothing roams it meanwhile, so a base
you left isn't 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. Built in J3, after single-player travel
works.
- **A server per land (Paul, Nov 2026)**, rather than one process holding
several worldlets (which would mean making every network module, the
saves and the actions handle several worlds at once):
- the Valley's server is the **hub**: the address everyone joins. It
starts a server for another land (the next UDP port: 24243 the Cove,
and so on) when someone first goes there, and stops it a couple of
minutes after its last player leaves;
- each land server is today's server, with its own worldlet, save file,
weather and creatures; it keeps a private link to the hub on the host
machine;
- characters live in one shared folder. A crossing saves your character
first, marks you as on your way, and hands your game the land's port
and a one-time pass; your game reconnects there ("On to the Cove...").
Nothing can be lost or copied on the way: crash mid-way and you come
back where you were going. Joining the hub when your character's in
another land sends you straight on there;
- chat, the player list (with where each player is) and admin commands
go through the hub to every land;
- the tunnel belongs to the Valley's server; the Cove's asks the hub
whether it's dug through;
- from the internet, friends need those extra ports forwarded too (on a
home network, nothing to do). The Host page will list them;
- the Old Stag and other bosses scale with the players in their land.
- Phases: **J3a** the tunnel online, land servers on demand, the
crossing and the shared characters; **J3b** chat, the player list and
admin across lands; **J3c** the awkward cases (a land server crashing,
rejoining mid-crossing, the Host page's ports).
- **One clock for every land (Paul, Nov 2026).** The day and hour are the
same everywhere, so the lands never drift apart:
- sleeping until morning needs **everyone, in every land**, in bed (on a
server the hub counts them all, and every land skips to morning
together);
- a land nobody is in doesn't run, but when someone arrives it catches up
on the time since anyone was last there, as after a night's sleep:
kilns and smelters work, fires burn down, food goes off, trees and
berries grow back. (Built for single-player already: `world.catch_up()`.)
No creatures roam it meanwhile, so a base you left isn't raided.
- **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).
- **Two ways to play (Paul, Nov 2026).**
- **The journey** (the standard game), single-player and multiplayer
alike: every new world starts in the Valley and the worldlets come in
their fixed order (Valley, Cove, Highlands, Marsh, Ember). Each
worldlet's shape is set by its definition; the seed only changes the
detail (hills, trees, where the den is), so the journey is the same
adventure for everyone.
- **One land** (a hosting choice): no journey, no crossings, the server
is locked to one worldlet, and the host picks which, from all of them.
Co-op or PvP as now (the rules preset). In code: a `mode` in
`server.json` ("journey" / "one_land"); in one-land mode the Host
page's "Where" lists every worldlet rather than only the starts, and
the crossings (`world.crossings()`) answer none, so a tunnel seam
can't be dug. Kept, like the seed and the worldlet, once the island
exists. Worth building once the Cove exists (until then there's only
the Valley to pick).
### 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 aren't running and cost nothing (each starts its own server when someone goes there).
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?