- commit
- 9eec40fd0df739db27c7a09ff8a6efedaa6139a1
- parent
- e947297cd1ec27a3f77ebec3fd5041537b84f037
- author
- Dami <dami.etoile@gmail.com>
| A | mods/ | 164 | +++++++++++++++++++++++++ |
| A | mods/ | 195 | ++++++++++++++++++++++++++++++ |
| A | mods/ | 183 | ++++++++++++++++++++++++++++ |
| A | mods/ | 156 | ++++++++++++++++++++++++ |
@@ -0,0 +1,164 @@
1+// Clockwork: a clock tower workshop where clockwork keeps moving between your2+// steps. The world's custom property is TICK, built on the engine's declarative3+// level.automata: `X IS TICK` makes every X repeat the level's step pattern,4+// one beat per accepted turn (a move or a wait).5+//6+// Runs on the shipped engine (manifest.engine 1). Verified with7+// `node tools/solve.mjs mods/worlds/clockwork.cjs`; see docs/worlds/clockwork.md.8+// The world's second property (OPEN and SHUT) is a proposal only; its draft9+// levels live with the prototype, not in this pack. See clockwork-api.md.10+const { word, object, level } = require('@miau/engine');11+12+// Pattern steps.13+const L = { action: 'move', dx: -1, dy: 0 };14+const R = { action: 'move', dx: 1, dy: 0 };15+const U = { action: 'move', dx: 0, dy: -1 };16+const D = { action: 'move', dx: 0, dy: 1 };17+const times = (n, step) => Array.from({ length: n }, () => step);18+19+// ASCII maps, one character per cell. Uppercase letters are word tiles,20+// lowercase letters and symbols are things, '.' is empty.21+const WORDS = {22+ M: 'MIAU', I: 'IS', Y: 'YOU', W: 'WALL', S: 'STOP', B: 'BELL', N: 'WIN',23+ O: 'MOUSE', T: 'TICK', A: 'AND', G: 'COG', X: 'DEFEAT', Q: 'SINK',24+};25+const THINGS = { c: 'MIAU', '#': 'WALL', b: 'BELL', m: 'MOUSE', g: 'COG' };26+27+function grid(id, rows, meta) {28+ const objects = [];29+ rows.forEach((row, y) => [...row].forEach((ch, x) => {30+ const tag = `${id}-${x}-${y}`;31+ if (WORDS[ch]) objects.push(word(WORDS[ch], x, y, tag));32+ else if (THINGS[ch]) objects.push(object(THINGS[ch], x, y, tag));33+ else if (ch !== '.') throw new Error(`${id}: unknown map character ${ch}`);34+ }));35+ return level(Math.max(...rows.map(r => r.length)), rows.length, objects, { id, ...meta });36+}37+38+module.exports = {39+ manifest: {40+ id: 'clockwork',41+ name: 'Clockwork',42+ version: '0.1.0',43+ engine: 1,44+ rules: 2, // authored against post-W6 rule semantics (D23)45+ author: 'Clockwork world-builder',46+ description: 'A clock tower workshop where mice keep time and cogs keep turning.',47+ world: { glyph: '⚙️', tagline: 'Some things keep walking after you stop.', accent: 'push' },48+ },49+ vocabulary: ['MIAU', 'WALL', 'BELL', 'MOUSE', 'COG'],50+ palette: {51+ MIAU: { glyph: '🐈', faces: 'left' },52+ MOUSE: { glyph: '🐁', faces: 'left' },53+ WALL: '🧱',54+ BELL: '🔔',55+ COG: '⚙️',56+ },57+ // PROPOSED glossary (clockwork-api.md, ask 6): how the stage explains an58+ // unfamiliar property tile. The shipped loader ignores this field today.59+ properties: {60+ TICK: { accent: 'push', describe: 'Repeats its steps, one for each of your turns.' },61+ },62+ levels: [63+ grid('mouse-keeps-time', [64+ 'MIY.WIS.OIT',65+ '...........',66+ '###########',67+ '#BI..N...m#',68+ '###########',69+ 'c..........',70+ '..........b',71+ ], {72+ title: 'The mouse keeps time',73+ allowBypass: true, // "just watch": the mouse moves the word, not the player74+ hint: 'The mouse takes one step for every step you take.',75+ category: 'required',76+ introduces: ['TICK', 'MOUSE'],77+ automata: [{ id: 'mouse-shuttle', subject: 'MOUSE', property: 'TICK', phase: 0, pattern: [...times(5, L), ...times(5, R)] }],78+ }),79+ grid('wind-it-up', [80+ 'MIY..WIS...',81+ '...........',82+ 'OI.........',83+ '....T......',84+ 'c.........b',85+ '###########',86+ '#BI..N...m#',87+ '###########',88+ ], {89+ title: 'Wind it up',90+ hint: 'A mouse with no rule just sits there.',91+ category: 'required',92+ introduces: ['TICK'],93+ automata: [{ id: 'mouse-shuttle', subject: 'MOUSE', property: 'TICK', phase: 0, pattern: [...times(5, L), ...times(5, R)] }],94+ }),95+ grid('ring-for-service', [96+ 'MIY...BIN',97+ '......I..',98+ '.........',99+ '.OIT.....',100+ '.c.......',101+ 'ggggggggg',102+ '#.###.###',103+ '#m###b###',104+ 'WIS###GIX',105+ ], {106+ title: 'Ring for service',107+ hint: 'You can\'t cross the cogs. The rule can.',108+ category: 'required',109+ introduces: ['COG', 'DEFEAT'],110+ automata: [{ id: 'spring', property: 'TICK', phase: 0, pattern: [U, U, U, D, D, D] }],111+ }),112+ grid('double-time', [113+ 'MIY...BIN',114+ 'I........',115+ '.........',116+ '...T.....',117+ '.c.......',118+ 'ggggggggg',119+ '.........',120+ '.b.......',121+ 'GIX......',122+ ], {123+ title: 'Double time',124+ hint: 'The cogs only bite if you stop on them.',125+ category: 'required',126+ introduces: ['TICK'],127+ automata: [{ id: 'spring', property: 'TICK', phase: 0, pattern: [D] }],128+ }),129+ grid('mouse-hole', [130+ 'MIYOITA.BIN',131+ '...........',132+ '...........',133+ '...........',134+ '......Q.m..',135+ '.c.........',136+ 'ggggggggggg',137+ '...........',138+ '.....b.....',139+ '........GIX',140+ ], {141+ title: 'Mouse hole',142+ hint: 'The mouse already walks through the cogs. Make it take one with it.',143+ category: 'required',144+ introduces: ['SINK'],145+ automata: [{ id: 'mouse-dive', subject: 'MOUSE', property: 'TICK', phase: 0, pattern: [D, D, U, U] }],146+ }),147+ grid('escapement', [148+ 'MIY.....OIT',149+ '......m....',150+ '.N.......B.',151+ 'c..........',152+ 'ggggggggggg',153+ '...........',154+ '...........',155+ 'BIN.GI..GIX',156+ ], {157+ title: 'Escapement',158+ hint: 'The obvious word only makes the cogs worse. What else could a cog be?',159+ category: 'challenge',160+ introduces: ['TICK'],161+ automata: [{ id: 'mouse-press', subject: 'MOUSE', property: 'TICK', phase: 0, pattern: [...times(6, D), ...times(6, U)] }],162+ }),163+ ],164+};@@ -0,0 +1,195 @@
1+// Crate Works: a warehouse world about Sokoban pushing under editable rules.2+//3+// Levels are drawn as ASCII maps (see `board` below) instead of hand-counted4+// coordinates. One character is one cell. World bible:5+// docs/worlds/crate-works.md6+const { word, object, level } = require('@miau/engine');7+8+// Things are symbols, rule words are uppercase letters.9+// . empty floor # WALL @ MIAU $ CRATE10+// & TRUCK ~ GAP11+// M MIAU C CRATE W WALL T TRUCK G GAP12+// I IS A AND Y YOU P PUSH S STOP N WIN K SINK13+const LEGEND = {14+ '.': [],15+ '#': ['obj:WALL'], '@': ['obj:MIAU'], '$': ['obj:CRATE'], '&': ['obj:TRUCK'], '~': ['obj:GAP'],16+ M: ['MIAU'], C: ['CRATE'], W: ['WALL'], T: ['TRUCK'], G: ['GAP'],17+ I: ['IS'], A: ['AND'], Y: ['YOU'], P: ['PUSH'], S: ['STOP'], N: ['WIN'], K: ['SINK'],18+};19+20+// board(rows, meta) turns equal-length strings into a level. Object ids are21+// derived from the level id and the starting cell, so they stay stable as22+// long as the map does.23+function board(rows, meta) {24+ const width = rows[0].length;25+ const objects = [];26+ rows.forEach((row, y) => {27+ if (row.length !== width) throw new Error(`${meta.id}: row ${y} is ${row.length} wide, expected ${width}`);28+ [...row].forEach((ch, x) => {29+ const specs = LEGEND[ch];30+ if (!specs) throw new Error(`${meta.id}: unknown map character '${ch}' at column ${x}, row ${y}`);31+ specs.forEach((spec, n) => {32+ const id = `${meta.id}-${x}-${y}${n ? `-${n}` : ''}`;33+ objects.push(spec.startsWith('obj:') ? object(spec.slice(4), x, y, id) : word(spec, x, y, id));34+ });35+ });36+ });37+ return level(width, rows.length, objects, meta);38+}39+40+module.exports = {41+ manifest: {42+ id: 'crate-works',43+ name: 'Crate Works',44+ version: '0.1.0',45+ engine: 1,46+ rules: 2, // authored against post-W6 rule semantics (D23)47+ author: 'Crate Works world team',48+ description: 'A loading dock where crates, gaps, trucks, and the rules that hold them can all be shoved.',49+ world: { glyph: '📦', tagline: 'Every crate goes somewhere. So does every word.', accent: 'push' },50+ },51+ vocabulary: ['MIAU', 'CRATE', 'TRUCK', 'WALL', 'GAP'],52+ palette: {53+ MIAU: { glyph: '🐈', faces: 'left' },54+ CRATE: '📦',55+ TRUCK: '🚚',56+ WALL: '🧱',57+ GAP: '🚧',58+ },59+ levels: [60+ // 1. A crate blocks the only doorway; WIN waits on the far side.61+ board([62+ 'MIY..WIS..CIP',63+ '#############',64+ '.....#.......',65+ '.@..$....N...',66+ '.....#.......',67+ '.....#..TI..&',68+ '.....#.......',69+ ], {70+ id: 'crates-move',71+ title: 'Crates move',72+ hint: 'The crate is in the doorway. TRUCK IS is waiting for a word.',73+ category: 'required',74+ introduces: ['CRATE', 'TRUCK', 'PUSH'],75+ }),76+77+ // 2. Every rule sits across the gap line. Sink the crate to make a bridge.78+ board([79+ '......~MIY..',80+ '......~.....',81+ '.@....~CIP..',82+ '....$.~.....',83+ '......~GIK..',84+ '......~....&',85+ '......~TI.N.',86+ ], {87+ id: 'fill-the-gap',88+ title: 'Fill the gap',89+ hint: 'A gap swallows whatever goes in, and closes behind it.',90+ category: 'required',91+ introduces: ['GAP', 'SINK'],92+ }),93+94+ // 3. Two gaps deep, one crate, and one loose word: PUSH. The crate has to95+ // go in while it is still pushable; PUSH goes in last.96+ board([97+ 'MIY#WIS#GIK#C#',98+ '...~~......#I#',99+ '&..~~.......P.',100+ '...~~...$.....',101+ 'TIN~~.......@.',102+ ], {103+ id: 'spend-it-last',104+ title: 'Spend it last',105+ hint: 'Words sink too, and take their rule with them. Pick the order.',106+ category: 'required',107+ introduces: ['sinking words'],108+ reuses: ['GAP', 'SINK', 'PUSH'],109+ }),110+111+ // 4. CRATE AND MIAU IS YOU. The crate climbs its lane while the cat must112+ // keep clear of the sentence; walls hold one still while the other moves.113+ board([114+ '#....#####&',115+ '..MIY#####.',116+ '.....###...',117+ '.A...###.##',118+ 'C....###...',119+ '.....#####.',120+ '..@..#$....',121+ '###########',122+ 'WIS..TIN...',123+ ], {124+ id: 'in-step',125+ mustForm: ['CRATE IS YOU'],126+ title: 'In step',127+ hint: 'Two things can answer to YOU. A wall holds one still while the other moves.',128+ category: 'required',129+ introduces: ['AND', 'shared YOU'],130+ }),131+132+ // 5. A tiny storeroom: two crates, two gaps, and the WIN word in front of133+ // them. Reorder the load so the word crosses last.134+ board([135+ 'MIY.GIK.CIP.',136+ '###.....#...',137+ 'WIS#####T###',138+ '#....###I###',139+ '#..$N~~...##',140+ '#@.$.##&####',141+ '#....#######',142+ ], {143+ id: 'load-order',144+ title: 'Load order',145+ hint: 'WIN has to cross too. Nothing crosses a gap until something fills it.',146+ category: 'optional',147+ introduces: ['push order'],148+ reuses: ['GAP', 'SINK', 'PUSH'],149+ }),150+151+ // 6. TRUCK IS PUSH AND WIN: you can never stand on it. Push it onto a152+ // crate, then make the crate YOU (or the other way round).153+ board([154+ 'WIS.MIY.TIPAN',155+ '#############',156+ '#....#......#',157+ '#.&..#.@C...#',158+ '#..#.....#..#',159+ '##.#######.##',160+ '#$.........IY',161+ '#############',162+ ], {163+ id: 'back-it-up',164+ mustForm: ['CRATE IS YOU'],165+ title: 'Back it up',166+ hint: 'The truck rolls away from you. It would not roll away from something standing still.',167+ category: 'optional',168+ introduces: ['pushable WIN'],169+ reuses: ['PUSH', 'shared YOU'],170+ }),171+172+ // 7. The truck sits behind a moat two gaps thick and there is one loose173+ // word on the board. Take CRATE out of CRATE IS PUSH and finish GAP IS174+ // CRATE: every gap becomes a crate nobody can push, so you walk through.175+ board([176+ 'WIS.MIY.GIK.',177+ '########I###',178+ '#........###',179+ '#.###.######',180+ '#..C.....~~~',181+ '#.#I#.#..~~~',182+ '#.#P#.#.~~.T',183+ '#.###.#.~~&I',184+ '#@......~~.N',185+ ], {186+ id: 'night-shift',187+ mustForm: ['GAP IS CRATE'],188+ title: 'Night shift',189+ hint: 'GAP IS has room for one more word. Crates only block you while they are PUSH.',190+ category: 'challenge',191+ introduces: ['noun transform'],192+ reuses: ['GAP', 'SINK', 'PUSH'],193+ }),194+ ],195+};@@ -0,0 +1,183 @@
1+// Crossword: a world made only from the base vocabulary. No custom nouns, no2+// palette, no custom rules. The board is a type case of words, and every3+// puzzle is a grammar puzzle: one IS read across and down, AND on either side,4+// stacked words that travel as one, and nouns that are both an answer and the5+// next clue.6+//7+// Levels are drawn as text grids so a reader can check a board at a glance.8+// Cells are separated by spaces:9+// . empty cell10+// m r f w a MIAU, ROCK, FLAG, or WALL thing11+// MIAU, IS ... a word tile (any uppercase token)12+// A+B several entities stacked in one cell, e.g. WIN+STOP13+// Every row must have the same number of cells. Entity ids are derived from14+// the level id and the cell, so they stay stable while the board is unchanged.15+const { word, object, level } = require('@miau/engine');16+17+const THINGS = { m: 'MIAU', r: 'ROCK', f: 'FLAG', w: 'WALL' };18+19+function grid(id, rows, meta) {20+ const cells = rows.map(row => row.trim().split(/\s+/));21+ const width = cells[0].length;22+ cells.forEach((row, y) => {23+ if (row.length !== width) throw new Error(`${id}: row ${y} has ${row.length} cells, expected ${width}`);24+ });25+ const objects = [];26+ cells.forEach((row, y) => row.forEach((cell, x) => {27+ if (cell === '.') return;28+ cell.split('+').forEach((part, i) => {29+ const entityId = `${id}-${part.toLowerCase()}-${x}-${y}${i ? `-${i}` : ''}`;30+ if (THINGS[part]) objects.push(object(THINGS[part], x, y, entityId));31+ else if (/^[A-Z][A-Z0-9_-]*$/.test(part)) objects.push(word(part, x, y, entityId));32+ else throw new Error(`${id}: unknown cell "${part}" at column ${x}, row ${y}`);33+ });34+ }));35+ return level(width, cells.length, objects, { id, ...meta });36+}37+38+module.exports = {39+ manifest: {40+ id: 'crossword',41+ name: 'Crossword',42+ version: '1.0.0',43+ engine: 1,44+ rules: 2, // authored against post-W6 rule semantics (D23)45+ author: 'Crossword world team',46+ description: 'Pure grammar from the base words: read across and down, share one word between two sentences, and carry stacked words.',47+ world: { glyph: '✏️', tagline: 'Every word can be read across and down.', accent: 'win' },48+ },49+ vocabulary: ['MIAU', 'ROCK', 'FLAG', 'WALL'],50+ levels: [51+ // 1. One IS, read twice. Slide WIN in under the IS from the side (pushing up52+ // from below would lift IS out of MIAU IS YOU) to read FLAG IS WIN down.53+ grid('across-and-down', [54+ '. . . . . . . . .',55+ '. . FLAG . . . . . .',56+ '. MIAU IS YOU . . . . .',57+ '. . . . . WIN . . .',58+ '. . . . . . . . .',59+ '. m . . . . . f .',60+ '. . . . . . . . .',61+ ], {62+ title: 'Across and down',63+ hint: 'There is only one IS. Words are read down as well as across.',64+ category: 'required',65+ introduces: ['crossing'],66+ reuses: [],67+ }),68+ // 2. MIAU IS YOU is pinned to the top right edge and cannot be broken. Push69+ // ROCK and AND up into the two open cells in front of it: ROCK AND MIAU IS70+ // YOU. The rock walks to the flag while the cat stays in its pen.71+ grid('company', [72+ '. . . . . . w . . MIAU IS YOU',73+ '. . . . . . w . . . . .',74+ '. . r . . . w . ROCK . . .',75+ '. . . . . . w . . AND m .',76+ '. . . . . . w . . . . .',77+ '. . . . f . w w w w w w',78+ '. . . . . . . . . . . .',79+ 'FLAG IS WIN . . . . . . WALL IS STOP',80+ ], {81+ title: 'Company',82+ hint: 'MIAU IS YOU is stuck to the edge, but the start of the line is open.',83+ category: 'required',84+ introduces: ['AND'],85+ reuses: ['YOU'],86+ mustForm: ['ROCK IS YOU'],87+ }),88+ // 3. ROCK IS DEFEAT is pinned to the top left corner. Append AND PUSH so89+ // the column of rocks can be shoved instead of touched.90+ grid('shove-the-danger', [91+ 'ROCK IS DEFEAT . . r .',92+ '. . . . . r .',93+ '. AND . . . r f',94+ '. . . PUSH . r .',95+ '. m . . . r .',96+ '. . . . . r .',97+ 'MIAU IS YOU . FLAG IS WIN',98+ ], {99+ title: 'Shove the danger',100+ hint: 'You cannot take DEFEAT away from ROCK. You can add something after it.',101+ category: 'required',102+ introduces: ['AND (predicate)'],103+ reuses: ['AND'],104+ mustForm: ['ROCK IS PUSH'],105+ }),106+ // 4. YOU and DEFEAT share a cell and can never be separated, and DEFEAT107+ // destroys whatever carries it. On MIAU the pair is suicide; on the ring of108+ // STOP rocks it is a demolition order. Build ROCK IS YOU+DEFEAT down from109+ // the pinned ROCK and walk through the gap to the flag.110+ grid('carry-the-pair', [111+ '. . . . . . . ROCK IS STOP',112+ '. . . . . . . . . .',113+ '. . IS . . . . . . .',114+ '. . . . . . r r r .',115+ '. m . YOU+DEFEAT . . r f r .',116+ '. . . . . . r r r .',117+ 'MIAU IS YOU . . . . FLAG IS WIN',118+ ], {119+ title: 'Carry the pair',120+ hint: 'Two words share one square and always move together. DEFEAT hurts whatever carries it.',121+ category: 'required',122+ introduces: ['stacked words', 'DEFEAT on its carrier'],123+ reuses: ['crossing'],124+ mustForm: ['ROCK IS DEFEAT'],125+ }),126+ // 5. The flag is sealed in. Put ROCK IS in front of the pinned FLAG IS WIN:127+ // ROCK IS FLAG IS WIN. The FLAG word is both an answer and the next clue.128+ grid('pass-it-on', [129+ '. . . . . FLAG IS WIN',130+ '. . . . w w w w',131+ '. IS . . w . . .',132+ '. . m . w . f .',133+ '. ROCK . r w . . .',134+ '. . . . w . . .',135+ 'MIAU IS YOU . w WALL IS STOP',136+ ], {137+ title: 'Pass it on',138+ hint: 'The flag is out of reach. A noun can end one sentence and start the next.',139+ category: 'required',140+ introduces: ['noun as predicate', 'transformation'],141+ reuses: ['AND row insertion'],142+ mustForm: ['ROCK IS FLAG'],143+ }),144+ // 6. The black square above YOU stops the easy ROCK IS YOU down. Build ROCK145+ // IS MIAU down onto the pinned MIAU; the rock becomes a second cat.146+ grid('down-to-miau', [147+ '. . . . . . w WALL IS STOP',148+ '. . . . . . w . r .',149+ '. . ROCK . . . w . . .',150+ '. . . . m . w . . .',151+ '. IS . . . . w . . .',152+ '. . w . . . w . . f',153+ 'MIAU IS YOU . . . w FLAG IS WIN',154+ ], {155+ title: 'Down to MIAU',156+ hint: 'MIAU starts a sentence across. What could end on MIAU from above?',157+ category: 'optional',158+ introduces: ['noun at a crossing'],159+ reuses: ['crossing', 'transformation'],160+ mustForm: ['ROCK IS MIAU'],161+ }),162+ // 7. One IS. First WALL IS ROCK (down from the pinned WALL) turns every wall163+ // into a passable rock, and that change stays after the sentence breaks.164+ // Then take the same IS for a WIN rule: FLAG IS WIN down, or ROCK IS WIN.165+ grid('spend-it-twice', [166+ '. . . . . FLAG WALL IS STOP',167+ '. . . . . . . . .',168+ '. . ROCK . . . . . .',169+ '. . . . IS . . . .',170+ '. . . . . w w w w',171+ '. m . . . w WIN . f',172+ 'MIAU IS YOU . . w . . .',173+ ], {174+ title: 'Spend it twice',175+ hint: 'There is one IS to go around. A thing that has changed stays changed.',176+ category: 'challenge',177+ introduces: ['sequencing'],178+ reuses: ['crossing', 'transformation'],179+ mustForm: ['WALL IS ROCK'],180+ solverBudget: 4000000,181+ }),182+ ],183+};@@ -0,0 +1,156 @@
1+// Tidewater: a coastline at changing tide. Hazards and identity:2+// SINK (water swallows whatever enters, words included), DEFEAT (jellies),3+// and transformation (becoming, or un-becoming, what crosses).4+//5+// Levels are drawn as text maps so nobody has to count coordinates.6+// Each map row is a list of whitespace-separated cells:7+// . empty8+// m w r j c b s MIAU WATER ROCK JELLY CRAB BOAT SHELL (objects)9+// MIAU, IS, YOU... uppercase tokens are word tiles10+// a+b a stack (for example SINK+w would be a word on water)11+const { word, object, level } = require('@miau/engine');12+13+const THINGS = { m: 'MIAU', w: 'WATER', r: 'ROCK', j: 'JELLY', c: 'CRAB', b: 'BOAT', s: 'SHELL', f: 'REEF' };14+15+function map(id, rows, metadata) {16+ const grid = rows.map(row => row.trim().split(/\s+/));17+ const width = Math.max(...grid.map(row => row.length));18+ const objects = [];19+ grid.forEach((row, y) => row.forEach((cell, x) => {20+ if (cell === '.') return;21+ cell.split('+').forEach((part, n) => {22+ const uid = `${id}-${x}-${y}${n ? `-${n}` : ''}`;23+ if (THINGS[part]) objects.push(object(THINGS[part], x, y, uid));24+ else if (/^[A-Z]+$/.test(part)) objects.push(word(part, x, y, uid));25+ else throw new Error(`${id}: unknown map cell "${part}" at column ${x + 1}, row ${y + 1}`);26+ });27+ }));28+ return level(width, grid.length, objects, { id, ...metadata });29+}30+31+module.exports = {32+ manifest: {33+ id: 'tidewater',34+ name: 'Tidewater',35+ version: '1.0.0',36+ engine: 1,37+ rules: 2, // authored against post-W6 rule semantics (D23)38+ author: 'Tidewater world team',39+ description: 'A coastline at changing tide. Water keeps what it takes; jellies sting; a crab sees the shore sideways.',40+ world: { glyph: '🌊', tagline: 'The sea keeps whatever you give it.', accent: 'sink' },41+ },42+ vocabulary: ['MIAU', 'WATER', 'ROCK', 'JELLY', 'CRAB', 'BOAT', 'SHELL', 'REEF'],43+ palette: {44+ MIAU: { glyph: '🐈', faces: 'left' },45+ WATER: '🌊',46+ ROCK: '🪨',47+ JELLY: '🪼',48+ CRAB: '🦀',49+ BOAT: '⛵',50+ SHELL: '🐚',51+ REEF: '🪸',52+ },53+ levels: [54+ map('low-tide', [55+ 'MIAU IS YOU . . w SHELL IS WIN',56+ '. . . . . w . . .',57+ '. . . . . w . . .',58+ '. m . . . w . s .',59+ '. . . . . w . . .',60+ 'WATER IS SINK . . w . . .',61+ '. . . . . w . . .',62+ ], {63+ title: 'Low tide',64+ hint: 'Water only swallows things while a sentence says so.',65+ category: 'required',66+ introduces: ['WATER', 'SINK', 'SHELL'],67+ }),68+ map('paper-bridge', [69+ 'MIAU IS YOU . . . w SHELL IS WIN',70+ 'r r r r r r w . . .',71+ '. . . . . r w . . .',72+ '. m . . . r w . s .',73+ 'ROCK IS STOP . . r w . . .',74+ '. . . . . r w . . .',75+ '. . . . . r w WATER IS SINK',76+ ], {77+ title: 'Paper bridge',78+ hint: 'The sea is not fussy about what you feed it.',79+ category: 'required',80+ introduces: ['ROCK', 'STOP'],81+ }),82+ map('sink-the-sting', [83+ 'MIAU IS YOU . . . w . . j .',84+ '. . . . . . w . j s j',85+ '. m . . . . w . . j .',86+ '. . . . . . w . . . .',87+ '. . JELLY IS DEFEAT . w . . . .',88+ '. . . . . . w . . . .',89+ 'SHELL IS WIN . . . w WATER IS SINK',90+ ], {91+ title: 'Sink the sting',92+ hint: 'Jellies defeat whatever is YOU. The sea can take one problem off your hands.',93+ category: 'required',94+ introduces: ['JELLY', 'DEFEAT'],95+ }),96+ map('sideways', [97+ '. . . . . w w SHELL IS WIN .',98+ '. . . . . w w . c . .',99+ 'MIAU IS YOU . . w w . . . .',100+ '. . . . . w w . . . .',101+ '. . . . . w w . . s .',102+ 'CRAB . . m . w w . . . .',103+ '. . . . . w w WATER IS SINK .',104+ ], {105+ title: 'Sideways',106+ mustForm: ['CRAB IS YOU'],107+ hint: 'The shell is out of reach for a cat. Who else is on this map?',108+ category: 'required',109+ introduces: ['CRAB'],110+ }),111+ map('shell-game', [112+ 'j JELLY j . MIAU',113+ 'j IS j . IS',114+ 'j DEFEAT . . YOU',115+ '. . . . .',116+ '. SHELL . . SHELL',117+ '. . . . IS',118+ 'm . . . WIN',119+ ], {120+ title: 'Shell game',121+ hint: 'There is no shell on this beach. Yet.',122+ category: 'required',123+ introduces: ['transformation'],124+ mustForm: ['JELLY IS SHELL'],125+ }),126+ map('one-for-the-tide', [127+ '. . . . . w w SHELL IS WIN .',128+ 'MIAU IS YOU . . w w . . . .',129+ '. . . . . w w . . . .',130+ '. c c c . w w . . s .',131+ '. . . . . w w . . . .',132+ 'CRAB m . . . w w . . . .',133+ '. . . . . w w WATER IS SINK .',134+ ], {135+ title: 'One for the tide',136+ hint: 'Every crab moves when you move. Not every crab has to arrive.',137+ category: 'optional',138+ introduces: ['sacrifice'],139+ }),140+ map('last-boat-out', [141+ '. . . . w w w . . BOAT',142+ '. j . . w w w . . IS',143+ '. . . j w w w . b WIN',144+ '. . . . w w w . . .',145+ 'JELLY IS DEFEAT . w w w . . WATER',146+ '. . . m w w w . . IS',147+ 'MIAU IS YOU . w w w . . SINK',148+ ], {149+ title: 'Last boat out',150+ hint: 'Three waves and one cat. Try reading MIAU from above.',151+ mustForm: ['JELLY IS MIAU'],152+ category: 'challenge',153+ introduces: ['noun as predicate'],154+ }),155+ ],156+};