1# Tidewater (world bible)23Pack: [`mods/worlds/tidewater.cjs`](../../mods/worlds/tidewater.cjs), id `tidewater`, 7 levels.45World identity is declared in `manifest.world`. `loadMod` accepts the field6today without validating it (see F-CW8):78```js9world: { glyph: '🌊', tagline: 'The sea keeps whatever you give it.', accent: 'sink' }10```1112## Theme1314A coastline at changing tide. The water swallows whatever walks or is pushed15into it: rocks, words, and cats. Jellies sting whatever is YOU. A crab lives16on the island you can't reach. Out past the waves, the last boat is waiting.1718The emotional line of the world is **giving things to the sea**. First you19turn the tide off. Then you feed it a sentence you don't need. Then a20sentence that was hurting you. Then you stop being the cat and become the21crab. Then you turn the stingers into prizes. Then you let one crab go so the22others can cross. Finally you turn the jellies into more of yourself and walk23the whole family into the waves so that one cat reaches the boat.2425| Noun | Glyph | Role |26| --- | --- | --- |27| `MIAU` | 🐈 (`faces: 'left'`, profile) | The player |28| `WATER` | 🌊 | The sea (`SINK`). One cell of water swallows one thing, and is used up doing it |29| `JELLY` | 🪼 | The hazard (`DEFEAT`), and later the raw material for transformation |30| `CRAB` | 🦀 (plain, drawn face-on) | The other identity. It lives where the cat can't go |31| `SHELL` | 🐚 | The goal in most levels (`WIN`) |32| `BOAT` | ⛵ (plain, no clear profile) | The goal of the finale (`WIN`) |33| `ROCK` | 🪨 | A barrier (`STOP`) whose own sentence becomes bridge material |34| `REEF` | 🪸 | Declared for future levels; unused in 1.0.0 |3536I checked the glyphs on Apple Color Emoji against `#0F0E0D` at 58px. All of37them read. 🪼 is dark blue and has the lowest contrast of the set, but it's38still legible. 🪼 and 🪸 are newer emoji (Unicode 15 and 14), so older39Android and Windows 10 devices will show tofu (F-TW6). Only the cat declares40`faces`. 🦀 is drawn face-on, and ⛵ has no reliable heading across fonts.4142## Mechanic4344Core vocabulary only. The world is about **hazards and identity**, and three45engine facts carry it:46471. **SINK takes everything in the cell, including the water.** A pushed rock,48 a pushed word, or a walking YOU all vanish together with one water cell.49 Every fill costs something. Words are the most common fill, so the world is50 really about *which sentence can you spare*.512. **DEFEAT only removes YOU things.** Words sit on jellies safely, and a52 transformed jelly stops stinging.533. **Identity is a sentence.** `CRAB IS YOU` moves control across the water.54 `JELLY IS SHELL` turns hazards into goals. `JELLY IS MIAU` turns hazards55 into more of you, and several YOUs in a line fill water one per turn.5657What the engine can't express is the brief's original "become a BOAT to cross58what MIAU cannot". There's no FLOAT, and SINK doesn't care what you are. The59world uses "become whoever is already on the other side" instead (F-TW2).6061## Levels6263Solver: `node tools/solve.mjs mods/worlds/tidewater.cjs --max 2000000`. All 764levels are SOLVED, with no BYPASS and no TRIVIAL. The full pass takes about6590s, most of it on `last-boat-out`, with a peak RSS of 1.7 GB. I also used a66compact port of the turn rules. It's differentially tested against67`src/engine.js` (57,579 random steps compared, 0 mismatches), and every path68it finds is replayed on the real engine. I used it to check that the intended69edit is *necessary*:7071- `paper-bridge` and `sink-the-sting` are UNSOLVED if no word may sink.72- `sideways` is UNSOLVED without `CRAB IS YOU`.73- `one-for-the-tide` is UNSOLVED if no crab or cat may be lost.74- `last-boat-out` is UNSOLVED without `JELLY IS MIAU` (147,349 states75 exhausted).76- `shell-game` declares `mustForm: ['JELLY IS SHELL']`, and the committed CLI77 proves it: `node tools/solve.mjs mods/worlds/tidewater.cjs --level78 shell-game --forbid 'JELLY IS SHELL' --max 3000000` returns UNSOLVED after79 623,612 states (F-DS1).8081| # | id | Title | Category | Board | Teaches | Solver steps | Flags | Intended insight |82| --- | --- | --- | --- | --- | --- | --- | --- | --- |83| 1 | `low-tide` | Low tide | required | 9×7 | `WATER`, `SINK`, `SHELL` | 10 `DDUURRRRRR` | none | A wall of water crosses the whole board. It only swallows things because `WATER IS SINK` says so. Knock the sentence apart and walk through the sea. |84| 2 | `paper-bridge` | Paper bridge | required | 10×7 | `ROCK`, `STOP`, words sink | 9 `DRRRRRRUR` | none | The cat is penned in by rocks, and `WATER IS SINK` is out of reach across the water. Breaking `ROCK IS STOP` frees you, and the loose words from that sentence are exactly what the sea will take as a bridge. |85| 3 | `sink-the-sting` | Sink the sting | required | 11×7 | `JELLY`, `DEFEAT` | 13 `DDRRRRRRUUURR` | none | The shell is ringed by jellies across the water. The only spare words are `JELLY IS DEFEAT`, and pushing that sentence into the sea does two jobs in one push: it bridges the water and removes the sting. |86| 4 | `sideways` | Sideways | required | 11×7 | `CRAB`, identity swap | 11 `DLLLUUUDDDR` | none | Two waves are too wide to bridge with the one spare word. Push `CRAB` up into `MIAU IS YOU`, and the crab on the island walks to the shell. |87| 5 | `shell-game` | Shell game | required | 5×7 | noun-to-noun transformation | 11 `UURDRUURULU` | none; `mustForm: ['JELLY IS SHELL']`, forbid search UNSOLVED (623,612 states) | There are no shells on the board, and both `MIAU IS YOU` and `SHELL IS WIN` are pinned in the right-edge column. `JELLY` and `IS` stand in a column between jellies. Steer the loose `SHELL` round to the right of `DEFEAT` and push it in from the side, so `JELLY IS SHELL` reads downwards: the stingers become the prize. |88| 6 | `one-for-the-tide` | One for the tide | optional | 11×7 | several YOUs, sacrifice | 14 `DLUUUURRRRRRRR` | none | Three crabs in a row. After `CRAB IS YOU` they all march together: the first crab fills the first wave, the second crab fills the second, and the third arrives. |89| 7 | `last-boat-out` | Last boat out | challenge | 10×7 | a noun as the predicate of a vertical sentence | 26 `UULLDRDLLRRUULUUUDDRRRRRRR` | none | Three waves and one cat. `MIAU IS YOU` is locked in the bottom-left corner, but its `MIAU` can also end a vertical sentence. Drop the `IS` from `JELLY IS DEFEAT` into the gap between `JELLY` and `MIAU` to read `JELLY IS MIAU` downwards. Both jellies become cats. Line the three cats up against the top edge, feed `DEFEAT` and two cats to the sea, and the last cat takes the boat. |9091### Why the challenge is non-obvious9293- The rule edit reuses a word that already works in another direction. `MIAU`94 is the subject of the locked horizontal `MIAU IS YOU` and becomes the95 predicate of the vertical `JELLY IS MIAU`. `JELLY` never moves; only the96 `IS` does.97- The count is tight. After the edit, the only sinkable word is `DEFEAT`98 (`JELLY` and the moved `IS` are pinned to column 0, and `MIAU IS YOU` is99 locked). With three waves, two cats have to go into the sea.100- Alignment is the second half. The three cats are in three rows and move in101 lockstep. The top edge of the board is the tool that lines them up in one102 row, in the order they'll fall.103104## Design notes105106- **Pinning rules.** A sentence is fixed in two ways: it's flush in a corner107 (`MIAU IS YOU` in the bottom-left of `last-boat-out`), it runs down the108 board edge flush into a corner (both sentences in `shell-game`), or it's109 flanked by jellies the cat can't stand on (`JELLY IS` in `shell-game`), or it's across the water (every `WATER IS SINK`).110 Across-the-water pinning reads naturally in this world ("the rule for the111 sea is on the far shore"), so it costs no extra rows.112- **Word economy is the difficulty dial.** In every water level, count the113 words that are spare (not needed for `YOU` or `WIN`, and not pinned) and114 compare that with the width of the water. `sideways` has two waves and one115 spare word, so the level forces the identity swap.116- **Map helper.** The pack draws levels as text maps (`map(id, rows, meta)`)117 with a legend of one-letter things and uppercase words. Ids come from118 position (`<level>-<x>-<y>`), with the known caveat in F-CW14.119- **Blank-row spacing.** This is honoured everywhere except the vertical120 far-shore sentences in `last-boat-out`, which sit in their own column.121- **Hints** name an observation, never a sequence: "The sea is not fussy about122 what you feed it."; "There is no shell on this beach. Yet."; "Three waves123 and one cat. Try reading MIAU from above."124125## Surprising engine behaviour (with minimal reproductions)126127All of these were checked on `src/engine.js`. The scripts are in128`/tmp/tw/repro.mjs` and `/tmp/tw/repro2.mjs` (scratch).1291301. **Ghost rule after a word sinks (F-TW1).** Rules are parsed *before*131 contacts resolve, and they aren't re-parsed after removals. If a sentence is132 still whole on the turn one of its words sinks, the rule stays in133 `state.rules`, and it keeps working for the next move. A blocked move134 doesn't refresh rules either, so the ghost lasts until some move is135 accepted.136137 ```js138 // 6x3. Words: MIAU IS YOU (row 0, x 0-2), WATER IS SINK (row 0, x 3-5),139 // ROCK IS STOP (row 1, x 1-3). Things: MIAU (0,1), WATER (4,1), ROCK (1,2).140 g.step(1, 0); // pushes ROCK IS STOP right; STOP enters the water and sinks141 // state.rules still contains 'ROCK IS STOP'142 g.step(0, 1); // MIAU steps down into the rock: rejected, reason 'stop'143 ```1441452. **Transformation resolves before contact.** In one step you can push the146 last word of `JELLY IS SHELL` into place and walk onto a jelly. The jelly is147 a shell before DEFEAT is checked, so you survive (and win if148 `SHELL IS WIN`), even though `JELLY IS DEFEAT` is still on the board.1493. **Transformation chains advance one link per accepted move.** With150 `ROCK IS SHELL` and `SHELL IS JELLY`, the rock is a SHELL at reset and151 becomes a JELLY after the next step (also reported in F-XW6).1524. **SINK and DEFEAT both beat WIN in the same cell.** `SHELL IS WIN AND SINK`:153 walking onto the shell removes both and doesn't win. `JELLY IS WIN` while154 `JELLY IS DEFEAT` is active: touching the jelly kills you first, which is a155 nice trap in `last-boat-out`.1565. **Nothing can start on water.** Reset resolves contacts, so a boat or jelly157 placed on a `WATER IS SINK` cell is gone before the first frame, with no158 warning (F-TW3). "Moored" and "floating" things can't be authored.1596. **Lockstep YOUs fill water in order.** The front mover is planned first. A160 line of YOUs walking into water loses one per wave, and the rest walk on161 over the filled cells. `controlLost` only fires when the last YOU is gone.1627. **A mod palette entry for a built-in noun used to turn off its facing.**163 `MIAU: '🐈'` as a plain string never flipped (F-TW4). The working-tree164 `src/render/facing.js` now inherits the built-in facing when the glyph165 matches. Tidewater declares `{ glyph: '🐈', faces: 'left' }` anyway.166167## Crumbling tiles (proposal)168169The engine team asked for concrete desired interactions for **tiles that170crumble after one step**. Tidewater is the natural home for them: driftwood171that sinks after you cross, and sandbars the tide takes back. Everything172below is **provisional**. The drafts are not in the `.cjs`, and the user may173define different semantics.174175### The design default, and one challenge to it176177Design's default is: a rule-driven property (`DRIFT IS FRAIL`); the tile178crumbles when its occupant leaves; any mover counts; it leaves an empty cell;179a level can place `WATER` under it to make the hole deadly; the counter is 1180only.181182I agree with all of it **except "place WATER under it"**. It can't work in183the current contact model. A cell holding `DRIFT` and `WATER` while184`WATER IS SINK` is active is a two-occupant cell with a SINK thing, so reset185destroys both before the first frame (behaviour 5 above). Making it work would186need a floor layer, meaning FRAIL things shield their cell from contact. That187adds precedence rules every other property would have to respect.188189**Counter-proposal: a crumble *becomes* its leftover.** Each frail object may190carry `leaves: '<NOUN>'` in level data. When it crumbles, it is transformed191into that noun, keeping its id. With no `leaves`, it's removed. This reuses192the transform event and tween. The deadly hole is simply `leaves: 'WATER'`,193and it's only as deadly as `WATER IS SINK` currently is, so players can still194reason about it with sentences.195196### Semantics, decided by the scenarios197198| Question | Decision | Why (scenario) |199| --- | --- | --- |200| On step on, or step off? | **Step off.** Standing is safe. | Draft A: standing on the last plank while pushing a word ahead is the core move. Crumbling on entry makes every frail tile a SINK in disguise. |201| When in the turn? | **At end of movement, before contacts:** move, rules, transform, crumble, contacts. A frail tile crumbles if a non-frail thing stood on it at the start of the turn and nothing stands on it now. | Draft A: in a push, the pushed word leaves plank 1 in the same step that the cat enters it. Evaluating per mover would drop the cat. End-of-turn evaluation makes a **handover** safe. |202| YOU only, or any mover? | **Any mover** (things and words). | Draft A needs a word to cross. Draft B uses a pushed rock to spend a sandbar deliberately. Counting only YOU would make pushed things weightless, which contradicts SINK, where pushed things do count. |203| What's left behind? | **Nothing by default. `leaves` makes it a noun** (usually `WATER`). | See above. Both drafts use `leaves: 'WATER'`. |204| Rule or level data? | **A rule: `DRIFT IS FRAIL`.** The leftover is level data. | Draft A's alternative solution is breaking `DRIFT IS FRAIL` so the bridge stays. That's the Baba answer, and it only exists if crumbling is a sentence. |205| Counter above 1? | **Not as a counter. Express it as a leftover chain.** | Draft B: `SAND` leaves `SHOAL`, and `SHOAL` leaves `WATER`, which gives two crossings. The remaining count is readable as a *different glyph* (🟨 then 🟫), because design forbids decorations on sprites (no crack pips or `×N` on a tile). It also needs no new state. |206207### Undo, reset, sharing, and the solver208209- With the leftover-chain design, **all crumble state is the noun**, which the210 snapshot, undo, reset, sharing, and `tools/solve.mjs` (`keyOf` hashes id,211 x, y, and noun) already cover.212- If the engine ever adds a numeric counter, it has to live on the object213 record in `state.objects` so that `structuredClone` history carries it.214 `reset()` must rebuild it from level data (today it drops unknown fields;215 see F-CW17), and the solver key must include it. Otherwise BFS merges216 distinct states and reports false UNSOLVED or wrong shortest paths.217- Shared packs need `leaves` validated by `loadMod` (it must be a noun token)218 and preserved by `exportPack`.219220### Draft A: "One plank each" (DRAFT, not in the pack)221222`d` is `DRIFT` with `leaves: 'WATER'`. `DRIFT IS FRAIL` is new vocabulary.223224```js225// 11x7. The shell is across three planks. WIN is on the near shore and must226// cross too.227[228 'MIAU IS YOU . . w w w . . .',229 '. . . . . w w w . . .',230 '. m . WIN . d d d . s .',231 '. . . . . w w w . . .',232 'DRIFT IS FRAIL . . w w w SHELL IS .',233 '. . . . . w w w . . .',234 '. . . . . w w w WATER IS SINK',235]236```237238Intended solution: push `WIN` right across the planks. Each plank that `WIN`239leaves is entered by the cat in the same step, which is a handover, so the240planks hold. Then the cat steps off plank 3, it becomes water, and the bridge241is gone behind you. Carry `WIN` into `SHELL IS _` from above and touch the242shell. Alternative: break `DRIFT IS FRAIL` first, and the planks stay. This243tests step-off, end-of-turn evaluation, any mover counting, and the rule form.244245### Draft B: "Two tides" (DRAFT, not in the pack)246247`a` is `SAND` with `leaves: 'SHOAL'`, and `h` is `SHOAL` with248`leaves: 'WATER'`. The rule is `SAND AND SHOAL IS FRAIL`.249250```js251// 12x7. The IS you need is on the island. You go out and come back, so each252// sandbar is crossed twice.253[254 'MIAU IS YOU . . w w w . . . .',255 '. . . . . w w w . . . .',256 '. m . . . a a a . IS . .',257 '. . r . . w w w . . . .',258 'SAND AND SHOAL IS FRAIL w w w . . .',259 '. . . . . w w w . . . .',260 'SHELL . WIN . s w w w WATER IS SINK .',261]262```263264Out: sand becomes shoal. Back, pushing `IS` ahead: shoal becomes water behind265you. You can't go out a third time. The trap: if you push the rock `r` across266first "to test the bridge", it spends one crossing (any mover counts), and267you're stranded on the island. The glyph change from sand to shoal is the268visible counter.269270### Open questions for the engine2712721. Does a frail tile crumble when it's *transformed away* (for example273 `DRIFT IS ROCK`)? I suggest no: only departures count.2742. Does a word standing on a plank count as an occupant for handover? I275 suggest yes, for consistency with "any mover".2763. `FRAIL` on a YOU thing (`MIAU IS FRAIL`): I suggest it has no effect,277 because FRAIL only triggers on things that are left, not on the mover.278279### Regression fixtures (D25 semantics)280281D25 accepted FRAIL and, as revised, makes leftovers a property of the noun.282Three data-only fixtures for the upcoming engine implementation are in283[`docs/worlds/fixtures/frail.json`](fixtures/frail.json). Each entry has284`id`, `title`, `level` (explicit stable object ids and a `mechanics`285declaration), `expectedRoute`, `steps`, `observations`, `exercises`, `notes`,286and `proposedFields`. The engine currently rejects `mechanics`, so the287fixtures are data only and aren't in the pack.288289| id | Route | Leftover map | Exercises |290| --- | --- | --- | --- |291| `one-plank-each` (Draft A) | 14 `RRRRRRRRURDDUL` | `{ DRIFT: 'WATER' }` | Step-off crumbles on turns 5, 6, and 7. Handovers on turns 4, 5, and 6: the pushed word `WIN` leaves a plank while the cat enters it. DRIFT becomes WATER with the same id. |292| `two-tides` (Draft B, no rock) | 30 `RRRRRRRURRDLLLLLLLLDLUUUDDDDRR` | `{ SAND: 'SHOAL', SHOAL: 'WATER' }` | Two-link chain on the same id: SAND becomes SHOAL on turns 5 to 7, then SHOAL becomes WATER on turns 15 to 17. Handovers while pushing `IS` back on turns 14 to 16. `SAND AND SHOAL IS FRAIL`. |293| `log-walks-off` (new, minimal) | 8 `RRRRRRRR` | `{}` | A FRAIL mover never crumbles itself: `LOG` is FRAIL and YOU, and it moves every turn. A different FRAIL mover triggers a crumble: `LOG` leaving the SAND it shared at reset. An unmapped noun is removed (`to: null`). |294295**Declaration (D25 revised).** Each level declares296`mechanics: [{ id: 'frail', type: 'frail', version: 1, state: 'derived', leaves: { <NOUN>: <NOUN> } }]`.297On a crumble, the thing becomes `leaves[currentNoun]` and keeps its id. A noun298missing from the map is removed. There's no per-object `leaves` field. Because299the lookup uses the current noun, a chain like SAND, then SHOAL, then WATER300needs no counter. This resolves F-TW11. Moving from my per-object map to the301per-level map changed no route and no observation: I re-ran all three302fixtures and they matched exactly.303304**How the routes were derived.** I used a scratch simulator305(`/tmp/tw/frail.mjs`). It uses the engine's own parser306(`src/engine/rules.js`) plus a port of the current movement, transform, and307contact code. The crumble checkpoint runs after movement and transforms and308before contacts. A frail thing crumbles only if it:309- was FRAIL at the start of the turn and is FRAIL at the checkpoint,310- didn't move,311- had a distinct starting occupant that moved away,312- and is alone at the checkpoint.313314With crumbling disabled, the simulator matches `src/engine.js` on 33,645315random steps over the three fixture boards. For that comparison, `FRAIL` is316registered as a property through a wait-only automaton. Each route is the317first shortest route BFS finds, trying U, D, L, R in that order. Other routes318of the same length may exist, so compare route *length* and replay the319observations.