Tidewater (world bible)
Pack: mods/, id tidewater, 7 levels.
World identity is declared in manifest.world. loadMod accepts the field
today without validating it (see F-CW8):
world: { glyph: '🌊', tagline: 'The sea keeps whatever you give it.', accent: 'sink' }
Theme
A coastline at changing tide. The water swallows whatever walks or is pushed into it: rocks, words, and cats. Jellies sting whatever is YOU. A crab lives on the island you can't reach. Out past the waves, the last boat is waiting.
The emotional line of the world is giving things to the sea. First you turn the tide off. Then you feed it a sentence you don't need. Then a sentence that was hurting you. Then you stop being the cat and become the crab. Then you turn the stingers into prizes. Then you let one crab go so the others can cross. Finally you turn the jellies into more of yourself and walk the whole family into the waves so that one cat reaches the boat.
| Noun | Glyph | Role |
|---|---|---|
MIAU |
🐈 (faces: 'left', profile) |
The player |
WATER |
🌊 | The sea (SINK). One cell of water swallows one thing, and is used up doing it |
JELLY |
🪼 | The hazard (DEFEAT), and later the raw material for transformation |
CRAB |
🦀 (plain, drawn face-on) | The other identity. It lives where the cat can't go |
SHELL |
🐚 | The goal in most levels (WIN) |
BOAT |
⛵ (plain, no clear profile) | The goal of the finale (WIN) |
ROCK |
🪨 | A barrier (STOP) whose own sentence becomes bridge material |
REEF |
🪸 | Declared for future levels; unused in 1.0.0 |
I checked the glyphs on Apple Color Emoji against #0F0E0D at 58px. All of
them read. 🪼 is dark blue and has the lowest contrast of the set, but it's
still legible. 🪼 and 🪸 are newer emoji (Unicode 15 and 14), so older
Android and Windows 10 devices will show tofu (F-TW6). Only the cat declares
faces. 🦀 is drawn face-on, and ⛵ has no reliable heading across fonts.
Mechanic
Core vocabulary only. The world is about hazards and identity, and three engine facts carry it:
- SINK takes everything in the cell, including the water. A pushed rock, a pushed word, or a walking YOU all vanish together with one water cell. Every fill costs something. Words are the most common fill, so the world is really about which sentence can you spare.
- DEFEAT only removes YOU things. Words sit on jellies safely, and a transformed jelly stops stinging.
- Identity is a sentence.
CRAB IS YOUmoves control across the water.JELLY IS SHELLturns hazards into goals.JELLY IS MIAUturns hazards into more of you, and several YOUs in a line fill water one per turn.
What the engine can't express is the brief's original "become a BOAT to cross what MIAU cannot". There's no FLOAT, and SINK doesn't care what you are. The world uses "become whoever is already on the other side" instead (F-TW2).
Levels
Solver: node tools/. All 7
levels are SOLVED, with no BYPASS and no TRIVIAL. The full pass takes about
90s, most of it on last-boat-out, with a peak RSS of 1.7 GB. I also used a
compact port of the turn rules. It's differentially tested against
src/ (57,579 random steps compared, 0 mismatches), and every path
it finds is replayed on the real engine. I used it to check that the intended
edit is necessary:
paper-bridgeandsink-the-stingare UNSOLVED if no word may sink.sidewaysis UNSOLVED withoutCRAB IS YOU.one-for-the-tideis UNSOLVED if no crab or cat may be lost.last-boat-outis UNSOLVED withoutJELLY IS MIAU(147,349 states exhausted).shell-gamedeclaresmustForm: ['JELLY IS SHELL'], and the committed CLI proves it:node tools/returns UNSOLVED after 623,612 states (F-DS1).solve.mjs mods/ worlds/ tidewater.cjs --level shell-game --forbid 'JELLY IS SHELL' --max 3000000
| # | id | Title | Category | Board | Teaches | Solver steps | Flags | Intended insight |
|---|---|---|---|---|---|---|---|---|
| 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. |
| 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. |
| 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. |
| 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. |
| 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. |
| 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. |
| 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. |
Why the challenge is non-obvious
- The rule edit reuses a word that already works in another direction.
MIAUis the subject of the locked horizontalMIAU IS YOUand becomes the predicate of the verticalJELLY IS MIAU.JELLYnever moves; only theISdoes. - The count is tight. After the edit, the only sinkable word is
DEFEAT(JELLYand the movedISare pinned to column 0, andMIAU IS YOUis locked). With three waves, two cats have to go into the sea. - Alignment is the second half. The three cats are in three rows and move in lockstep. The top edge of the board is the tool that lines them up in one row, in the order they'll fall.
Design notes
- Pinning rules. A sentence is fixed in two ways: it's flush in a corner
(
MIAU IS YOUin the bottom-left oflast-boat-out), it runs down the board edge flush into a corner (both sentences inshell-game), or it's flanked by jellies the cat can't stand on (JELLY ISinshell-game), or it's across the water (everyWATER IS SINK). Across-the-water pinning reads naturally in this world ("the rule for the sea is on the far shore"), so it costs no extra rows. - Word economy is the difficulty dial. In every water level, count the
words that are spare (not needed for
YOUorWIN, and not pinned) and compare that with the width of the water.sidewayshas two waves and one spare word, so the level forces the identity swap. - Map helper. The pack draws levels as text maps (
map(id, rows, meta)) with a legend of one-letter things and uppercase words. Ids come from position (<level>-<x>-<y>), with the known caveat in F-CW14. - Blank-row spacing. This is honoured everywhere except the vertical
far-shore sentences in
last-boat-out, which sit in their own column. - Hints name an observation, never a sequence: "The sea is not fussy about what you feed it."; "There is no shell on this beach. Yet."; "Three waves and one cat. Try reading MIAU from above."
Surprising engine behaviour (with minimal reproductions)
All of these were checked on src/. The scripts are in
/ and / (scratch).
-
Ghost rule after a word sinks (F-TW1). Rules are parsed before contacts resolve, and they aren't re-parsed after removals. If a sentence is still whole on the turn one of its words sinks, the rule stays in
state.rules, and it keeps working for the next move. A blocked move doesn't refresh rules either, so the ghost lasts until some move is accepted.// 6x3. Words: MIAU IS YOU (row 0, x 0-2), WATER IS SINK (row 0, x 3-5), // ROCK IS STOP (row 1, x 1-3). Things: MIAU (0,1), WATER (4,1), ROCK (1,2). g.step(1, 0); // pushes ROCK IS STOP right; STOP enters the water and sinks // state.rules still contains 'ROCK IS STOP' g.step(0, 1); // MIAU steps down into the rock: rejected, reason 'stop' -
Transformation resolves before contact. In one step you can push the last word of
JELLY IS SHELLinto place and walk onto a jelly. The jelly is a shell before DEFEAT is checked, so you survive (and win ifSHELL IS WIN), even thoughJELLY IS DEFEATis still on the board. -
Transformation chains advance one link per accepted move. With
ROCK IS SHELLandSHELL IS JELLY, the rock is a SHELL at reset and becomes a JELLY after the next step (also reported in F-XW6). -
SINK and DEFEAT both beat WIN in the same cell.
SHELL IS WIN AND SINK: walking onto the shell removes both and doesn't win.JELLY IS WINwhileJELLY IS DEFEATis active: touching the jelly kills you first, which is a nice trap inlast-boat-out. -
Nothing can start on water. Reset resolves contacts, so a boat or jelly placed on a
WATER IS SINKcell is gone before the first frame, with no warning (F-TW3). "Moored" and "floating" things can't be authored. -
Lockstep YOUs fill water in order. The front mover is planned first. A line of YOUs walking into water loses one per wave, and the rest walk on over the filled cells.
controlLostonly fires when the last YOU is gone. -
A mod palette entry for a built-in noun used to turn off its facing.
MIAU: '🐈'as a plain string never flipped (F-TW4). The working-treesrc/now inherits the built-in facing when the glyph matches. Tidewater declaresrender/ facing.js { glyph: '🐈', faces: 'left' }anyway.
Crumbling tiles (proposal)
The engine team asked for concrete desired interactions for tiles that
crumble after one step. Tidewater is the natural home for them: driftwood
that sinks after you cross, and sandbars the tide takes back. Everything
below is provisional. The drafts are not in the .cjs, and the user may
define different semantics.
The design default, and one challenge to it
Design's default is: a rule-driven property (DRIFT IS FRAIL); the tile
crumbles when its occupant leaves; any mover counts; it leaves an empty cell;
a level can place WATER under it to make the hole deadly; the counter is 1
only.
I agree with all of it except "place WATER under it". It can't work in
the current contact model. A cell holding DRIFT and WATER while
WATER IS SINK is active is a two-occupant cell with a SINK thing, so reset
destroys both before the first frame (behaviour 5 above). Making it work would
need a floor layer, meaning FRAIL things shield their cell from contact. That
adds precedence rules every other property would have to respect.
Counter-proposal: a crumble becomes its leftover. Each frail object may
carry leaves: '<NOUN>' in level data. When it crumbles, it is transformed
into that noun, keeping its id. With no leaves, it's removed. This reuses
the transform event and tween. The deadly hole is simply leaves: 'WATER',
and it's only as deadly as WATER IS SINK currently is, so players can still
reason about it with sentences.
Semantics, decided by the scenarios
| Question | Decision | Why (scenario) |
|---|---|---|
| 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. |
| 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. |
| 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. |
| What's left behind? | Nothing by default. leaves makes it a noun (usually WATER). |
See above. Both drafts use leaves: 'WATER'. |
| 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. |
| 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. |
Undo, reset, sharing, and the solver
- With the leftover-chain design, all crumble state is the noun, which the
snapshot, undo, reset, sharing, and
tools/(solve.mjs keyOfhashes id, x, y, and noun) already cover. - If the engine ever adds a numeric counter, it has to live on the object
record in
state.objectsso thatstructuredClonehistory carries it.reset()must rebuild it from level data (today it drops unknown fields; see F-CW17), and the solver key must include it. Otherwise BFS merges distinct states and reports false UNSOLVED or wrong shortest paths. - Shared packs need
leavesvalidated byloadMod(it must be a noun token) and preserved byexportPack.
Draft A: "One plank each" (DRAFT, not in the pack)
d is DRIFT with leaves: 'WATER'. DRIFT IS FRAIL is new vocabulary.
// 11x7. The shell is across three planks. WIN is on the near shore and must
// cross too.
[
'MIAU IS YOU . . w w w . . .',
'. . . . . w w w . . .',
'. m . WIN . d d d . s .',
'. . . . . w w w . . .',
'DRIFT IS FRAIL . . w w w SHELL IS .',
'. . . . . w w w . . .',
'. . . . . w w w WATER IS SINK',
]
Intended solution: push WIN right across the planks. Each plank that WIN
leaves is entered by the cat in the same step, which is a handover, so the
planks hold. Then the cat steps off plank 3, it becomes water, and the bridge
is gone behind you. Carry WIN into SHELL IS _ from above and touch the
shell. Alternative: break DRIFT IS FRAIL first, and the planks stay. This
tests step-off, end-of-turn evaluation, any mover counting, and the rule form.
Draft B: "Two tides" (DRAFT, not in the pack)
a is SAND with leaves: 'SHOAL', and h is SHOAL with
leaves: 'WATER'. The rule is SAND AND SHOAL IS FRAIL.
// 12x7. The IS you need is on the island. You go out and come back, so each
// sandbar is crossed twice.
[
'MIAU IS YOU . . w w w . . . .',
'. . . . . w w w . . . .',
'. m . . . a a a . IS . .',
'. . r . . w w w . . . .',
'SAND AND SHOAL IS FRAIL w w w . . .',
'. . . . . w w w . . . .',
'SHELL . WIN . s w w w WATER IS SINK .',
]
Out: sand becomes shoal. Back, pushing IS ahead: shoal becomes water behind
you. You can't go out a third time. The trap: if you push the rock r across
first "to test the bridge", it spends one crossing (any mover counts), and
you're stranded on the island. The glyph change from sand to shoal is the
visible counter.
Open questions for the engine
- Does a frail tile crumble when it's transformed away (for example
DRIFT IS ROCK)? I suggest no: only departures count. - Does a word standing on a plank count as an occupant for handover? I suggest yes, for consistency with "any mover".
FRAILon a YOU thing (MIAU IS FRAIL): I suggest it has no effect, because FRAIL only triggers on things that are left, not on the mover.
Regression fixtures (D25 semantics)
D25 accepted FRAIL and, as revised, makes leftovers a property of the noun.
Three data-only fixtures for the upcoming engine implementation are in
docs/. Each entry has
id, title, level (explicit stable object ids and a mechanics
declaration), expectedRoute, steps, observations, exercises, notes,
and proposedFields. The engine currently rejects mechanics, so the
fixtures are data only and aren't in the pack.
| id | Route | Leftover map | Exercises |
|---|---|---|---|
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. |
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. |
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). |
Declaration (D25 revised). Each level declares
mechanics: [{ id: 'frail', type: 'frail', version: 1, state: 'derived', leaves: { <NOUN>: <NOUN> } }].
On a crumble, the thing becomes leaves[currentNoun] and keeps its id. A noun
missing from the map is removed. There's no per-object leaves field. Because
the lookup uses the current noun, a chain like SAND, then SHOAL, then WATER
needs no counter. This resolves F-TW11. Moving from my per-object map to the
per-level map changed no route and no observation: I re-ran all three
fixtures and they matched exactly.
How the routes were derived. I used a scratch simulator
(/). It uses the engine's own parser
(src/) plus a port of the current movement, transform, and
contact code. The crumble checkpoint runs after movement and transforms and
before contacts. A frail thing crumbles only if it:
- was FRAIL at the start of the turn and is FRAIL at the checkpoint,
- didn't move,
- had a distinct starting occupant that moved away,
- and is alone at the checkpoint.
With crumbling disabled, the simulator matches src/ on 33,645
random steps over the three fixture boards. For that comparison, FRAIL is
registered as a property through a wait-only automaton. Each route is the
first shortest route BFS finds, trying U, D, L, R in that order. Other routes
of the same length may exist, so compare route length and replay the
observations.