Design: tools
Build plan
Planned · Tools I–III
How to build the tools, version by version, against the game as it is now: what to add, which functions change, how old saves come across, and the tests that prove it.
Part 1: Tools of the trade
Ship this alone and the game is whole: every gathering verb needs a tool and there is always a way to get one. In site/play/index.html:
- Data. Add the tools to
ITEMSwithk:'tool'(below) and their icons toICON(inbook/design/tools.mjs). GivePOOLGeach atool, and the treegin both zone builderstool:'kapak',hard:1. Adding a property draws no random numbers, so the seeded island doesn't move. - State.
S.belt=new Array(4).fill(null); save it, and check it on load likeinv. - Choosing a tool. A
bestTool(kind)that scans belt, weapon and pack and returns the highest tier the player can use. AgatherTicks(o,tool)used by bothinteract()andactFrac(), so the action ring stays honest. - Gathering. In
interact(), before the level check: no tool → stop with the message for this zone; then addtool.powto the chance and taketool.fastoff the ticks. - Teachers.
TALK.matandTALK.esahget astart()that gives the starter tool (belt first), and theirafterbranch replaces a missing one. Esah'sreward()adds a candat. - Guru and the journal. Guru's first lines send you to Esah for a net; his boat lines send you to Pak Mat for a hatchet.
objective()andquestList()gain the two “get a tool” steps, done as soon as you hold one. - Kak Yah. New stock in
SHOPS.yah. Insell(), tools withoutvget her line about Pak Mat's hatchet. - Panels. The belt row in the Pack tab; clicking moves a tool between belt and pack.
useSlot,effect,itemInfoanduseHintlearnk:'tool'. - Hover and examine.
labelOf()adds the red “needs a …” line; the tree's examine text names the hatchet. - Hands. The
chopclip's prop becomes the hatchet you are using: one prop geometry per hatchet, the head coloured by tier. Thenetandlineclips show the net and the line. - Old saves. Saves from before tools get both starter tools on the belt and one line in the chat. Bump the save format's
v.
/* tools: t is the kind of work, tier ranks tools of a kind, lv is the level to use it, pow adds to every attempt's chance */
kapakl:{n:'Old hatchet',en:'Kapak lama',k:'tool',t:'kapak',tier:1,lv:1,pow:0},
kapakb:{n:'Iron hatchet',en:'Kapak besi',k:'tool',t:'kapak',tier:2,lv:5,pow:.08,v:16},
jalal:{n:'Old cast net',en:'Jala lama',k:'tool',t:'jala',tier:1,lv:1,pow:0,net:true},
candat:{n:'Squid jig',en:'Candat',k:'tool',t:'candat',tier:1,lv:5,pow:0,v:3},
bubu:{n:'Prawn trap',en:'Bubu',k:'tool',t:'bubu',tier:1,lv:8,pow:0,v:6},
pancing:{n:'Hand-line',en:'Tali pancing',k:'tool',t:'pancing',tier:1,lv:12,pow:0,line:true,v:8}/* the best tool of a kind you carry and can use: belt, hand, then pack */ const TOOLSK={kapak:'kayu',parang:'kayu',jala:'nelayan',candat:'nelayan',bubu:'nelayan',pancing:'nelayan'}; function bestTool(t){ let b=null; for(const id of [...S.belt,S.eq.weapon,...S.inv]){ const x=id&&ITEMS[id]; if(x&&x.k==='tool'&&x.t===t&&L(TOOLSK[t])>=x.lv&&(!b||x.tier>ITEMS[b].tier))b=id; } return b; } const gatherTicks=(o,tool)=>Math.max(2,o.g.ticks-(learned(o.type==='pool'?'esah':'mat')?1:0)-(tool&&ITEMS[tool].fast||0)); /* in interact(): */ const tool=o.g.tool?bestTool(o.g.tool):null; if(o.g.tool&&!tool){stopAct(NEED[o.g.tool][Z.id],'w');return} if(o.g.hard&&ITEMS[tool].tier<o.g.hard){stopAct(TOO_SOFT(o,tool),'w');return} ... if(Math.random()<Math.min(.92,.35+(L(g.skill)-g.req)*.03+(ITEMS[tool]?.pow||0))){ ... }
Part 1 tests
A new headless test, tests/tools.js, in the style of the others (Playwright with the __bfTest hook):
- A tree with no hatchet stops with the Pak Mat message on Perhentian and the Kak Yah one in Kuala Besut; with the old hatchet it gives timber.
- Each spot refuses without its tool and works with it; the wrong tool (a net at a squid spot) is refused.
- With an old and an iron hatchet both carried, the iron one is used once Woodwork is 5, and the old one before.
- Talking to Pak Mat and Mak Long Esah gives the starters to the belt; with a full belt they go to the pack; with the tool gone, the next talk replaces it.
- A save from before tools loads with both starters on the belt, once.
- Kak Yah refuses the starters and buys the rest at
v. - Castaway's Training can still be finished from a new game, start to boat, without buying anything.
- The action ring's ticks equal the gathering ticks for every tool.
Part 2: Hardwood and the forge
- Items: the steel hatchet, the parang, meranti, cengal, bamboo, rattan, the meranti handle.
- Nodes:
meranti,cengal,buluh,rotanas object types with their owng, examine text, verbs (“Chop down”, “Cut”) and models. Hardness check ininteract(). - Placement: after the seeded layout, as on the hatchets page. Bamboo and rattan join
BLOCKand the stump logic. - Pak Leh: an NPC, a forge building on the main road, a shop in
SHOPS, and a “forge” list for the steel hatchet (cost in items plus coins). - Recipes:
RECIPESentries getatandskill; the Craft tab shows the station you stand at. - Parang as weapon:
useSlotequips a tool with aslot;wBonus()already readsbonus. - Tests: hardness refusals, the forge job (handle and coins in, steel hatchet out), parang wielded still cuts bamboo.
Part 3: Traps and lines
- A saved clock:
S.clock, ticks played since the new game, saved and loaded. Traps stamp it, so they keep time across zones and sessions. - Traps:
S.traps=[{zone,x,y,set}]; a set trap is drawn at its spot and the spot is “Haul” when full. Max traps by Fishing level. Hauling rolls the extra prawns and re-sets. - Bait: the tuna spot's
g.bait; check before the first attempt, consume on a catch. - Wear: torn and snapped variants as items; the roll after a catch; mending by clicking rope or a hook (a short action at no station).
- Net racks: a new object beside Esah and by the fish market; the net, big net and hand-line recipes.
- Tests: a trap fills after 60 ticks and survives a save and reload; never more traps than the level allows; tuna without mackerel refuses; a torn net is slower and mends with one rope.
How this book is made
The book lives in book/ in the Pendekar repository and deploys as its own Cloudflare Worker to book.pendekar.themasterofnone.xyz.
book/game.mjsreads the committed game (git show HEAD:site/play/index.html), cuts out the data and the zone builders, and runs them in a sandbox. Every live table in this book is that data, so the book can't drift from the game.book/design/tools.mjsis the tools design as data, in the game's own shapes, ready to lift into the game.book/pages.mjsis the words;book/build.mjslays them out intobook/site/, with a search index.
node book/build.mjs # or --working to read the uncommitted game
cd book && set -a && . ~/.kracked/deploy.env && set +a && npx wrangler@4 deployRebuild after every game version so the live chapters follow it.