// ==UserScript== // @name proto23 mod // @namespace https://github.com/Xeus24/23html.github.io // @version 4.7 // @description The proto23 mod (4.7), on the author's hosted game. Backs up your save before it first runs. // @author Xeus24 // @homepageURL https://github.com/Xeus24/23html.github.io // @downloadURL https://raw.githubusercontent.com/Xeus24/23html.github.io/main/userscript/proto23-mod.user.js // @updateURL https://raw.githubusercontent.com/Xeus24/23html.github.io/main/userscript/proto23-mod.user.js // @match https://23html.github.io/ // @match https://23html.github.io/index.html // @run-at document-end // @grant none // ==/UserScript== // GENERATED by tools/build-userscript.mjs from mod.js and // changelog/mod-changelog.html. Do not edit; rebuild. (function () { 'use strict'; var BUILT_FOR = 470; // the author's global.ver this build was tested against if (typeof window.MOD !== 'undefined') return; // the page has the mod already if (typeof window.global !== 'object' || typeof window.you !== 'object') return; // not the game if (window.global.ver !== BUILT_FOR) { console.warn('[proto23 mod] built for game v' + BUILT_FOR + ', this is v' + window.global.ver + '. Loading anyway; your save is backed up first (modBackups()).'); } // read by mod.js section 48, which says so in the game when the versions differ window.MOD_USERSCRIPT = { builtFor: BUILT_FOR, version: "4.7" }; window.MOD_CHANGELOG_HTML = "\n\n\nproto23 mod — changelog\n\n\n\nPROTO23 MOD — CHANGELOG
\nChanges made by the local mod.js. None of this is the work of the\ngame's author — the game's own changelog is here.
\nRemove the <script src=\"mod.js\"> line from index.html and none of it applies.

\n
\n\n----MOD 4.7 (a pill for the wall):
\n[2026/10/07]
\nStuck at a realm 6-10 wall without the pill? The instructor keeps one back. The dojo's pills for those realms are
\n  paid at character levels (45, 60, 75, 90, 105), while the walls are Circulate Qi levels, so a wall could come
\n  before the level that pays for it. \"A pill for the wall\" on the dojo screen gives that realm's pill once; the level
\n  reward for the same realm then skips its pill
\n
\n\n----MOD 4.7 (creature gear):
\n[2026/10/07]
\nCreatures no longer share one weapon. In the game every creature held the same object, and the affinities the author
\n  wrote for 16 of them were all written onto it, so each creature carried whichever was written last. Your bare fists
\n  were that same object too, so your level raised physical and blunt creatures' damage. Each creature now has its own
\n  gear with the values he wrote for it, and your fists are your own
\n
\n\n----MOD 4.7 (dishes, a screen fix):
\n[2026/10/07]
\nFifteen dishes nothing could teach are now taught by Cooking. Bacon and Eggs, Morning Set, Lunch Set, Onion Rings,
\n  Cooked Fish, Batter Fried Fish, Fried Nori, Steamed Bun, Hippo Cookie, Steak, Cinnamon Bun, Broth, Egg Soup, Creamy
\n  Chowder and Mushroom Soup were in the game and unreachable. You work each out at a Cooking level from 6 to 44, simplest
\n  first. Trinity, Star Shell and Grand Gelatin (his late accessories) are still unteachable
\nThe gate's two locked-door hints no longer wrap over the line below them; the full sentence is in the tooltip
\n  Tooling, not gameplay: a check that draws every mod screen with a capped character and a very long name; the
\n  step from the author's last area to the Hollow is now held winnable with margin

\n
\n\n----MOD 4.7 (background tabs):
\n[2026/10/07]
\nA game left in a background tab catches up. Browsers slow a hidden tab's timers to about once a minute, so the
\n  game nearly stopped while you were away. When it next runs, the mod replays the missed time, up to an hour: the
\n  clock, restocks and weather, and the seconds of the action you were doing. Fights are never replayed, so being
\n  away cannot kill you. setCatchUp(seconds) changes the cap, 0 turns it off; it travels with your save
\n
\n\n----MOD 4.7 (wiki search):
\n[2026/10/07]
\nThe wiki search covers every page. The page list shows how many entries match on each page, dims the ones with none,
\n  and Enter jumps to the first page that has a match
\n  Tooling, not gameplay: package-lock.json is now committed (it was ignored, so every GitHub run failed at install)
\n
\n\n----MOD 4.7 (pills and hosting):
\n[2026/09/30]
\nThe Pill Tower stocks the top realm pills sooner. A day is 24 real minutes and the Tower restocks every three, so
\n  realm 10's pill at 6% took about twenty hours on average. Realms 8, 9 and 10 now stock at 26%, 20% and 15%: about
\n  4.6, 6 and 8 hours, rising with the realm
\n  Tooling, not gameplay: instructions in the README for playing from the fork's own GitHub Pages site, and a
\n  .nojekyll file so Pages serves the files as they are; a check that every Tower pill is in stock within 8 hours

\n
\n\n----MOD 4.7 (small):
\n[2026/09/30]
\nSkill rarity has a grade marker. A small number after a skill's name (2 to 10) gives its rarity rank, so it reads
\n  without colour. Rank 1 shows nothing
\n  Tooling, not gameplay: GitHub caches the downloaded test browsers between runs; a check for the marker
\n
\n\n----MOD 4.7 (housekeeping):
\n[2026/09/29]
\nSwitching save slots cannot pull in another slot's settings. A browser now takes settings from a save once, on
\n  its first load; a later load (a slot switch) leaves the browser's own settings alone
\n  Tooling, not gameplay: a check that the docs' stated numbers (sections, scripts, version, suite time) match the
\n  source; the tests now run on Firefox in GitHub too; the docs' script count is 42
\n  (the suite takes about 75 seconds)

\n
\n\n----MOD 4.7:
\n[2026/09/28]
\nThe Qi Settling Pill costs what failing cost. Qi Deviation now lasts as long as the seclusion you skipped —
\n  up to 91 in-game days at the top — and a flat 5,200 made it ten minutes of late-game coin. It now costs the
\n  breakthrough pill of the realm you failed at, never less than before
\nThe rank line shows the wall: at a bottleneck it reads, for example, \"½ step to Nascent Soul >> 40/104\" — the
\n  insight you have against what it asks for
\nYour settings travel with your save. A browser with no settings of its own takes pacing, fights, number format,
\n  speed and the rest from the save, so moving to a new browser or device brings them. One that has its own keeps them
\nAfter an update the log says so, once, with the changelog a click away
\nOn Safari, a reminder to download a backup, at most once a day and not after you have downloaded one. Safari can
\n  clear a site's saved data after a week unvisited, backups included
\nThe userscript says so in the game when the author's game is a version the mod was not tested on
\nNear-death insight, explained: the wiki says where fights you nearly lost actually happen. On Original fights his
\n  areas are no threat once you reach them; the three past the golem arena and the rank duels bring you there within a
\n  few fights. No wall needs it — each can be met by sitting alone
\nSmall visuals: the mod's number boxes lose the spinner arrows that sat on their digits in Safari, and the wiki uses a
\n  proper monospace font where the game's is missing, so tables wrap the same everywhere. The old pacing preset is
\n  labelled legacy: it is kept exactly as it was, and its prices come cheap
\n  Tooling: every tooltip in the game (1,367 of them) is rendered and read in a new check, so a description
\n  that shows code fails the suite; the spirit pills and the Root Cleansing Pill are priced against income too; and the
\n  tests wait for the game to tick instead of a flat 4.5 seconds — the whole suite is about 75 seconds. 40 checks.

\n
\n----MOD 4.6:
\n[2026/09/28]
\nFights as the author wrote them. A new Fights setting, and Original is the default: every area the author
\n  made has his creatures exactly as he made them — his level ranges, his HP, his exp — so you out-grow
\n  them the way you do in his game. The forest goes back to a swing or two a kill
\n  The mod's own areas (the three past the golem arena, and the rank duels) are still sized to you, since
\n  there is no original of them. Scaled is the old behaviour everywhere, and the Mod before 4.2 preset picks it
\n  Raw Meat is back to the author's 6% on Original: the raise to 18% only made up for the mod's longer fights
\n  Checked against the author's own page: all 70 spawns in his areas come out stat for stat the same, and
\n  every step of his route past the tutorial is winnable at the point you first reach it.

\nBreakthrough walls sized to the climb. Realms 3 to 10 take days to months of Circulate Qi on the current skill
\n  curve, but the insight they asked for (5 to 19) and the fifteen minutes of seclusion were sized for when
\n  they took minutes, and meditating on the way up paid for every wall long before you reached it
\n  Now a wall asks for about 5% of the climb to it, and meditating only earns insight while you are at one.
\n  Realm 3's wall is 60 insight, about 1.4 hours in seclusion; realm 10's is 1,580, about 37 hours
\n  Full consolidation takes as long as seclusion takes to earn that insight, so sitting it out finishes both,
\n  and failing in the open leaves you deviated for as long as the seclusion you skipped. The wiki's cultivation
\n  page has the table. Realms 1 and 2 are as they were
\nThe Pill Tower's top pills cost what you can earn. Realm 8, 9 and 10 were 900K, 2.4M and 7M, priced when
\n  enemies dropped coin. With the original's no coin drop, they are 540K, 560K and 6.5M
\n  Measured: the best income at every stage, from fighting in each area and gathering at each node, against
\n  every price the ladder requires. The rule is that none costs more than 10% of the climb it gates. The
\n  Herbalist's pills and the Jade Slips already passed; all hundred slips come to about 4% of what the game
\n  pays after the Slip Archive opens.

\nPlay it on the author's hosted game, with no local copy: userscript/proto23-mod.user.js, for Tampermonkey or
\n  Violentmonkey. It runs the whole mod on https://23html.github.io/, and your save there is backed up before the
\n  mod first touches it
\n  Tooling, not gameplay: the tests run on GitHub for every push and pull request; a check that the author's
\n  two files are still his (his changelog byte for byte, index.html his plus the one script tag); and the test
\n  runner reports seconds per script. allareas, listed as the slow one, now takes about seven seconds, so it
\n  was left as it is. New checks: fights.mjs and userscript.mjs; econ.mjs is now a real check rather than a
\n  printout. 38 in all.

\nSafari. The skill list no longer wraps each exp bar onto a second line (Safari's scrollbar left the
\n  row a few pixels short, which also made the list scroll sideways); the mod's dropdowns in settings match
\n  the panel instead of showing as grey system buttons; and clicking a section header no longer selects its text
\nThe realm tooltip on the rank line showed a page of program code instead of your realm, root, insight and
\n  breakthrough odds. It shows those now
\nThe skill list does far less work while it is open: it only redraws what changed each second, rather than
\n  every line — about ten times cheaper, in every browser
\n  Tooling: every check can now run on WebKit, the engine Safari is built on, and GitHub runs them on
\n  both. The Safari problems above were invisible in Chrome's test browser, which has no scrollbar width.
\n  On Safari, download a backup now and then: it can clear a site's saved data after a week unvisited.

\n
\n----MOD 4.5:
\n[2026/09/28]
\nThree bugs in the author's own code, fixed.
\n  Death made dying worse the more you levelled it. You kept 55% of your energy at level 0, none at
\n  level 10, and went into NEGATIVE energy from level 11 — the opposite of what the skill says. It now
\n  keeps 55% at level 0, exactly as before, rising to 78% at 10 and 96% at 110
\n  Shield Mastery could turn your defence into extra damage. At high levels one bracket in the enemy's
\n  damage went below zero and your defence was added to the hit instead of taken off it. That bracket
\n  now stops at zero. Nothing changes where it was already fine
\n  Luck never touched your crit chance. The author wrote a luck multiplier and then declared it where
\n  the crit roll could not see it. It works now, up to a 50% crit chance from luck
\n  Why the cap: the mod's perks grant far more luck than the author's game ever did, and through his
\n  formula a capped-out character would crit on every swing. Enemies are sized with it included.

\nBreakthrough history on the What next page: your last attempts, their odds, and how each ended
\nBackups: once a day as well, in their own place so they cannot push out an update's backup; and any
\n  backup can be downloaded as a file and loaded back, so a save survives clearing the browser
\nShop prices read: the mod's own stock (pills to seven million) follows your number format. The base
\n  game's prices, all under 10,000, are untouched
\n  Tooling: tests for the cultivation road (every rule, which until now had only been checked by hand),
\n  for the three fixes, and a balance baseline — the numbers that define the balance are kept in
\n  tests/baselines/balance.json, so any change to them shows as a diff. 36 checks, all passing.

\n
\n----MOD 4.4:
\n[2026/09/27]
\nYour saves are backed up before a new version of the mod loads. Every slot, copied before the game
\n  has even read its save. Three are kept. The saves panel lists them, restores one, or backs up on demand
\n  It is the very first thing the mod does when it loads, and depends on nothing after it: an
\n  update that breaks is exactly when you want the backup, and a broken section stops everything
\n  below it while the game goes on loading and autosaving. Restoring puts every slot back, and backs
\n  up what you had first, so it can be undone. It restores your saves, not the mod's code.

\nA Pacing setting: Original, the default since 4.2, or Mod before 4.2 — skills much faster early,
\n  skill exp 2x and a 15% coin drop, exactly as the mod played before. Level 110 is the same distance
\n  away in both, and fights are the same. Change one of those boxes yourself and it reads Custom
\n  New checks: tests/backups.mjs and tests/pacing.mjs. The backup check caught a real bug before it
\n  shipped: when there was no room, it cleared every older backup to make space and then lost them
\n  anyway when the copy still did not fit. It now frees only the one it is replacing.

\n
\n----MOD 4.3:
\n[2026/09/27]
\nDocumentation, not gameplay: the notes and READMEs brought up to date.
\n  MOD-NOTES.md opens with an overview of the mod as it stands at 4.3, and gains the missing
\n  chapters for 4.0 to 4.3. Older passages that later versions changed now carry a dated note
\n  beside them rather than being rewritten, since the reasoning is the point of that file
\n  Two corrections along the way: the notes gave the top realm as \"Ascendant\" (it is Tribulation
\n  Transcendence), and the README listed seven realms, Mortal through Ascendant (there are ten above
\n  Mortal). Both READMEs now describe 43 sections, 31 test scripts and 3,720 matchups.

\n  Repository: the checkout's remotes were renamed — origin is the fork, upstream is the author's
\n  game — so the author's-changelog check is now against upstream/main.

\nmodHelp() lists every console command — fifty-four, grouped by purpose. It listed six, from the first
\n  version, and said skill exp resets on reload, which stopped being true when the settings box
\n  began persisting it. A check now fails if a new command is left out of it
\n  Housekeeping: the code comments in sections 38 and 39, and two lines of the 4.0 entry below,
\n  quoted Wuxiaworld's glossary at length. They paraphrase and cite it now. CLAUDE.md brought up to
\n  date as well: the remotes, five actions, the dead wrappers now removed, and current counts.

\n
\n[2026/09/26]
\nYour realm is on the main screen, and it is the breakthrough button.
\n  The rank line now reads \"rank: 135T · Core Formation 5/9\". At a bottleneck it says \"½ step to
\n  Nascent Soul >>\" and clicking it breaks through, with the same checks, odds and tribulation as ever.
\n  Its tooltip shows your root, your insight, whether you hold the pill, your odds, and whether
\n  you are sheltered in seclusion
\n  The player panel is a fixed-height box and already full, so this shares the rank line rather
\n  than adding one. Plain text only: a ▲ and a ⚠ each made the panel taller, measured.

\nA What next page in the wiki, right after Start here. It reads your save and says what is ready
\n  now, what you are working on, and what opens the rest: story cap, cultivation, locked actions,
\n  the dojo, the late areas, the rank ladder, crafting, convergence, and odd jobs
\nRarity reads as a word, not only a colour. The skill tooltip names it. The skill panel sorts by rarity
\n  and can show only one rarity and above
\nThe Damp cellar is finally somewhere. It is the author's area, but it had no spawn weights (nothing could
\n  appear), no way in, and an exit into two screens that were never written. It is Notice #1 on the
\n  Message Board now, once the dojo has taught you to fight. The first clear pays
\nFour unfinished titles finished: Second-Rate and First-Rate Shopper at 5,000 and 10,000 purchases (the
\n  author's own numbers, commented out in his code), and Peasant and Merchant at 10 and 100 gold
\n  The fifth, named \"null\", is left alone: nothing says what it was for.
\nFixed: the skill list rebuilt itself every second, forever, once it had grown with the panel open: every
\n  row, every second, until you closed it. It redraws once per change now, and notices a skill leaving
\nRank duels have more headroom (1.25 to 1.4). The top challengers hit about 12% softer
\nEvery enemy has been checked against a fully merged character too, 3,720 fights in all, and all are winnable
\n  Housekeeping: three unused copies of the stat-refresh code removed. Tutorial fight 1 was on the list
\n  as a fight you could lose; that is only true of the original's numbers, and here it was already
\n  winnable (8 swings to win, 23 to lose), so nothing changed. New check: tests/polish.mjs.

\n
\n----MOD 4.2:
\n[2026/09/26]
\nMeasured against the original game, and brought back in line with it.
\n  A new check (tests/vanilla.mjs) loads the game twice, once with this mod blocked, which is
\n  the author's game exactly, and runs the same measurements on both.

\n  Skills cost exactly what the original charges, for the first ten levels. The mod's old curve
\n  was 12x faster than the author's at level 5 and 364x faster at level 10, which is exactly the
\n  range he designed: all 22 of his perk ladders end between level 1 and 15. From level 10 it
\n  rises about 3% a level, at a rate worked out so level 110 is exactly as far away as it was
\n  Why not match him to 15, his last perk? His level 15 alone costs 823,099 xp, and ninety-five
\n  more levels even at that flat price is two and a half times the whole budget.

\n  Skill exp is 1x and enemy coin drops are off, the original's values. Both were raised by this
\n  mod (2x, 15%). The coin drop is the author's own mechanism, switched on only by the Coin Ring
\n  and the Ring of Greed; at 15% under them those two rings were worthless. Both settings
\n  boxes still work, and anything you set there yourself still wins
\n  Fixed: the six elemental masteries were stuck on the original's curve. The mod installed its
\n  curve before they existed, so level 20 cost 7,474,050 xp instead of 375 and their perks to
\n  110 could not be reached
\n  Fixed: about one spawn in twenty came out much weaker than intended. Enemy strength is set by
\n  sampling 32 of your swings, and when none of them crit the crits were not averaged badly, they
\n  were left out entirely, and they are 30% of your damage. Those spawns were ~20% short. Now
\n  98% of spawns land within 4% of their intended fight length
\n  The same bug in the balance tests is also why they sometimes failed on a rank duel and
\n  passed on a re-run: 0.15% of the time they read a fight 40% longer than it was.

\n  Not matched, on purpose: fight length. At equal progression the original's early fights take
\n  one or two swings and almost never hurt you, because you out-level its fixed creatures. This
\n  mod holds every fight near 9 swings and 22 to die. The test prints that gap
\n
\nBig numbers are readable: Short (4.56M), Scientific (1.23e7) or Myriads (5.6亿).
\n  A Number format row in settings. Stats, health, energy, exp, damage, Power rank and money all use it
\n  Up to 9,999 every format prints exactly what the original prints. That threshold is the author's.
\n  His damage log checks for numbers over 9,999 and calls his own compactor, then throws the result
\n  away, so damage was never compacted. It is now
\n  Myriads are his notation too: his wallet already counts in 10^8 coins (㋧) over groups of
\n  10^4, which is 亿 and 万. \"As the original\" leaves every number exactly as he draws it.

\n
\n----MOD 4.1:
\n[2026/09/26]
\nSkill rarity, and convergence — fold a whole section into one skill.
\n  Every skill has a rarity now, ten of them from Novice to Transcendent, and the
\n  skill list paints each row in its colour
\n  It is the rank of the level you have taken the skill to — the same thresholds and
\n  the same ten colours titles use. Derived, not assigned: a hand-picked rarity per skill
\n  would be a hundred numbers to keep in step with a ladder that moves, and would say
\n  nothing about your character.

\n  Jade Slips (玉简) fold a skill into its section. Fold every skill in a section and it
\n  is one line on your sheet instead of up to twenty-five — Warfare, Constitution,
\n  Pathfinding, Livelihood, Workmanship, Fortitude, Attunement, Provision, Maintenance,
\n  Fellowship
\n  The Slip Archive is on the dojo lobby, beside Level Advancement. It sells the slips;
\n  the price rises with every one you have spent, so converging all hundred is a project
\n  Folding costs you nothing but the slip. A folded skill keeps its level, keeps earning
\n  exp, keeps every perk and keeps applying every buff it ever applied. It is hidden, not
\n  removed, which is also why it can be unpicked at any time
\n  Hiding rather than removing is the whole design. Shortening the skill list breaks three
\n  things at once: the game's panel only redraws when the list GROWS, so removed rows would
\n  stay on screen forever; the game pushes a skill back onto the list on its next level-up,
\n  banner and all, so a fold would undo itself; and the list is saved by id. The mod's
\n  buffs were always applied from the skills themselves, never from the list — so \"all
\n  of the buffs\" is literally true rather than re-implemented. Measured: every stat after
\n  folding all hundred is exactly its unfolded value times the convergence bonus.

\n  A converged section is worth x1.03 on every stat, compounding — x1.34 with all ten.
\n  A partly folded one pays a share in proportion, so it rewards you as you go
\n  A Skill handbook in the wiki, beside the skills page: the rarity ladder, which skills
\n  fold into which, where you stand, and what the slips cost
\n  New: modConverge(), modRarity(), modFold(section), modUnfold(section).
\n  tests/convergence.mjs, 43 checks. tests/perkcoverage.mjs no longer grades the ten
\n  converged skills — they are never trained, so a perk ladder on them could never be
\n  climbed, the same reason Renown was already exempt.

\n
\n----MOD 4.0:
\n[2026/09/25]
\nThe cultivator's road — the realm ladder finally has a road between its rungs.
\n  Sourced, not invented. Wuxiaworld's \"General Glossary of Terms in Wuxia, Xianxia &
\n  Xuanhuan Novels\" is the genre's own reference work and it names every piece below.
\n  Its entry on bottlenecks was the design brief: breaking one can take new insight, a
\n  medicinal pill, or harsher training.
\n  The mod shipped with one of those three. All three are here now.

\n  Spiritual Root (灵根) — rolled once, when the dojo finishes teaching you to
\n  fight. Five grades by purity, commonest worst. Never a dead end: the worst costs 15%
\n  cultivation speed, not access, and a Root Cleansing Pill moves you up a grade
\n  Nine layers to every realm (层) — derived from where Qi Circulation sits between
\n  this realm's threshold and the next, so breaking through lands you at the first layer
\n  of the new one on its own. You are now \"Core Formation, 5th layer (middle stage)\", or
\n  at the Peak of it, or a half step to Nascent Soul — which is the genre's own name
\n  for the state this mod has been calling a bottleneck
\n  Insight (参悟) — the half of the price that cannot be bought, from the three
\n  sources the glossary names and no fourth of our own: meditating, a kill made under
\n  20% health, and the first time you set foot in an area. Spent whether the attempt
\n  works or not, because what you spend is understanding
\n  Closed Door Training (闭关) — a fifth action, and the only one that costs you
\n  something: it holds the action timer, and it refuses to start unless you are actually
\n  at a wall. Fifteen minutes in it is worth +30% on the attempt
\n  And it shelters you. The glossary gives seclusion two purposes, breaking a
\n  bottleneck and not being disturbed at the crucial moment and suffering a backlash
\n  — so a breakthrough attempted while you are still sitting in it cannot
\n  deviate, only fail. Sit and be safe, or push it in the open and risk it.

\n  Qi Deviation (走火入魔) — failing in the open now leaves your cultivation base
\n  unstable for half an in-game day: stats at x0.55, body at x0.70, no second attempt
\n  until it passes. Clears on its own or at once with a Qi Settling Pill. Never kills
\n  Heavenly Tribulation (天劫) — realm 10 was called Tribulation Transcendence and
\n  there was no tribulation. From realm 8 up, a successful breakthrough is answered by
\n  lightning: 3, 5 then 7 bolts costing 55/70/85% of your max HP. A gate on walking in
\n  healthy, anchored to the player like every other number here. Fall and the realm
\n  does not open
\n  Impurities (杂质) — something black and foul-smelling leaves you on the way up
\n  New: modRoad() and modRoots() in the console; two pills at the Herbalist; the wiki's
\n  Cultivation page carries all of it, generated from the live tables as ever.

\n
\nFixed: a realm's body multiplier did nothing at all.
\n  Found while building the above. Section 28 raised max HP and max energy by ASSIGNING
\n  you.hpmax and you.satmax — the exact mistake v3.9 fixed for hpTrack, still sitting
\n  in the realm code. stat_r recomputes BOTH from their inputs, so both were undone on
\n  the next call. Measured on a realm-10 character: hpmax 332 after allbuff and 39 after
\n  one stat_r; satmax 1700, then 200. The x8.5 the top realm advertised lasted one call.

\n  Ten realms of health and energy you were never given, you now have. Both go
\n  through hpm and satm, which are inputs to stat_r and therefore survive
\n  Two contributors wanted the same two fields, and two delta-trackers on one field
\n  cannot work — each recovers its base by dividing out its own factor and finds the
\n  other's sitting in it. So nothing writes hpm/satm any more: contributors publish a
\n  factor to MOD_BODY and one idempotent applier writes the product, one delta flag per
\n  axis. Verified: survives stat_r, zero drift over 200 calls, Mortal untouched.

\n
\n----MOD 3.9:
\n[2026/09/23]
\nFixed: you never actually had the max HP the game was giving you.
\n  The mod raises max HP with your offence (hpTrack). It did that by ASSIGNING you.hpmax —
\n  and stat_r recomputes hpmax from (hp_r + hpa) * hpm * hpe on the very next call, which
\n  the tick, combat, and allbuff itself all make constantly. Measured on a cap-60
\n  character: one stat_r() took max HP from 1,492,947 to 72,377. Sampled every 137ms
\n  across six seconds of play, the low number appeared in all 43 samples.

\n  Enemy damage was sized against the number you never held. MOD_scaleEnemy reads
\n  you.hpmax in the instant after allbuff, while the assignment still stands, so every
\n  fight was built for a character with ~18x the health you actually had. Harmless early,
\n  worst at the cap
\n  It writes the multiplier now, not the total — hpm is an input to stat_r, so it
\n  survives. Max HP holds through 200 consecutive stat_r calls
\n  hpm is SAVED, so the mod's factor is tracked in global.flags and divided back out
\n  rather than multiplied in again — the same pairing the coin-drop rate uses, and the
\n  same trap you.res once fell into. Verified across six save/load cycles and 20,000
\n  allbuff calls: zero drift.

\n  Your fights are now as deadly as they were always meant to be, which is to say
\n  roughly eighteen times less so than they have been
\nTwo corrections found by the tests, not by reading:
\n  the recovered base needed flooring at 1 — anything resetting hpm without clearing the
\n  flag recovered 1/18.86 instead, and allareas immediately reported the player at 12 max
\n  HP with six rank duels flipped to kill > die; and Math.ceil rather than Math.round, to
\n  agree with stat_r to the unit
\nAlso: tests/run.sh now serves with -c-1. Without it http-server sends max-age=3600 and
\n  a browser can be handed a mod.js from before the edit being tested — which showed up
\n  once during this work as an allareas failure that would not reproduce standalone.
\n  A flaky safety net is worse than a failing one.
\nTests. tests/targets.mjs gains 5 checks — max HP survives one stat_r and two hundred,
\n  the factor lives in global.flags rather than being written as a total, hpm never drops
\n  below the game's own value whatever resets it, and 5,000 allbuff calls do not compound
\n  it. Verified they fail against the old behaviour
\n  All 2,790 allareas matchups still pass — they were measuring the right number all
\n  along. It was play that had the wrong one.

\n
\n----MOD 3.8:
\n[2026/09/23]
\nTooling, not gameplay: the balance numbers now come with error bars.
\n  Every balance figure in the suite is a sample mean of something heavy-tailed — crit rate
\n  reaches 33% and a crit is ~8x a normal swing. Until now the scripts reported point
\n  estimates: fightsmoke medians nine duels, allareas prints one number per matchup, and
\n  nothing said how much of a gap between measurement and target was real.

\n  tests/lib/stats.mjs — the first shared code in tests/. Confidence intervals on
\n  Student's t (z at n=9 is 15% too narrow, and fightsmoke fights nine times), plus a
\n  practical-equivalence check that fails two ways — real drift, or an interval too wide
\n  to conclude anything — and says which
\n  fightsmoke now prints a CI beside the median. Immediately useful: The Long Vigil's
\n  median of 2 swings carries an interval of [0.46, 4.87], ±83%. That number was being
\n  read as a measurement and it is noise. Flagged in place rather than failed
\nNew: tests/targets.mjs. Every other balance script checks a RELATION — kill < die,
\n  whiff 0. None checked whether the model hits its own dials, which a single matchup
\n  cannot tell you. 45 spawns an area, compared against the target THAT SPAWN was solved
\n  for, with an interval. Catches the model drifting without breaking the ordering
\n  Verified it fails: a 15% drift injected into _modDmg is caught at 0.869 of target.
\n  Result: the model is on its dials — every area tracks its per-spawn kill and die
\n  targets to within 1%, and kill < die holds on 45/45 spawns in all four areas sampled
\nFour corrections along the way, all to the new test, none to the mod:
\n  comparing against the flat dial instead of the per-spawn target (a basement rat is
\n  legitimately a five-swing kill); omitting the hit rate, which pinned every die ratio at
\n  exactly 0.45 — MOD_ENEMY.hit, the tell; a plain sample mean instead of the crit
\n  stratification CLAUDE.md already demands; and testing significance rather than
\n  equivalence, which at ±0.1% precision fails on a 1% quantisation bias that means
\n  nothing in play
\nNot gameplay. No change to mod.js, index.html, or any save.
\n
\n----MOD 3.7:
\n[2026/09/16]
\nFixed: every item the mod added was labelled \"Book\".
\n  An item's id is two schemes at once. One decides which list it belongs to; the OTHER
\n  decides its class, read straight off the number with no field for it — under 3000 Food,
\n  3000-4999 Medicine/Tool, 5000-8999 Material/Misc, 9000+ Book. stype is not a proxy:
\n  the author's stype 4 spans Food, Medicine and Book.

\n  Everything the mod added sat at 9100+ to stay clear of the author's maxima, so
\n  26 of its 32 items read as Books, each with a \"Read: Never\" line under it — ore,
\n  hides, essences, tonics, and all fourteen pills. Only the six skillbook manuals,
\n  which really are books, were right
\n  Tonics are 4201-4204, breakthrough pills 4210-4219, spirit pills 4220-4223, crafting
\n  materials 5200-5211. The manuals stay where they were
\n  MOD_ITEM_IDS now holds the ranges with the class table written beside them, so the
\n  next addition picks from it instead of from \"whatever is free above his highest\"
\nYour items survive the move. The inventory is saved BY id and restored by matching it,
\n  so an entry whose id no longer exists is dropped without a word — and a Tribulation
\n  Pill is seven million coin. The save is read BEFORE the game loads it, the moved ids
\n  are noted, and those stacks are given back afterwards under their new ids
\n  The save itself is never rewritten. Anything unexpected in it and the migration does
\n  nothing at all — a migration that fails is better than a load that fails.

\nTests. tests/ids.mjs gains 7 checks: the class of every added item, taken from the mod's
\n  own tables rather than from the ids; that nothing which is not a book gets a \"Read:\"
\n  footer; and a round trip through a save deliberately rewritten back to 3.6's ids, which
\n  must come back with the same amounts and must not double-grant on the next save.
\n  Verified they fail before the fix
\n  modItems() prints every added item with the class its id gives it
\n
\n----MOD 3.6:
\n[2026/09/16]
\nFixed: two skills were both called \"Foraging\".
\n  The base game has skl.hvt, \"Foraging\". MOD 1.x added skl.frg and called it the same
\n  thing — two identical rows in the skill panel, and TEN identically named titles,
\n  because a title's name is derived from its skill's. Nothing errored.

\n  The mod's is Wildcraft now. Its key and id are unchanged, so nobody loses a level —
\n  names are not saved, ids are
\n  Swept the rest: all 140 things the mod names are unique, including across the whole
\n  inventory bag (item / wpn / eqp / sld / acc share one list)
\n  The author's own repeats are reported and left alone: \"Chashu Ramen\" twice, \"Bandage\"
\n  on both an item and an armour, two \"Blue Slime\", two \"Nameless\", nine \"Training Grounds\".

\nFixed: the hunter's quest had become impossible in summer.
\n  Yamato wants ten Raw Meat HELD AT ONCE, and raw meat rots on a daily timer that runs
\n  twice as fast in summer. A perishable stock converges instead of accumulating, so there
\n  is a ceiling and the quest needs that ceiling to be above ten.

\n  The mod never touched the drop table — it changed how long a rabbit takes to kill.
\n  At the point the quest is offered a Wild Rabbit now has 3,574 HP against the base
\n  game's 93, so one that died in a single hit takes ten. Ten times less meat an hour
\n  against an unchanged rot clock
\n  Measured at the old 6%: steady state 15.6 in a normal season, 51 in winter, and
\n  4.3 in summer — against the ten required
\n  Raw meat now drops at 18%, solved backwards from the requirement rather than picked:
\n  a steady state of 25 in the worst season and the first hunting area. Applied to every
\n  creature carrying it, found by scanning the drop tables rather than by naming the
\n  rabbit and the wolf. Worst case is now 26 against 10
\n  Not an income: a kill in the first hunting area yields 0.28 coin of meat.
\n  modMeat() prints the table per area and season
\nTests. tests/names.mjs — 12 checks. Verified both halves fail without their fix: the
\n  name half reports the skill clash and all five title collisions, and the meat half
\n  reports a summer steady state of 4 against 10 at the old drop rate
\n
\n----MOD 3.5:
\n[2026/09/15]
\nCrafting goes to five stars.
\n  It stopped at two. Measured across all 62 recipes, by what they produce AND by whether
\n  any giveRcp call can actually teach them: 35/43 reachable at 1★, 7/17 at 2★, 0 of 1 at
\n  3★, 1 of 1 at 4★, and nothing whatsoever at 5★. The only 3-star recipe in the game is
\n  Trinity, and nothing can teach it; the one 4-star result is a Clover Pin you get for
\n  holding seven clovers.

\n  Four new rungs, 2★ to 5★, each a full set — weapon, body armour, shield,
\n  accessory and tonic. Twenty craftable things
\n  Twelve materials along three lines (ore, weave, essence), one set per rung
\n  Four resource nodes — the Stone Hollow, the Iron Seam, Ashfall Quarry and the
\n  Deep Vein — behind one door on the Village Center, opening one at a time with the
\n  story. They set no tier flag: a gathering spot that hands out a level cap on arrival
\n  is the catacombs bug over again
\n  A Gather action that turns up at the first node you walk into. It trains Mining,
\n  Geology and Foraging — three skills the base game defines and then barely uses —
\n  and the first thing out of a seam teaches that rung's five blueprints
\n  Each rung eats the rung below it, so it is a ladder and not four shopping lists.
\n  The 4★ blade lands beside the game's own best (Fate Cutters, str 108) and the 5★ one
\n  goes past it, since that tier has nothing else in it
\nFound: 19 of the 62 base-game recipes cannot be learned at all. They are defined,
\n  priced and complete, and no giveRcp anywhere hands them over — trr, lnch1/2/3, orgs,
\n  ffsh1/2, fnori, cbun1, sshl, hpck, steak, cnmnb, brth, eggsp, crmchd, msoop, jln4.
\n  Listed in the wiki rather than wired up; which were gated on purpose is not our call
\nEconomy. The first pass came out backwards — a 5★ sword priced at 1,161 while each of
\n  the eight ingots it eats priced at 13,000, so selling the ore paid ninety times better
\n  than using it. The value model is re-indexed after the new recipes exist, and the
\n  materials are anchored at 6/18/54/162 instead of being priced on an equipment scale.
\n  Gathering now pays about 6 coin a second sold, against roughly 1 from endgame combat
\nWiki. A new Crafting page, after Items: what can be made at each rarity, which
\n  base-game recipes are unreachable, every rung with its costs, and the gather rates
\nTests. tests/crafting.mjs — 27 checks, including the two traps this had to avoid:
\n  every id must sit in the block load() restores its namespace from, and no rarity may
\n  pass 6 or equip() runs off the end of global.text.wecs the moment you wear it
\n
\n----MOD 3.4:
\n[2026/09/15]
\nThe title picker groups the titles it used to leave in a heap.
\n  595 titles, 512 grouped by skill — and 83 in a flat list at the bottom, which is the
\n  wall of rows the grouping existed to remove, just smaller. Reading them, almost none
\n  were really one-offs.

\n  +19 folded into the skill they belong to. The flagship titles (Executioner,
\n  Deathless, Sage...) are paired with their skill in the mod's own table, and the
\n  author's numbered tiers are keyed <skill><n> — tghs1/2/3 under Toughness, srd3/srd4
\n  under Swordsmanship, dth4 under Death, rtr1 under Retreating
\n  Most of those numbered tiers are granted by NOTHING — giveTitle never mentions them
\n  anywhere in the game. Unfinished content, like the Damp cellar. They group correctly
\n  whether or not they are ever earned.

\n  +40 are ladders of their own and now collapse like a skill does: the ten cultivation
\n  realms, five for creatures killed, four for items gathered, four for titles collected,
\n  four for punch power, three each for time indoors / shields / jobs, and more
\n  Each ladder says what it is on the row — \"Nameless (4)\" could be punch power or
\n  titles collected, so the label sits beside it rather than only in the tooltip
\n  595 titles held now renders as 126 rows, and the flat tail is down from 83 to 23 —
\n  Nobody, Initiate, Thrasher, Wolf Slayer, Safehouse and friends, which really are
\n  one-off story titles with nothing to group under
\n  Membership is derived from the key, not typed out: only a LABEL per ladder is written
\n  down, eleven of them, and a ladder with no label falls back to its lowest title's name.

\nThe wiki's title page uses the same grouping, so the two cannot drift apart. It had the
\n  same problem solved worse — everything skill-less under \"From the story\", split into
\n  rank buckets. The ladders nest under \"Ladders of their own\"
\nFound: five titles the author never finished. ttl.ddcd has the literal string \"null\"
\n  for its name AND its description; shpt2, shpt3 and mone3 are blank. Nothing grants any
\n  of them. The picker skips them rather than drawing a blank row; the wiki lists them and
\n  tags them \"unfinished\", the same call the Damp cellar got
\nTests. tests/titlepicker.mjs gains 8 checks — the ladders group, every one has a
\n  readable label, no label names a ladder that does not exist, and \"Thrasher\" is NOT
\n  filed under Throwing on a bare stem match (which is what the trailing-digit rule is
\n  there to prevent). Its group counts are derived now instead of hardcoded
\n
\n----MOD 3.3:
\n[2026/09/15]
\nFixed: every title was listed twice after loading a save.
\n  Found by auditing, not reported. giveTitle pushes each earned title onto TWO arrays —
\n  global.titles and global.titlese — and load() rebuilds global.titles from the save
\n  BY INDEX and then appends the whole of titlese on top. So everything earned that
\n  session ends up in the array twice. Measured on a character holding 241 titles: 241
\n  before a load, 481 after, 241 distinct.

\n  It does not compound — the next load finds titlese empty — but one load is enough,
\n  and save() writes the inflated array straight back out
\n  Both title screens are affected: the game's own, and the mod's grouped picker. Both
\n  iterate global.titles, so each title got two rows
\n  A base-game bug, but the mod is what makes it loud: vanilla hands out around a hundred
\n  titles and section 24 grants five per skill, so the doubling is hundreds of rows.

\n  Existing saves are repaired on load, by identity, the same way the skill sheet is —
\n  global.titles holds references to the objects in ttl, so the same object twice is
\n  always wrong whatever put it there. Not fixed at the source: titlese is the author's
\n  mechanism and the mod has no business changing what it means
\nHardened: the rank-duel scaler now refuses the player and creature.default explicitly,
\n  matching MOD_scaleEnemy, instead of relying on their not carrying a spawn target
\nTests. tests/ids.mjs gains 8 checks for the title list: distinct before a load, distinct
\n  after one, distinct after two, no holes, the worn title still present, and an
\n  already-doubled list repaired. Verified they fail without the fix (85 entries for 43
\n  distinct) and pass with it
\nAudit, no change needed. All 75 locations draw without throwing, no choice row wraps its
\n  22px line, all 12 wiki pages build, no id clashes in any namespace, the wiki has no
\n  horizontal overflow at 375/768/1200px, no interval leaks over 15s at 20x speed, and
\n  acts/you.skls/furn/qsts all stay distinct across repeated save/load cycles
\n
\n----MOD 3.2:
\n[2026/09/11]
\nRank 1 is reachable. It just never felt like it.
\n  The number under your portrait is the base game's Power rank, and it is DERIVED from
\n  your stats every time it is drawn — there is no stored field. Measured on a character
\n  built to each rung of the ladder: rank 21,617,349,975 at tier 0, 65,544 at tier 5, 258 at
\n  tier 7, 20 at tier 8, and 1 at the very top. Ranks 1–10 all live inside the last fifteen
\n  skill levels — at 108 across the board you are still rank 3 — after a whole game in
\n  which the number is an unreadable ten-digit blob.

\nSo there is a second way to hold a rank: take it off whoever has it.
\n  The Hall of the First Gate, off the Old Path trailhead past the Western Woods gate.
\n  Ten named challengers, rank 10 down to rank 1. Beating one takes that rank
\n  Strictly in order. Rank N opens only when rank N+1 has been taken, and each rung also
\n  wants a character level (60, 65, 70 ... 100, 110), so the ten fights span the endgame
\n  instead of being one evening
\n  The rank you hold floors the rank you are shown, and never caps it — hold rank 6 with
\n  rank-900 stats and it shows 6; pass it on your own and it shows the better number. A held
\n  rank is painted gold
\n  Implemented by wrapping you.rank and keeping the earned rank in global.flags, like
\n  mod_realm. Nothing is written into a stat, so there is no delta to track and nothing
\n  compounds on load — the trap you.res and skl.p both fell into.

\n  Losing costs nothing but the walk back. A rank you have not taken cannot be taken off
\n  you, and a rank you have taken is never lost
\nThe duels get longer, not deadlier. Each is a protected one-creature area, so the enemy
\n  scaling already treats it as an arena boss and anchors it to your power as you walk in.
\n  Each rung then steps up ~7% into FIGHT LENGTH — the rank 1 duel runs 1.77x the rank 10
\n  one — and the kill<die guarantee is re-derived from the targets afterwards rather than
\n  hoped for
\n  Scaling both targets in opposite directions does not survive ten rungs: margin 1.5 over
\n  step^1.5 is already under 1.2 by a step of 1.16.

\nWiki. A new \"The rank ladder\" page, after Progression: the formula, what a maxed
\n  character actually measures at, and the ten rungs with their gates and payouts
\nFound, not fixed: the player's real max HP is not the one the balance model uses.
\n  Section 9's hpTrack assigns you.hpmax at the end of the allbuff wrapper, but allbuff
\n  opens with stat_r(), which recomputes hpmax from the base — so the raised value only
\n  survives until the next stat_r, and the tick calls that constantly. Sampled 43 times over
\n  six seconds of real play on a cap-60 character, you.hpmax was 10,050 every time; the
\n  hpTrack value of 184,754 never appeared. Every balance script reads it in the one instant
\n  it is standing, so enemy damage is sized for ~18x the health you really have. Harmless
\n  early, worst in the endgame. Written up in MOD-NOTES and CLAUDE.md; the fix is its own
\n  job, because it moves a number all 2,790 matchups are measured against.

\nTests. tests/ranks.mjs — 31 checks. The wrapper must floor and never cap; the held rank
\n  must survive a save/load; the hall must offer exactly one rung at a time and every line
\n  must fit a 22px row; and all 100 matchups (10 rungs x 10 story tiers) must keep kill<die
\n  with zero whiffs, measured through the game's own dmg_calc. allareas is now 2,790
\n  matchups across 31 areas
\n
\n----MOD 3.1:
\n[2026/09/09]
\nFixed: a hundred Fire Masteries and a hundred Companions Disciplines.
\n  Two skills shared id 2010 — the Companions parent (2000 + section 10,
\n  section 11) and Fire Mastery (2010 + 0, section 29). Ids are the save's key for a
\n  skill and the loader matches them in a loop with no break, so one shared id pushed
\n  BOTH skills onto the sheet for BOTH saved entries. The next save wrote all of them.
\n  It doubled every save/load: 1, 2, 4, 8, 16, 32... A week of play is a hundred of each.
\n  Only Fire Mastery moved (2010 → 2016), so water through dark keep the ids they were
\n  saved under and nobody loses those levels
\n  The Companions parent gets id 2010 back, and with it the level cap that the clash had
\n  been handing to Fire Mastery instead
\nExisting saves are repaired. The duplicates are IN the save, so fixing the id only stopped
\n  it getting worse. The skill list is now de-duplicated on load and the panel redrawn —
\n  a 138-row sheet comes back as 12, with every level intact
\nAlso fixed: The Sunken Hollow and the Pill Tower both had location id 976 (the mod's own,
\n  from MOD 2.9). global.lst_loc is matched the same way, so both would have drawn.
\n  The tower is 980 now
\nTests. tests/ids.mjs — 32 checks. Every namespace (skl, ttl, act, abl, item, wpn, eqp,
\n  sld, acc, chss, area, creature, quest, rcp) must have distinct ids; five save/load cycles
\n  must not grow the sheet; and a deliberately damaged sheet must come back clean.
\n  Nothing checked this before, which is why it shipped.
\n
\nYou can get into the Marketplace again.
\n  The base game's chain: finish at the dojo, then a Paper Boy turns up at the village
\n  center at 40% a visit with a \"Pamphlet\", then you read it for three hours. Exactly one
\n  item in the game sets the flag, and the Paper Boy is retired the moment the Pamphlet is
\n  GIVEN rather than read. MOD 1.x added selling and never marked it a key item — so you
\n  could sell it at the food stand, before ever reading it, and lose the marketplace, its
\n  three shops, the guard-duty quest and the realm 2-5 breakthrough pills permanently.

\n  Key items can no longer be sold. Derived, not listed: the game's own source is scanned
\n  for flags it ever sets back to false, and an item that sets a flag nothing clears is
\n  the only copy of something. Six items protected. A Smoke Bomb sets a flag the game does
\n  clear, so it is state rather than a key, and stays sellable
\n  The Paper Boy comes back if you no longer have a Pamphlet — which is what gets a save
\n  that is already stuck out of the hole, without a console. A fresh character still meets
\n  him exactly once, through the author's own code
\n  And the village center says WHY the door is not there instead of silently omitting the
\n  line: \"finish at the dojo first\", \"read your Pamphlet\", or \"you do not know the way\",
\n  with the full explanation in the tooltip. The Herbalist inside gets the same treatment
\n  Added lines sit above the location's \"<= Return\" and are kept under 50 characters:
\n  .chs is height:22px with no overflow rule, so a choice that wraps spills into the next row.

\nTests. tests/marketplace.mjs — 30 checks, including that every added line fits on one row.
\n
\n----MOD 3.0:
\n[2026/09/09]
\nA wiki. A wiki button on the bottom bar and a \"Game wiki\" row in settings, both
\n  opening a reference in a tab of its own. Eleven pages: Start here, Progression, Skills,
\n  Cultivation, Elemental mastery, Titles, Areas & enemies, Items, Actions, The dojo, Mechanics
\n  It is GENERATED from the live game data every time it is opened, not written out.
\n  A hand-written wiki for 100 skills, 595 titles, 371 items and 19 areas would be wrong
\n  within a week, and a reference nobody can trust is worse than none. Reading skl, item,
\n  area, ttl, act and the mod's own tables means it cannot disagree with the game —
\n  including changes made from the console mid-session.

\n  It reads the save too, so it doubles as a progress sheet: your level in every skill,
\n  which perks you have taken, which titles you hold, your realm and what the next one costs
\n  Everything you have not reached is still listed — it is a wiki, not a fog-of-war map
\n  Groups collapse. With everything listed, three pages ran past 40,000 pixels open;
\n  collapsed, the tallest is 5,800 and Skills opens as ten lines you can take in at once
\n  Search opens the groups that still have matches, since a match hidden inside a
\n  collapsed group reads as no match at all
\n  No fetches and no dependencies — it works from file:// as readily as from a server
\nTwo things found by generating it rather than writing it:
\n  Spawn chances were not what pop[i].c says. z_bake normalises those weights into
\n  area.popc, the [lo,hi] bands mon_gen actually rolls against, and the weights need not
\n  sum to 1. The Southern forest's .35/.45/.25 are really 33/43/24%. The wiki reads the bands
\n  Nothing can ever spawn in the Damp cellar (base game). Its pop entries carry no
\n  weight at all, so z_bake produces NaN bands and no comparison in the spawn loop can be
\n  true. Nothing in the game travels there either. Left as it is and reported in the wiki
\n  rather than quietly fixed — it is the author's unfinished content, like chss.pltwr1 was
\nNot listed as places: \"Somewhere\" (where current_z parks whenever you are not fighting)
\nand \"Test\" (reached only from chss.tst, which has id -1 and is in no sector).

\nTests. tests/wiki.mjs — 57 checks. Coverage is counted from the game rather than
\n  listed, so adding a skill without adding it to the wiki fails the suite automatically
\n
\n----MOD 2.9:
\n[2026/09/08]
\nThe Catacombs are reachable. 26 finished locations — The Mourning Hall, Eleven Wisemen,
\n  Thawing Candles, Tunnel of the Dead — written by the game's author and linked to by nothing
\n  An entrance on the Village Center, which is where their own exit already led
\n  Not a word of it is the mod's; the entrance is the whole change
\n  Opens once you have been to the deep forest, so the level cap ladder keeps its order:
\n  forest 20 → deep forest 30 → catacombs 40 → golem arena 50
\n  This also makes the cap-40 rung live for the first time — nothing could reach it before
\nThe Pill Tower. The author wrote this choice on the Village Center and commented it out;
\n  the location behind it was never built. Now it is
\n  An alchemists' tower that sells the pill grades a village herbalist cannot source,
\n  realms 6–10 and the Sublime and Transcendent Spirit Pills
\n  A spirit vein you can sit in once a day for Qi Circulation exp, scaling with your realm
\n  They do not let mortals past the door — you need a realm to get in
\n
\n\n----MOD 2.8:
\n[2026/09/08]
\nFIXED: the breakthrough pills had no source. All ten existed as items and nothing gave,
\n  dropped or sold them, so the entire realm ladder was unreachable in normal play
\nThree sources now, matching how far along each realm is:
\n  Realm 1 — the instructor hands it over when the dojo finishes teaching you
\n  Realms 2–5 — the Herbalist, who already sells spirit pills (900 to 38,000 copper)
\n  Realms 6–10 — the dojo's Level Advancement rungs at 45, 60, 75, 90 and 105
\n  The first one is deliberately NOT the Herbalist: the marketplace is gated behind the
\n  Paper Boy, a 40% chance per visit, and realm 1 opens long before that
\nThe Herbalist also stocks the Superior and Refined Spirit Pills now
\n
\n\n----MOD 2.7:
\n[2026/09/08]
\nCirculate Qi is unlocked by the dojo now, not by a skill level
\n  Clear the Easiest, Easy and Normal dummies and it is yours — it used to hang off
\n  Temperance 5, a skill you train by throwing your possessions away, which is an obscure
\n  gate for the thing the entire cultivation system is levelled through
\n  Qi Circulation appears on the skill sheet at the same moment
\n  Temperance keeps a perk at level 5 (INT +1, Energy Effectiveness +2%) so existing saves
\n  do not have their milestone flags shift under them
\nFIXED: the bottom bar was 8px taller than it should be, covering part of the panel above it
\n  The mod's three buttons were display:inline-block where the game's own are plain inline;
\n  only an inline-block forces the line box to grow. Bar is back to its original 24px
\n
\n\n----MOD 2.6:
\n[2026/09/08]
\nCultivation realms. The game had Qi Circulation, spirit pills \"made from condensed Ki\",
\n  meridians in its skill text — and no realm to advance through. Now there are seven:
\n  Ten realms above Mortal, on exactly the levels that earn each title rank — so realm 3
\n  is the Qi Circulation level that earns a rank 3 title, and its own title comes out rank 3
\n  Qi Refining · Foundation Establishment · Core Formation · Nascent Soul · Spirit Severing ·
\n  Soul Transformation · Void Refinement · Body Integration · Great Ascension · Tribulation Transcendence
\n  Reaching the Qi Circulation level does NOT advance you — it puts you at a bottleneck
\n  A breakthrough costs a pill, and can fail; training past the requirement improves the odds
\n  Each realm multiplies every stat and widens what the body holds, up to x6 at the top
\n  Ten breakthrough pills, one per realm, and a title for each realm reached
\nElemental mastery. The six Absorption skills were purely defensive — trained by being hit,
\n  and no way to use an element at all. Six Mastery skills to match them:
\n  Fire, Water, Air, Earth, Light and Dark, each with a technique that fires in combat
\n  \"Lantern Splitting Palm\", \"Still Water Draw\", \"Hollow Gale\", \"Settling Weight\",
\n  \"Noon Without Shade\", \"Lamp Going Out\"
\n  They scale with INT and their element rather than with your weapon, and are capped at
\n  45% of strikes so a weapon still matters
\n  Nothing fires below Qi Refining — your channels are not open yet
\nmodRealm() and modMastery() print the ladders
\n
\n\n----MOD 2.5:
\n[2026/09/08]
\nThe mod's changelog moved out of the game's own file and into this one
\n  It had been prepended to changelog/changelog.html above the author's entries, which meant
\n  the mod was editing a file that was not its own. That file is now untouched — the only
\n  change the mod makes to anything of the author's is the single script tag in index.html
\nThe bottom-bar \"changelog\" button opens this file; the version number opens the game's, as it always did
\n  (the version number's link was absolute, \"/changelog/changelog.html\", so it 404'd from a
\n  file:// URL or any subdirectory — it is resolved relative to the page now)
\n
\n\n----MOD 2.4:
\n[2026/09/08]
\nThe dojo's Level Advancement ladder now runs to character level 110
\n  It stopped at 30, though the instructor promises a reward \"after every 5 levels you reach\"
\n  Sixteen more rungs, 35 to 110 in fives, sequential like the original six
\nFour new spirit pill grades, sized against what a level actually costs
\n  Superior 100,000 · Refined 600,000 · Sublime 3,500,000 · Transcendent 20,000,000 EXP
\n  (a level at 110 costs 5,336,100 — the game's best pill was worth 0.3% of one)
\nA third grade of skillbook, +30% weapon mastery exp, offered as a CHOICE at the 50/75/100 rungs
\n  Sword Saint, Nightblade, Headsman, Dragoon, Earthbreaker and Iron Body Manuals
\n  the same shape as the choice the instructor gives you the first time you clear the dummies
\nmodDojo() prints the ladder and what is claimed
\nRepository: a README added, describing the mod, how to disable it, and how to run the tests
\n  (not a game change — logged here because every change gets an entry)
\n
\n\n----MOD 2.3:
\n[2026/09/08]
\nTitle ranks now run 1–10 and are derived from the level that earns them, not assigned by feel
\n  rank 1 at lv 1, rank 3 at 18, rank 5 at 45, rank 7 at 70, rank 9 at 90, rank 10 at 110
\n  (the base game gave out eight rank-3 titles at skill level 8–15 — Runner at 10, Rookie at 15)
\nEvery skill now grants five titles, at levels 25 / 50 / 75 / 90 / 110 — ranks 3 / 5 / 7 / 9 / 10
\n  465 new titles; the pool goes from 120 to 585, and all ten ranks are populated
\nTitles do something now: each raises its own skill's exp rate, 5% at rank 3 up to 30% at rank 10
\n  Only the title you are WEARING applies — which finally gives the selection screen a purpose
\n  At renown level N, every title of rank N or lower applies permanently whether worn or not,
\n  so renown 10 makes the whole collection passive
\nRenown thresholds rescaled to the larger pool: level 10 now wants 550 titles rather than 100
\nmodTitles() prints held/total by rank and which ranks have gone passive; modTitles(n) lists one rank
\nThe title screen groups by skill — one row showing the best you hold, with a caret to open the rest
\n  Base-game titles group with the generated ones, so Rookie and Fighter sit under Fighting too
\n  Story titles have no skill and stay as plain rows below the groups
\nTitles are coloured by rank, in the picker and in the tooltip, and the tooltip states the rank
\n  grey 1, cyan 2, lime 3, yellow 4, orange 5, purple 6, pink 7, red 8, gold 9, white 10
\n  FIXED: the game's own rank 7 tooltip threw an error (it set `this.dl`, which is undefined
\n  in that branch). It had never run because the base game had no title above rank 5 —
\n  there are now 93 at rank 7. Ranks 1, 8, 9 and 10 had no colour at all
\n
\n\n----MOD 2.2:
\n[2026/09/08]
\nPerks now run the whole ladder — every skill has milestones at 10/25/50/60/75/90/110
\n  (447 added; 81 of 94 skills previously gave nothing at all between level 51 and 110)
\nPerk rewards match the skill's type: resistances give resistance, absorptions give elemental defence,
\n  combat gives STR and crit damage, mental gives INT and EXP, gathering gives energy
\nCritical damage from combat perks kept deliberately small — it is shared by 13 skills and compounds,
\n  and at full value a crit reached 23x a normal swing, which made combat a coin flip on crits
\nThe four added actions are earned now, not handed over on load
\n  Toughness 4 → Endurance Drill, Harvesting 4 → Forage,
\n  Temperance 5 → Circulate Qi, Literacy 8 → Practice Calligraphy
\nNew settings checkbox \"Unrestricted actions\": run several actions at once, and start them anywhere
\n  (off by default — both are limits the base game means to impose)
\nThree save slots, with a \"saves\" panel and a \"new save\" button in the bottom bar
\n  Switching saves the slot you are leaving first; \"delete the save\" now deletes only the slot you are in
\nChangelog reachable from a \"changelog\" button in the bottom bar, not just the version number
\n
\n\n----MOD 2.1:
\n[2026/09/08]
\nEnemy scaling rewritten — it had been scaling the wrong fields
\n  An enemy's STR is subtracted from your damage, so scaling it armoured them instead of
\n  lengthening the fight. The tutorial needed ~63,000 swings and killed you in one;
\n  everything from the southern forest onward was kill-in-1 / die-in-1
\nDifficulty is now solved per spawn against your actual power: ~8 swings to kill, ~20 to die
\nEnemies in an area's higher level band are genuinely harder, and each creature keeps its own character
\nArena golems are proper boss fights rather than the softest thing in the game
\n
\n\n----MOD 2.0:
\n[2026/09/07]
\nThe ~100 \"discovered by playing\" skills consolidated to 10, with the same total effect
\nSkill EXP curve replaced so level 110 is reachable in about six months at 1x speed
\n  (the base game's curve made level 109→110 alone cost 1.16e14 xp)
\nThe three added areas now stay hidden until golem arena IV is cleared, then open in order
\nSaves from before this update still load; skills that were merged away simply do not return
\n
\n\n\n"; var s = document.createElement('script'); s.setAttribute('data-p23-mod', "4.7"); s.textContent = "/* ===========================================================================\n Proto23 — local mod\n ---------------------------------------------------------------------------\n Loaded as a plain inside content closing the block early, so that is the\n one thing scrubbed. */\n safe: function (h) {\n return String(h === undefined || h === null ? '' : h).replace(/<\\/script/gi, '<\\\\/script');\n }\n};\n\n/* Descriptions carry real HTML (coloured spans, the game's separator div), so\n they are passed through rather than escaped — but the separator becomes a\n rule, since a wiki row is not a tooltip. */\nfunction MOD_wikiDesc(d) {\n var s;\n try { s = (typeof d === 'function') ? d() : d; } catch (e) { s = ''; }\n if (!s) return '';\n s = String(s).split('
 
').join('
');\n return MOD_WIKI.safe(s);\n}\n\nfunction MOD_wikiNum(n) {\n if (!isFinite(n)) return '—';\n return Math.round(n).toLocaleString();\n}\n\n/* An effect line at a level the player has not reached yet. The effect\n functions read the skill they are handed, so the level is borrowed and put\n back; anything that throws simply gets no projection. */\nfunction MOD_wikiEffectAt(key, sk, lvl) {\n var eff = MOD_EFFECTS[key];\n if (!eff) return '';\n var was = sk.lvl, out = '';\n try { sk.lvl = lvl; out = eff(sk) || ''; }\n catch (e) { out = ''; }\n finally { sk.lvl = was; }\n return MOD_WIKI.safe(out);\n}\n\n/* --- page builders --------------------------------------------------------\n Each returns the inner HTML of one page. `wk-e` marks a searchable entry and\n `wk-g` a group that hides itself when all its entries are filtered out.\n ------------------------------------------------------------------------- */\n\nvar MOD_WIKI_PAGES = [];\n\nfunction MOD_wikiPage(id, label, build) {\n MOD_WIKI_PAGES.push({ id: id, label: label, build: build });\n}\n\n/* A collapsible group.\n\n Not decoration: with every skill, item and title listed, three of the eleven\n pages ran past 40,000 pixels — a hundred screens of scrolling, which is the\n same as not having the content at all. Collapsed, each of those pages opens\n as an index you can read in one screen and expand where you want.\n\n
rather than a click handler, so it still works with JavaScript off,\n the browser's own find can be made to reach inside it, and the summary is\n keyboard-reachable for free. `level` is the heading level for the summary,\n since a group is sometimes a section of a page and sometimes a subsection. */\nfunction MOD_wikiGroup(heading, count, inner, level) {\n var hn = 'h' + (level || 2);\n return '
<' + hn + '>' + heading +\n (count !== null && count !== undefined\n ? ' (' + count + ')' : '') +\n '
' + inner + '
';\n}\n\n/* --- 1. orientation ------------------------------------------------------- */\nMOD_wikiPage('start', 'Start here', function () {\n var cap = MOD_levelCap();\n var realm = MOD_realm();\n var h = '

proto23

' +\n '

An idle RPG by truezhangwei, plus a mod. You train skills by ' +\n 'doing things, the things you can do open up as the story does, and none of ' +\n 'it is in a hurry.

' +\n\n '

Where you are right now

' +\n '' +\n '' +\n '' +\n '' +\n '' +\n '
Character level' + MOD_wikiNum(you.lvl) + '
Skill level cap' + cap + ' — ' +\n MOD_WIKI.safe(MOD_CAP.name) + '
Cultivation realm' + MOD_WIKI.safe(realm.name) +\n ' (' + realm.n + ' of ' + (MOD_REALMS.length - 1) + ')
Titles held' + MOD_titleCount() + ' — renown ' +\n (skl.rnwn ? skl.rnwn.lvl : 0) + '
Coin' + MOD_wikiNum(you.wealth || 0) + '
' +\n\n '

The four things worth knowing early

' +\n '
' +\n '
Your skill levels are capped by the story, not by grinding.
' +\n '
The cap starts at 10 and ends at 110, and it moves when you reach a new ' +\n 'place — not when you earn enough experience. If a skill has stopped ' +\n 'levelling, you are not doing it wrong; you need to go somewhere. See ' +\n 'Progression.
' +\n\n '
Armour is subtraction, so levels matter more than they look.
' +\n '
A hit does the attacker\\'s STR minus the defender\\'s STR. That means a ' +\n 'small gap in strength is a large gap in damage, and a big enough one takes ' +\n 'your damage to nothing at all rather than merely reducing it. See ' +\n 'Mechanics.
' +\n\n '
Most skills have a parent that pulls them along.
' +\n '
Every section has a parent skill that earns a share of everything its ' +\n 'children do, and any child lagging behind the parent trains faster — up to ' +\n 'twice as fast at ' + MOD_PARENT.maxGap + ' levels behind. A neglected skill is ' +\n 'cheap to bring up later, which is the only reason having this many is ' +\n 'workable.
' +\n\n '
Cultivation is a second ladder, and it is not automatic.
' +\n '
Circulating qi levels a skill; the skill does not raise your realm. ' +\n 'Reaching the level opens a bottleneck, and breaking through it costs a pill ' +\n 'and can fail. See Cultivation.
' +\n '
' +\n\n '

What is the mod and what is the game

' +\n '

The game is index.html and it is entirely truezhangwei\\'s ' +\n 'work. Everything in mod.js is a modification of it and is not ' +\n 'endorsed by the author. Pages here mark added content with ' +\n 'mod where the distinction matters. The full ' +\n 'list of changes is in the changelog, on the bottom bar.

';\n return h;\n});\n\n/* --- 2. progression ------------------------------------------------------- */\nMOD_wikiPage('progress', 'Progression', function () {\n var cap = MOD_levelCap();\n var h = '

Progression

' +\n '

Skill levels are capped by how far the story has gone. Ten ' +\n 'rungs, 10 through 110. The cap applies to every skill at once; Renown is ' +\n 'the single exception, since it is counted rather than trained.

' +\n '' +\n '';\n\n MOD_TIERS.forEach(function (t) {\n var got = null;\n try { got = MOD_tierFlag(t); } catch (e) {}\n var here = (t.cap === cap);\n var need = t.needText ? t.needText\n : (t.flags.length ? t.flags.join(' or ') : 'the start of the game');\n h += '' +\n '' +\n '' +\n '' +\n '';\n });\n h += '
CapStageWhat opens it
' + t.cap + '' + MOD_WIKI.safe(t.name) + '' + MOD_WIKI.safe(need) + '' + (here ? 'you are here'\n : got ? 'reached' : '') + '
' +\n\n '

The experience curve

' +\n '

The game\\'s own skill curve is super-exponential — level 109 to 110 ' +\n 'alone costs ' + MOD_wikiNum(MOD_vanillaExpnext(109)) + ' experience, so the top of the ladder ' +\n 'is unreachable by a factor of about ten trillion. But the author only ever ' +\n 'designed skills up to level 15, and below that his curve is fine.

' +\n (MOD_XP.vanillaTo > 0\n ? '

So the mod keeps it. Below level ' + MOD_XP.vanillaTo + ' a skill costs ' +\n 'exactly what the original charges; from there it rises by ×' +\n MOD_XP.ratio.toFixed(4) + ' a level, a ratio solved so the climb to 110 takes ' +\n 'the same time it always did.'\n : '

You have chosen the Mod before 4.2 pacing: a plain geometric curve, ' +\n '×' + MOD_XP.ratio.toFixed(3) + ' a level from level 0, much faster than the ' +\n 'original early on. The Original pacing in settings puts back the ' +\n 'author\\'s own cost below level 10.') +\n ' Character level uses the game\\'s own curve, untouched.

' +\n '' +\n '';\n [1, 5, 9, 10, 15, 25, 50, 75, 90, 109].forEach(function (l) {\n h += '';\n });\n h += '
LevelExperience for the nextOriginal game
' + l +\n '' + MOD_wikiNum(MOD_expnextFor(l)) + '' +\n MOD_wikiNum(MOD_vanillaExpnext(l)) + '
';\n return h;\n});\n\n/* --- 3. skills ------------------------------------------------------------ */\nMOD_wikiPage('skills', 'Skills', function () {\n var cap = MOD_levelCap();\n var bySection = {};\n var keys = [];\n for (var k in skl) {\n var s = skl[k];\n if (!s || typeof s !== 'object' || !s.name) continue;\n keys.push(k);\n }\n keys.forEach(function (k) {\n var s = skl[k];\n var sec = MOD_sectionOf(s);\n (bySection[sec] = bySection[sec] || []).push(k);\n });\n\n var h = '

Skills

' +\n '

' + keys.length + ' skills in ' +\n Object.keys(MOD_SECTIONS).length + ' sections. Every one has perks at ' +\n MOD_LADDER.join(', ') + ', and every one that has milestones grants titles at ' +\n MOD_TITLE_RUNGS.join(', ') + '. Levels and ticks below are yours.

';\n\n Object.keys(MOD_SECTIONS).forEach(function (sec) {\n var list = bySection[sec];\n if (!list || !list.length) return;\n var parentKey = MOD_PARENT_KEY[sec];\n var g = '';\n if (parentKey && skl[parentKey]) {\n g += '

Parent skill: ' +\n MOD_WIKI.safe(skl[parentKey].bname || skl[parentKey].name) +\n ' — earns ' + Math.round(MOD_PARENT.feed * 100) +\n '% of what its children earn, and speeds up any child that lags behind it.

';\n }\n list.sort(function (a, b) { return skl[a].name < skl[b].name ? -1 : 1; });\n list.forEach(function (k) {\n var s = skl[k];\n var lvl = Number(s.lvl) || 0;\n g += '
' +\n '

' + MOD_WIKI.safe(s.bname || s.name) +\n (s.bname ? ' (' + MOD_WIKI.safe(s.name) + ')' : '') +\n '= cap ? ' capped' : '') + '\">lv ' + lvl +\n (lvl >= cap ? ' — at the cap' : '') + '

' +\n '
' + MOD_wikiDesc(s.desc) + '
';\n\n var at110 = MOD_wikiEffectAt(k, s, 110);\n if (at110) g += '
At level 110: ' + at110 + '
';\n\n if (s.mlstn && s.mlstn.length) {\n g += '';\n s.mlstn.forEach(function (m) {\n g += '' +\n '' +\n '' +\n '';\n });\n g += '
' + m.lv + '' + MOD_WIKI.safe(m.p || '—') + '' + (m.g === true ? '✓' : '') + '
';\n }\n g += '
';\n });\n h += MOD_wikiGroup(MOD_WIKI.safe(MOD_SECTIONS[sec]), list.length, g, 2);\n });\n return h;\n});\n\n/* --- 4. cultivation ------------------------------------------------------- */\nMOD_wikiPage('cultivation', 'Cultivation', function () {\n var now = MOD_realm();\n var elig = 0;\n try { elig = MOD_realmEligible().n; } catch (e) {}\n var qic = skl.qic ? skl.qic.lvl : 0;\n\n var h = '

Cultivation

' +\n '

Ten realms above Mortal. Circulating qi levels the Qi ' +\n 'Circulation skill; the skill does not advance the realm. Reaching the level ' +\n 'opens a bottleneck, and breaking through it costs a pill and can fail — the ' +\n 'pill is spent either way.

' +\n '

Where you are

' +\n '' +\n '' +\n '' +\n '' +\n '' +\n '' +\n '
Realm' + MOD_WIKI.safe(MOD_realmTitle()) + '
Spiritual Root' + MOD_WIKI.safe(MOD_root().name) + '
Qi Circulationlevel ' + qic + '
Insight' + MOD_insight() + '
Bottleneck open' +\n (elig > now.n ? 'yes — ' + MOD_WIKI.safe(MOD_REALMS[now.n + 1].name) +\n ' is within reach' : 'no') + '
Qi Deviation' + (MOD_deviated()\n ? MOD_deviationLeft() + ' in-game minutes left' : 'no') + '
' +\n '

Realm thresholds are the same levels that earn a title rank, so realm N ' +\n 'is the level at which a rank N title arrives. Every realm multiplies all ' +\n 'stats and health, and the multipliers do not stack with each other — the ' +\n 'realm you are in is the one that applies.

' +\n '' +\n '';\n\n MOD_REALMS.forEach(function (r) {\n var pill = r.n >= 1 ? item['mod_bp' + r.n] : null;\n h += '' +\n '' +\n '' +\n '' +\n '' +\n '' +\n '';\n });\n h += '
RealmNameQi Circ.StatsHealthPill
' + r.n + '' + MOD_WIKI.safe(r.name) + '
' +\n MOD_WIKI.safe(r.desc) + '
' + r.qic + '×' + r.mult + '×' + r.hp + '' + (pill ? MOD_WIKI.safe(pill.name) : '—') +\n '
' +\n\n '

Where the pills come from

' +\n '

Every breakthrough pill has a source in normal play. None of them are ' +\n 'craftable, and none are cheap — a bottleneck you can buy in bulk is not a ' +\n 'bottleneck.

' +\n '' +\n '' +\n '' +\n '' +\n '
RealmsSource
1The dojo instructor, when qi ' +\n 'circulation is first taught
2–5The Herbalist in the village ' +\n '(vendor.pha1), restocked
6–10The dojo\\'s Level Advancement ' +\n 'ladder, and the Pill Tower
' +\n '

Realm 1 is deliberately not sold at the marketplace: ' +\n 'the marketplace is behind a 40%-a-visit chance encounter, and realm 1 opens ' +\n 'long before that is reliable.

' +\n\n '

Breaking through

' +\n '

A bottleneck wants three things, which is what the genre says it wants: ' +\n 'a pill, insight, and enough consolidation to make the ' +\n 'attempt worth making. The pill and the insight are both spent whether the ' +\n 'attempt works or not.

' +\n '

The odds improve the further past the threshold you are, plus your root, ' +\n 'plus time sat in Closed Door Training, and cap short of certain.

' +\n '' +\n '';\n [0, 2, 4, 6, 8].forEach(function (o) {\n var base = Math.min(0.55 + o * 0.05 + MOD_root().odds, 0.95);\n h += '';\n });\n h += '
Levels past the thresholdChanceAfter full seclusion
+' + o + '' +\n Math.round(base * 100) + '%' +\n Math.round(Math.min(base + MOD_CULT.secludeOdds, 0.95) * 100) + '%
' +\n\n '

Insight

' +\n '

Insight is the half of the price that cannot be bought. It comes from ' +\n 'three places, and they are the three the genre names: meditating, fights you ' +\n 'nearly lost, and going somewhere you have not been.

' +\n '' +\n '' +\n '' +\n '' +\n '' +\n '
SourceRate
Circulate Qi, at a wall' +\n (MOD_CULT.meditateChance * 100).toFixed(1) + '% a second
Closed Door Training' +\n (MOD_CULT.secludeChance * 100).toFixed(1) + '% a second
A kill made under ' +\n Math.round(MOD_CULT.brinkFrac * 100) + '% health — ' +\n (MOD_FIGHTS.mode === 'original'\n ? 'the author\\'s areas rarely come close once you reach them; the three past ' +\n 'the golem arena and the rank duels do, within a few fights, as does an area ' +\n 'you walk into early'\n : 'every fight costs a share of your health, so a few in a row without resting') +\n '' +\n Math.round(MOD_CULT.brinkChance * 100) + '% a kill
Entering an area for the first timealways
' +\n '

The insight a realm asks for is sized to the climb to it: about ' +\n Math.round(MOD_CULT.wallShare * 100) + '% of the Circulate Qi time the climb took, ' +\n 'spent at the wall. Seclusion earns it three times as fast, and full ' +\n 'consolidation takes as long as that on average.

' +\n '' +\n '' +\n '';\n var wkHours = function (sec) {\n return sec >= 7200 ? (sec / 3600).toFixed(sec >= 36000 ? 0 : 1) + ' h' : Math.round(sec / 60) + ' min';\n };\n MOD_REALMS.forEach(function (r) {\n if (!r.n) return;\n h += '' +\n '' +\n '' +\n '' +\n '' +\n '';\n });\n h += '
RealmClimb (Circulate Qi, 1x)Insight to enterFull seclusionQi Deviation (in-game)
' + MOD_WIKI.safe(r.name) + '' + wkHours(MOD_realmClimbSec(r.n)) + '' + MOD_insightNeed(r.n) + '' + wkHours(MOD_secludeFull(r.n)) + '' + (MOD_devMinutes(r.n) / 1440).toFixed(1) + ' days
' +\n\n '

The nine layers

' +\n '

Each realm is nine layers deep, derived from where Qi Circulation sits ' +\n 'between this realm\\'s threshold and the next one\\'s — so breaking through ' +\n 'puts you at the first layer of the new realm on its own. Layers are worth ' +\n '×' + MOD_CULT.layerMult + ' stats and ×' + MOD_CULT.layerBody +\n ' body each above the first, and the top realm has no rung above it to ' +\n 'measure against, so it stays at its first layer.

' +\n '' +\n '';\n for (var li = 1; li <= MOD_CULT.layers; li++) {\n h += '';\n }\n h += '
LayerCalled
' +\n li + '' + MOD_LAYER_ORD[li] + ' layer, ' +\n MOD_WIKI.safe(MOD_layerStage(li)) + '
' +\n '

At the threshold of the next realm and before breaking ' +\n 'through, you are a half step to it — the genre\\'s own name for the ' +\n 'state this mod calls a bottleneck.

' +\n\n '

Closed Door Training

' +\n '

Seclusion does two things. It consolidates — a full sitting ' +\n '(the table above) is worth +' + Math.round(MOD_CULT.secludeOdds * 100) +\n '% on the attempt — and it shelters: a breakthrough attempted while ' +\n 'you are still sitting in it cannot end in Qi Deviation, only in a lost ' +\n 'pill. It will not start unless you are actually at a wall.

' +\n\n '

Qi Deviation

' +\n '

Failing a breakthrough out in the open leaves your cultivation base ' +\n 'unstable for as long as the seclusion you skipped (the table above, never ' +\n 'under ' + MOD_CULT.devMinutes + ' in-game minutes): stats at ×' +\n MOD_CULT.devStat + ', body at ×' + MOD_CULT.devBody + ', and no second ' +\n 'attempt until it passes. It clears on its own, or at once with a ' +\n 'Qi Settling Pill. It never kills — the death system stays out of it.

' +\n\n '

Heavenly Tribulation

' +\n '

From realm ' + MOD_CULT.tribFrom + ' up, a successful breakthrough is ' +\n 'answered. The cost is a fraction of your maximum health, so it is a gate on ' +\n 'walking in healthy rather than a damage race; the realm bonus you are ' +\n 'standing on is what pays it. Fall to it and the realm does not open.

' +\n '' +\n '';\n MOD_REALMS.forEach(function (r) {\n if (!r.n || r.n < MOD_CULT.tribFrom) return;\n h += '' +\n '' +\n '';\n });\n h += '
RealmBoltsOf your max HP
' + MOD_WIKI.safe(r.name) + '' + MOD_tribulationBolts(r.n) + '' + Math.round(MOD_tribulationCost(r.n) * 100) + '%
' +\n\n '

Spiritual Root

' +\n '

Rolled once, when the dojo finishes teaching you to fight. It is never a ' +\n 'dead end: the worst grade costs cultivation speed, not access, and a ' +\n 'Root Cleansing Pill moves you up a grade.

' +\n '' +\n '';\n var rootTotal = 0;\n MOD_ROOTS.forEach(function (r) { rootTotal += r.weight; });\n MOD_ROOTS.forEach(function (r) {\n h += '' +\n '' +\n '' +\n '' +\n '' +\n '';\n });\n h += '
GradeRootChanceCultivationBreakthrough
' + r.n + '' + MOD_WIKI.safe(r.name) + '
' +\n MOD_WIKI.safe(r.desc) + '
' + Math.round(r.weight / rootTotal * 100) + '%×' + r.xp + '' + (r.odds >= 0 ? '+' : '') +\n Math.round(r.odds * 100) + '%
';\n return h;\n});\n\n/* --- 5. elemental mastery ------------------------------------------------- */\nMOD_wikiPage('mastery', 'Elemental mastery', function () {\n var h = '

Elemental mastery mod

' +\n '

Six masteries, each with a technique that fires on its own ' +\n 'during a fight. Combat is automatic and there is no ability picker, so a ' +\n 'technique is a chance on each of your swings to become something else — one ' +\n 'that scales with INT and the element rather than with your weapon.

' +\n '

Nothing fires while you are still Mortal: the channels have to be open ' +\n 'first. Chance is ' + (MOD_TECH.perLevel * 100).toFixed(1) + '% per mastery ' +\n 'level plus ' + (MOD_TECH.perRealm * 100).toFixed(1) + '% per realm, capped at ' +\n Math.round(MOD_TECH.cap * 100) + '%; a technique lands for ×' +\n MOD_TECH.power + ' of a normal strike.

' +\n '' +\n '';\n\n var realm = 0;\n try { realm = MOD_realm().n; } catch (e) {}\n MOD_MASTERY.forEach(function (m) {\n var lvl = Number(m.skill.lvl) || 0;\n var ch = realm < 1 ? 0\n : Math.min(lvl * MOD_TECH.perLevel + realm * MOD_TECH.perRealm, MOD_TECH.cap);\n h += '' +\n '' +\n '' +\n '';\n });\n h += '
MasteryTechniqueLevelChance now
' +\n MOD_WIKI.safe(m.skill.name) + '' + MOD_WIKI.safe(m.name) + '' + lvl + '' + (realm < 1 ? 'Mortal'\n : (ch * 100).toFixed(1) + '%') + '
';\n return h;\n});\n\n/* --- 6. titles ------------------------------------------------------------ */\nMOD_wikiPage('titles', 'Titles', function () {\n var perm = MOD_permanentRank();\n var held = MOD_titleCount();\n var total = 0; for (var kk in ttl) if (ttl[kk] && ttl[kk].name) total++;\n\n var h = '

Titles

' +\n '

A title\\'s rank runs 1 to 10 and is derived from the level ' +\n 'that earns it, so a rank is a statement about where in the game you are. ' +\n 'Only the title you are wearing applies — until renown makes the lower ranks ' +\n 'permanent.

' +\n '

Where you are

' +\n '' +\n '' +\n '' +\n '
Titles held' + held + ' of ' + total + '
Renown level' + (skl.rnwn ? skl.rnwn.lvl : 0) +\n ' of ' + MOD_RENOWN_MAX + '
Ranks that apply without wearing' +\n (perm >= 1 ? '1–' + perm : 'none yet') + '
' +\n\n '

Ranks

' +\n '';\n MOD_RANK_AT.forEach(function (lv, i) {\n var st = MOD_rankStyle(i + 1);\n h += '' +\n '';\n });\n h += '
RankEarned from levelColour
' + (i + 1) + '' + lv + '' + '●●●' + '
' +\n\n '

Renown

' +\n '

Renown is not trained. It counts the titles you hold, and each level ' +\n 'multiplies every stat by ' + (1 + MOD_RENOWN_RATE) + ' — compounding, so ' +\n 'level ' + MOD_RENOWN_MAX + ' is ×' +\n (Math.round(MOD_renownMult(MOD_RENOWN_MAX) * 1000) / 1000) + '. At renown N, ' +\n 'every title of rank N or below applies permanently.

' +\n '' +\n '';\n MOD_RENOWN_STEPS.forEach(function (n, i) {\n h += '= n ? 'done' : '') + '\">' +\n '';\n });\n h += '
RenownTitles neededAll statsPassive ranks
' + (i + 1) +\n '' + n + '×' +\n (Math.round(MOD_renownMult(i + 1) * 1000) / 1000) + '1–' + (i + 1) + '
' +\n\n '

Every title

' +\n '

Grouped by the skill that grants it. What is left is either ' +\n 'a ladder of its own — creatures killed, items gathered, the ' +\n 'cultivation realms — or a one-off from the story.

';\n\n var groups = {}, fams = {}, loose = [];\n for (var k in ttl) {\n var t = ttl[k];\n /* Unlike the picker, the wiki KEEPS the author's broken entries and says\n so — same call as area.clg, which is listed and tagged \"nothing spawns\"\n rather than quietly dropped. A reader looking for why a title never\n appears is better served by the row than by its absence. */\n if (!t || t.name === undefined || t.name === null) continue;\n var sk = MOD_TITLE_SKILL[k];\n if (sk && skl[sk]) { (groups[sk] = groups[sk] || []).push(k); continue; }\n var stem = MOD_titleStem(k);\n if (stem) { (fams[stem] = fams[stem] || []).push(k); continue; }\n loose.push(k);\n }\n /* Same rule as the picker: a family of one is not a family. */\n Object.keys(fams).forEach(function (stem) {\n if (fams[stem].length < 2) { loose.push(fams[stem][0]); delete fams[stem]; }\n });\n var order = Object.keys(groups).sort(function (a, b) {\n return skl[a].name < skl[b].name ? -1 : 1;\n });\n\n var row = function (k) {\n var t = ttl[k];\n var st = MOD_rankStyle(t.rar);\n var gen = null;\n for (var i = 0; i < MOD_SKILL_TITLES.length; i++) {\n if (MOD_SKILL_TITLES[i].key === k) { gen = MOD_SKILL_TITLES[i]; break; }\n }\n /* The author left a handful unfinished: ttl.ddcd carries the literal string\n \"null\" for both name and description, and shpt2/shpt3/mone3 are blank.\n None is granted by anything, so none can be earned. */\n var broken = !t.name || t.name === 'null';\n return '
' +\n '' +\n MOD_WIKI.safe(t.name || '(unnamed)') + '' +\n (broken ? 'unfinished' : '') +\n 'rank ' + (t.rar || 1) + '' +\n (gen ? 'lv ' + gen.rung + ' · +' +\n Math.round(gen.xp * 100) + '% ' + MOD_WIKI.safe(skl[gen.skill].name) +\n ' exp' : '') +\n (t.have === true ? 'held' : '') +\n '
' + MOD_wikiDesc(t.desc) + '
';\n };\n\n var byRank = function (a, b) { return (ttl[a].rar || 1) - (ttl[b].rar || 1); };\n order.forEach(function (sk) {\n groups[sk].sort(byRank);\n h += MOD_wikiGroup(MOD_WIKI.safe(skl[sk].bname || skl[sk].name),\n groups[sk].length, groups[sk].map(row).join(''), 3);\n });\n /* The ladders that belong to no skill, by the same derivation the picker\n uses — so the two cannot drift apart. Sorted by the label, since that is\n what the reader scans. */\n var famList = Object.keys(fams)\n .map(function (stem) {\n fams[stem].sort(byRank);\n return { stem: stem,\n label: MOD_titleFamilyLabel(stem, fams[stem].map(function (k) { return ttl[k]; })) };\n })\n .sort(function (a, b) { return a.label < b.label ? -1 : a.label > b.label ? 1 : 0; });\n if (famList.length) {\n /* Nested one level down rather than sitting beside the skills. Eleven more\n top-level summaries cost ~530px on a page that is already the tallest in\n the wiki, and these are a different KIND of thing from a skill anyway. */\n var famInner = '', famTotal = 0;\n famList.forEach(function (f) {\n famTotal += fams[f.stem].length;\n famInner += MOD_wikiGroup(MOD_WIKI.safe(f.label), fams[f.stem].length,\n fams[f.stem].map(row).join(''), 4);\n });\n h += MOD_wikiGroup('Ladders of their own', famTotal, famInner, 3);\n }\n if (loose.length) {\n loose.sort(byRank);\n /* The story titles are the one group with no skill to divide them, and\n there are enough of them to need dividing. Rank rather than the alphabet:\n it is the axis they are already sorted on, and the one a reader cares\n about — a rank is a statement about where in the game a title comes from. */\n var inner;\n if (loose.length <= MOD_WIKI_ALPHA_AT) {\n inner = loose.map(row).join('');\n } else {\n var byRankNo = {};\n loose.forEach(function (k) {\n var r = Math.min(Math.max(Number(ttl[k].rar) || 1, 1), 10);\n (byRankNo[r] = byRankNo[r] || []).push(k);\n });\n inner = '';\n Object.keys(byRankNo).sort(function (a, b) { return a - b; }).forEach(function (r) {\n var st = MOD_rankStyle(r);\n inner += MOD_wikiGroup('Rank ' + r + '', byRankNo[r].length,\n byRankNo[r].map(row).join(''), 4);\n });\n }\n h += MOD_wikiGroup('From the story', loose.length, inner, 3);\n }\n return h;\n});\n\n/* --- 7. areas ------------------------------------------------------------- */\nMOD_wikiPage('areas', 'Areas & enemies', function () {\n var h = '

Areas & enemies

' +\n '

Every area, what lives in it and at what levels, and what it ' +\n 'drops. Enemy strength is not read off these levels — see the note below the ' +\n 'table.

';\n\n /* Two of the game's 21 areas are not places and listing them misleads:\n nwh \"Somewhere\" — where global.current_z parks whenever you are NOT in\n a fight, populated by creature.default (\"Nothing\").\n tst \"Test\" — reached only from chss.tst, which has id -1 and is in no\n sector, so nothing in the game can travel there.\n The test for \"is this a place\" is whether you can get to it, and neither\n can be. Everything else is listed whether or not you have been. */\n var MOD_WIKI_NOT_PLACES = { nwh: 1, tst: 1 };\n\n var keys = [];\n for (var k in area) {\n if (area[k] && area[k].pop && !MOD_WIKI_NOT_PLACES[k]) keys.push(k);\n }\n /* Ordered by the middle of the level band, which is the closest thing the\n game has to a difficulty order. */\n keys.sort(function (a, b) {\n var mid = function (z) {\n if (!z.pop.length) return 0;\n var s = 0; z.pop.forEach(function (e) { s += (e.lvlmin + e.lvlmax) / 2; });\n return s / z.pop.length;\n };\n return mid(area[a]) - mid(area[b]);\n });\n\n keys.forEach(function (k) {\n var z = area[k];\n /* An area whose baked bands are not finite can never spawn anything — see\n the note rendered under its table. Derived rather than listed by name, so\n it stays true if the game changes. */\n var broken = !z.popc || z.popc.length !== z.pop.length ||\n z.popc.some(function (band) { return !band || !isFinite(band[1] - band[0]); });\n h += '

' + MOD_WIKI.safe(z.name) +\n ' ' + k + '' +\n (broken ? 'nothing spawns' : '') +\n '' + (z.size === -1 ? 'endless' : z.size + ' to clear') +\n '

';\n if (z.pop.length) {\n /* The spawn share is NOT pop[i].c. z_bake normalises those weights into\n area.popc — a list of [lo,hi] bands that mon_gen rolls a uniform\n random against — and the weights do not have to sum to 1, so they are\n off wherever they don't. The Southern forest's .35/.45/.25 are really\n 33/43/24%. Read the bands the game actually rolls against. */\n var bands = z.popc || [];\n h += '';\n z.pop.forEach(function (e, i) {\n var c = e.crt || {};\n var band = bands[i];\n var share = band ? band[1] - band[0] : NaN;\n h += '' +\n '' +\n '';\n });\n h += '
' + MOD_WIKI.safe(c.name || '?') + 'lv ' + e.lvlmin +\n (e.lvlmax !== e.lvlmin ? '–' + e.lvlmax : '') + '' + (isFinite(share)\n ? Math.round(share * 100) + '% of spawns'\n : '—') + '
';\n if (broken) {\n h += '
Nothing spawns here. The area\\'s entries carry ' +\n 'no spawn weight, so the table the game rolls against comes out as ' +\n 'NaN and no creature can ever match it. A bug in the base game, left ' +\n 'as it is — nothing in the game travels to this area either.
';\n }\n }\n if (z.drop && z.drop.length) {\n var d = z.drop.map(function (x) {\n return MOD_WIKI.safe((x.item && x.item.name) || '?') +\n ' ' + ((x.c || 0) * 100).toFixed(2) + '%';\n }).join(', ');\n h += '
Drops: ' + d + '
';\n }\n h += '
';\n });\n\n var fightsOrig = MOD_FIGHTS.mode === 'original';\n h += '

How hard an enemy is

' +\n '

It depends on the Fights setting, which is ' +\n (fightsOrig ? 'Original' : 'Scaled') + ' now. On Original (the default) ' +\n 'every area the author wrote is exactly as he wrote it: his creatures, his ' +\n 'level ranges, and you out-grow them as you do in his game. On Scaled every ' +\n 'area is sized to you. The three areas past the golem arena and the rank ' +\n 'duels are sized to you either way.

' +\n '

Sizing to you works like this. Enemies are not fitted to a level. Each spawn is solved against your ' +\n 'actual power at the moment it appears, aiming at roughly ' + MOD_ENEMY.kill +\n ' swings to kill it and ' + MOD_ENEMY.die + ' before it kills you, with a ' +\n 'guaranteed margin between the two so a fight is never a coin flip. Variety ' +\n 'comes from the area\\'s level band and each creature\\'s own stat weighting, ' +\n 'not from difficulty scaling.

' +\n '

The reason for that: within the first stage alone you go from 1 STR to ' +\n 'around 265, so anything anchored to a level is either trivial at the end of ' +\n 'a stage or impossible at the start of one.

';\n return h;\n});\n\n/* --- 8. items -------------------------------------------------------------\n The one place where \"list everything\" needs help: 321 of the game's 371\n plain items are the same inventory type, so grouping by type alone leaves a\n single group of 321. Anything past MOD_WIKI_ALPHA_AT gets an alphabetical\n sub-split as well — the oldest index pattern there is, and derived rather\n than invented, which a hand-made taxonomy would not be.\n ------------------------------------------------------------------------- */\n\nvar MOD_WIKI_ALPHA_AT = 60;\n\n/* The game's own inventory tabs (isort): ALL / WPN / EQP / USE / OTHER. Using\n its categories rather than new ones means a group here is a tab there. */\nvar MOD_WIKI_STYPE = { 2: 'Weapons', 3: 'Equipment', 4: 'Usable', 5: 'Other' };\n\nfunction MOD_wikiItemRow(it) {\n var val = it.v !== undefined ? it.v : (it.val !== undefined ? it.val : null);\n return '
' +\n '

' + MOD_WIKI.safe(it.name) +\n (it.rar > 1 ? 'rarity ' + it.rar + '' : '') +\n (val !== null ? '' + MOD_wikiNum(val) + ' coin' : '') +\n (it.amount > 0 ? '×' + it.amount + '' : '') +\n '

' + MOD_wikiDesc(it.desc) + '
';\n}\n\n/* The letter a name files under. Several items are quoted — the master's\n manuals are literally named \"Sword Saint Manual\" — and filing those under a\n quotation mark is no use to anyone looking for S. */\nfunction MOD_wikiLetter(name) {\n var m = String(name || '').replace(/^[^0-9A-Za-z]+/, '');\n return (m.charAt(0) || '?').toUpperCase();\n}\n\nfunction MOD_wikiByLetter(a, b) {\n var x = MOD_wikiLetter(a.name) + String(a.name || '').toLowerCase();\n var y = MOD_wikiLetter(b.name) + String(b.name || '').toLowerCase();\n return x < y ? -1 : (x > y ? 1 : 0);\n}\n\n/* Flat below the threshold, alphabetically sub-grouped above it. Ranges are cut\n from the entries actually present, so they stay even as content is added. */\nfunction MOD_wikiAlpha(entries, render, level) {\n if (entries.length <= MOD_WIKI_ALPHA_AT) return entries.map(render).join('');\n var per = Math.ceil(entries.length / Math.ceil(entries.length / 40));\n var out = '';\n for (var i = 0; i < entries.length; i += per) {\n var chunk = entries.slice(i, i + per);\n var first = MOD_wikiLetter(chunk[0].name);\n var last = MOD_wikiLetter(chunk[chunk.length - 1].name);\n out += MOD_wikiGroup(first === last ? first : first + '–' + last,\n chunk.length, chunk.map(render).join(''), level || 4);\n }\n return out;\n}\n\nMOD_wikiPage('items', 'Items', function () {\n var h = '

Items

' +\n '

Everything the game defines, whether or not you have seen ' +\n 'it. Grouped the way your inventory groups it. Values are the base sell ' +\n 'price where the game gives one.

';\n\n /* item[] first, split by the inventory tab it appears under. */\n var buckets = {};\n for (var k in item) {\n var it = item[k];\n if (!it || !it.name || it.name === 'dummy') continue;\n var lab = MOD_WIKI_STYPE[it.stype] || 'Uncategorised';\n (buckets[lab] = buckets[lab] || []).push(it);\n }\n ['Usable', 'Other', 'Weapons', 'Equipment', 'Uncategorised'].forEach(function (lab) {\n var list = buckets[lab];\n if (!list || !list.length) return;\n list.sort(MOD_wikiByLetter);\n h += MOD_wikiGroup(lab, list.length,\n MOD_wikiAlpha(list, MOD_wikiItemRow, 4), 2);\n });\n\n /* and the equipment objects, which live in their own globals */\n [['Weapons', wpn], ['Armour', eqp], ['Shields', sld], ['Accessories', acc]]\n .forEach(function (g) {\n var list = [];\n for (var kk in g[1]) {\n if (g[1][kk] && g[1][kk].name && g[1][kk].name !== 'dummy') list.push(g[1][kk]);\n }\n if (!list.length) return;\n list.sort(MOD_wikiByLetter);\n h += MOD_wikiGroup(g[0], list.length,\n MOD_wikiAlpha(list, MOD_wikiItemRow, 4), 2);\n });\n return h;\n});\n\n/* --- 9. actions ----------------------------------------------------------- */\nMOD_wikiPage('actions', 'Actions', function () {\n var unlock = {};\n MOD_ACTION_UNLOCKS.forEach(function (u) {\n if (u.act) unlock[u.act.id] = (skl[u.skill] ? skl[u.skill].name : u.skill) +\n ' level ' + u.lv;\n });\n if (typeof act.mod_qi !== 'undefined') {\n unlock[act.mod_qi.id] = 'clearing the dojo\\'s Easiest, Easy and Normal fights';\n }\n\n var h = '

Actions

' +\n '

What you can be doing. One at a time, and only where the ' +\n 'action makes sense — unless \"Unrestricted actions\" is ticked in settings, ' +\n 'which lets several run at once anywhere. Both limits are deliberate; the ' +\n 'switch is there because they are not to everyone\\'s taste.

';\n\n var keys = [];\n for (var k in act) if (act[k] && act[k].name && act[k].name !== 'dummy') keys.push(k);\n keys.sort(function (a, b) { return act[a].name < act[b].name ? -1 : 1; });\n\n keys.forEach(function (k) {\n var a = act[k];\n var how = unlock[a.id];\n h += '
' +\n '

' + MOD_WIKI.safe(a.name) +\n (a.have === true ? 'unlocked'\n : 'locked') +\n (how ? 'from ' + MOD_WIKI.safe(how) + '' : '') +\n '

' + MOD_wikiDesc(a.desc) + '
';\n });\n return h;\n});\n\n/* --- 10. the dojo --------------------------------------------------------- */\nMOD_wikiPage('dojo', 'The dojo', function () {\n var h = '

The dojo

' +\n '

The instructor promises a reward every five levels. The base ' +\n 'game stops delivering at 30; the mod carries the same ladder to 110, on the ' +\n 'same terms.

' +\n '

Only the lowest rung you have not claimed is offered, which is how the ' +\n 'first six behave. Rewards are sized against the character experience curve ' +\n '(4×level3 + level2) — a level at 110 costs 5.3 ' +\n 'million, which is why the base game\\'s best pill is worth 0.3% of one there.

' +\n '' +\n '';\n\n MOD_DOJO_RUNGS.forEach(function (r) {\n var pill = item[r.pill];\n var extra = [];\n if (r.realmPill) extra.push('breakthrough pill for realm ' + r.realmPill);\n if (r.manual) extra.push('a choice of master\\'s manual');\n h += '' +\n '' +\n '' +\n '' +\n '' +\n '';\n });\n h += '
LevelRewardCoinAlso
' + r.lv + '' + (pill ? MOD_WIKI.safe(pill.name) + ' ×' + r.n\n : '—') + '' + MOD_wikiNum(r.coin) + '' + (extra.join(', ') || '—') + '' + (global.flags[r.flag] ? 'claimed' : '') +\n '
' +\n\n '

Spirit pills

' +\n '

Character experience in a bottle. The four the mod adds continue the ' +\n 'game\\'s own series up to the new curve.

' +\n '' +\n '';\n MOD_PILLS.forEach(function (p) {\n h += '';\n });\n h += '
PillExperience
' + MOD_WIKI.safe(p[2]) + '' +\n p[3].toLocaleString() + '
' +\n\n '

Master\\'s manuals

' +\n '

A third grade of skillbook, offered at levels 50, 75 and 100 as a choice ' +\n 'the same way the first one was. Each is worth +' +\n Math.round(MOD_MANUAL_RATE * 100) + '% experience in its skill, permanently.

' +\n '' +\n '';\n MOD_MANUALS.forEach(function (m) {\n h += '';\n });\n h += '
ManualSkill
' + MOD_WIKI.safe(m[2]) + '' +\n MOD_WIKI.safe(skl[m[0]] ? (skl[m[0]].bname || skl[m[0]].name) : m[0]) +\n '
';\n return h;\n});\n\n/* --- 11. mechanics -------------------------------------------------------- */\nMOD_wikiPage('mechanics', 'Mechanics', function () {\n return '

Mechanics

' +\n\n '

Damage is subtractive, in both directions

' +\n '

A hit does the attacker\\'s strength minus the defender\\'s strength, plus ' +\n 'the weapon, plus one, before criticals. A defender\\'s STR is their ' +\n 'armour — there is no separate armour stat doing that job.

' +\n '

Two consequences worth carrying around:

' +\n '' +\n '

This is also the reason enemies are never scaled by raising ' +\n 'their strength: it would raise their offence and their armour together, and ' +\n 'the second effect arrives first.

' +\n\n '

Criticals carry the late game

' +\n '

Critical rate reaches about a third at the top of the ladder and a ' +\n 'critical is roughly eight times a normal swing, so around three quarters of ' +\n 'all damage done at that point is critical damage. Anything that raises ' +\n 'critical rate or critical damage is worth more than it looks; Sharp Eye and ' +\n 'War are the two that do.

' +\n\n '

Hit chance

' +\n '

Landing a hit is a contest of agility. It is a ratio rather than a ' +\n 'subtraction, so agility behaves more gently than strength does — falling ' +\n 'behind on it costs you accuracy, not everything.

' +\n\n '

Skill sections and parents

' +\n '

Each of the ' + Object.keys(MOD_SECTIONS).length + ' sections has a parent ' +\n 'skill. A parent earns ' + Math.round(MOD_PARENT.feed * 100) + '% of everything ' +\n 'its children earn and grants no stats of its own. In return, a child below ' +\n 'the parent\\'s level trains ' + Math.round(MOD_PARENT.perLevel * 100) +\n '% faster per level of lag, up to ' + MOD_PARENT.maxGap + ' levels behind. ' +\n 'Bringing one section up makes every skill in it cheaper to catch up.

' +\n\n '

Saving

' +\n '

Three slots, switched from the bottom bar. The game builds its world once ' +\n 'at startup and cannot unload a save, so switching slots or starting a new ' +\n 'game reloads the page. Exporting a save before running anything unusual is ' +\n 'always the safe move.

';\n});\n\n/* --- the document ---------------------------------------------------------\n One string, no fetches, no dependencies: this has to work from file:// as\n readily as from a server.\n ------------------------------------------------------------------------- */\n\nfunction MOD_wikiCss() {\n return [\n ':root{color-scheme:dark}',\n '*{box-sizing:border-box}',\n /* Menlo and Consolas before the generic: with MS Gothic missing (any Mac)\n \"monospace\" is Courier, which Safari sets wider than Chrome does, and\n table cells wrapped on one and not the other. */\n 'body{margin:0;background:#060a1c;color:#dfe6f5;font:14px/1.6 \"MS Gothic\",' +\n '\"MS Mincho\",Menlo,Consolas,\"DejaVu Sans Mono\",monospace;display:flex;min-height:100vh}',\n 'a{color:#7cb0ff}',\n 'nav{width:210px;flex:0 0 210px;background:#081040;border-right:1px solid #46a;' +\n 'position:sticky;top:0;height:100vh;overflow:auto;padding:14px 0}',\n 'nav h2{font-size:13px;color:#9fb4d8;margin:0 0 10px 14px;letter-spacing:1px}',\n 'nav a{display:block;padding:6px 14px;text-decoration:none;color:#cfe0ff;' +\n 'border-left:3px solid transparent}',\n 'nav a:hover{background:#154080}',\n 'nav a.on{background:#123070;border-left-color:#7cb0ff;color:#fff}',\n 'nav .srch{margin:0 10px 12px;width:calc(100% - 20px);background:#050a20;' +\n 'color:#dfe6f5;border:1px solid #46a;padding:5px;font:inherit}',\n 'nav .xall{display:block;margin:12px 10px 0;color:#8296b8;font-size:12px;' +\n 'cursor:pointer;-webkit-user-select:none;user-select:none}',\n 'nav .xall input{vertical-align:-1px;margin-right:4px}',\n 'details.wk-g{border:1px solid #17224a;background:#0a1130;margin:8px 0}',\n 'details.wk-g>summary{cursor:pointer;padding:7px 12px;list-style:none;' +\n 'background:#0d1740}',\n 'details.wk-g>summary:hover{background:#154080}',\n 'details.wk-g>summary::-webkit-details-marker{display:none}',\n // a caret drawn rather than borrowed, so it is the same in every browser\n 'details.wk-g>summary::before{content:\"\\\\25B8\";color:#7cb0ff;' +\n 'display:inline-block;width:1em;transition:transform .12s}',\n 'details.wk-g[open]>summary::before{transform:rotate(90deg)}',\n 'details.wk-g>summary h2,details.wk-g>summary h3{display:inline;margin:0;' +\n 'font-size:15px;color:#fff}',\n '.wk-gi{padding:2px 12px 10px}',\n 'details.wk-g details.wk-g>summary h4{display:inline;margin:0;font-size:13px;' +\n 'color:#cfe0ff;font-weight:normal}',\n 'details.wk-g details.wk-g>summary{padding:5px 10px;background:#0b1435}',\n 'main{flex:1;min-width:0;padding:22px 30px 80px;max-width:1000px}',\n 'h1{font-size:26px;margin:0 0 6px;color:#fff;border-bottom:1px solid #46a;' +\n 'padding-bottom:8px}',\n 'h2{font-size:19px;color:#bcd4ff;margin:30px 0 8px}',\n 'h3{font-size:15px;color:#fff;margin:0 0 4px}',\n 'p.lede{color:#a8bcdc;margin:0 0 18px}',\n '.dim{color:#8296b8;font-weight:normal}',\n '.flav{font-size:12px;font-style:italic}',\n '.note{background:#0b1638;border-left:3px solid #46a;padding:8px 12px;' +\n 'color:#a8bcdc;margin:14px 0}',\n 'code,pre{background:#0b1638;border:1px solid #24356e;padding:1px 5px;' +\n 'color:#9fd0ff}',\n 'pre{padding:10px;overflow-x:auto}',\n 'dl dt{color:#fff;margin-top:14px}',\n 'dl dd{margin:2px 0 0 0;color:#a8bcdc}',\n 'ul{color:#a8bcdc}',\n 'table.wk-tbl{border-collapse:collapse;width:100%;margin:10px 0;' +\n 'display:block;overflow-x:auto}',\n 'table.wk-tbl th{text-align:left;color:#9fb4d8;font-weight:normal;' +\n 'border-bottom:1px solid #46a;padding:5px 9px;white-space:nowrap}',\n 'table.wk-tbl td{border-bottom:1px solid #17224a;padding:5px 9px;' +\n 'vertical-align:top}',\n 'table.wk-tbl tr.done td{color:#cfe0ff}',\n 'table.wk-tbl tr.here{background:#123070}',\n '.num{text-align:right;white-space:nowrap;font-variant-numeric:tabular-nums}',\n '.wk-e{border:1px solid #17224a;background:#081040;padding:9px 12px;' +\n 'margin:7px 0;border-radius:2px}',\n 'tr.wk-e{border:0;background:none}',\n '.wk-e.done{border-color:#2a4a8c}',\n '.wk-e h3{display:flex;flex-wrap:wrap;gap:8px;align-items:baseline}',\n '.lv{margin-left:auto;font-size:12px;color:#8296b8;font-weight:normal}',\n '.lv.capped{color:#ffd24a}',\n '.rank{font-size:12px;color:#8296b8;font-weight:normal}',\n '.desc{color:#a8bcdc;font-size:13px}',\n '.proj{color:#ffd24a;font-size:12px;margin-top:3px}',\n '.tag{font-size:11px;border:1px solid #46a;color:#9fb4d8;padding:0 5px;' +\n 'border-radius:2px;font-weight:normal}',\n '.tag.mod{border-color:#7a4ea8;color:#c07bff}',\n '.tag.done{border-color:#2e7d4f;color:#6fdc9a}',\n '.tag.here{border-color:#ffd24a;color:#ffd24a}',\n '.tag.warn{border-color:#a8562e;color:#ff9a5c}',\n 'table.wk-perks{border-collapse:collapse;margin:6px 0 0;width:100%}',\n 'table.wk-perks td{padding:2px 8px 2px 0;font-size:12px;color:#8296b8;' +\n 'border-bottom:1px solid #101a3c}',\n 'table.wk-perks tr.done td{color:#cfe0ff}',\n 'table.wk-perks td.tick{color:#6fdc9a;width:1em;text-align:right}',\n // fixed columns so the level bands line up between one area and the next\n 'table.wk-pop{table-layout:fixed}',\n 'table.wk-pop td:first-child{width:60%}',\n 'table.wk-pop td:nth-child(2){width:20%}',\n '.wk-title .tname{font-weight:bold}',\n '.wk-now{background:#0b1638;border:1px solid #24356e;padding:10px 14px;' +\n 'margin:0 0 18px;display:inline-block;min-width:320px}',\n '.wk-now h3{color:#9fb4d8;font-weight:normal;font-size:13px;' +\n 'letter-spacing:1px;margin-bottom:6px}',\n '.wk-now td{padding:2px 16px 2px 0}',\n '.wk-now td:last-child{color:#fff;text-align:right}',\n '.pg{display:none}.pg.on{display:block}',\n 'footer{color:#5a6c8c;font-size:12px;border-top:1px solid #17224a;' +\n 'margin-top:40px;padding-top:12px}',\n '@media(max-width:760px){body{display:block}nav{width:auto;height:auto;' +\n 'position:static;flex:none}main{padding:16px}}'\n ].join('\\n');\n}\n\n/* Filtering, page switching and the hash. Small enough to inline, and inlining\n it is the point — the document has to stand on its own. */\nfunction MOD_wikiJs() {\n return [\n 'var pages=[].slice.call(document.querySelectorAll(\".pg\"));',\n 'var links=[].slice.call(document.querySelectorAll(\"nav a[data-p]\"));',\n 'function show(id){',\n ' var found=false;',\n ' pages.forEach(function(p){var on=p.id===\"pg-\"+id;if(on)found=true;',\n ' p.className=\"pg\"+(on?\" on\":\"\")});',\n ' if(!found&&pages.length){pages[0].className=\"pg on\";id=pages[0].id.slice(3)}',\n ' links.forEach(function(a){a.className=a.dataset.p===id?\"on\":\"\"});',\n ' window.scrollTo(0,0);return id;}',\n 'links.forEach(function(a){a.addEventListener(\"click\",function(e){',\n ' e.preventDefault();location.hash=a.dataset.p;show(a.dataset.p)})});',\n 'window.addEventListener(\"hashchange\",function(){show(location.hash.slice(1))});',\n 'show(location.hash.slice(1)||\"start\");',\n // Search filters entries, hides groups left with none, and OPENS the groups\n // that still have some — a match hidden inside a collapsed group reads as\n // no match at all, which is worse than not having search.\n 'var box=document.getElementById(\"srch\");',\n 'var xall=document.getElementById(\"xall\");',\n 'var groups=[].slice.call(document.querySelectorAll(\"details.wk-g\"));',\n 'function filter(){',\n ' var q=box.value.trim().toLowerCase();',\n ' pages.forEach(function(p){',\n ' [].slice.call(p.querySelectorAll(\".wk-e\")).forEach(function(e){',\n ' e.style.display=(!q||e.textContent.toLowerCase().indexOf(q)>=0)?\"\":\"none\"})});',\n // Every page, not just the one on screen: the nav says how many entries\n // match in each, dims the ones with none, and Enter jumps to the first\n // page that has any. A prose page with no entries counts as one if its text\n // contains the query.\n ' links.forEach(function(a){',\n ' var pg=document.getElementById(\"pg-\"+a.dataset.p),n=0;',\n ' if(a.dataset.l===undefined)a.dataset.l=a.textContent;',\n ' if(q&&pg){var es=[].slice.call(pg.querySelectorAll(\".wk-e\"));',\n ' if(es.length)es.forEach(function(e){if(e.textContent.toLowerCase().indexOf(q)>=0)n++});',\n ' else if(pg.textContent.toLowerCase().indexOf(q)>=0)n=1}',\n ' a.dataset.n=n;a.style.opacity=(q&&!n)?\".4\":\"\";',\n ' a.textContent=a.dataset.l+(q&&n?\" (\"+n+\")\":\"\")});',\n ' groups.forEach(function(g){',\n ' var any=[].slice.call(g.querySelectorAll(\".wk-e\")).some(function(e){',\n ' return e.style.display!==\"none\"});',\n ' g.style.display=any?\"\":\"none\";',\n ' g.open=xall.checked||(!!q&&any)}); }',\n 'box.addEventListener(\"input\",filter);',\n 'box.addEventListener(\"keydown\",function(e){',\n ' if(e.key===\"Escape\"){box.value=\"\";filter()}',\n ' if(e.key===\"Enter\"&&box.value.trim()){',\n ' var cur=links.filter(function(a){return a.className===\"on\"})[0];',\n ' if(cur&&+cur.dataset.n)return;',\n ' var hit=links.filter(function(a){return +a.dataset.n>0})[0];',\n ' if(hit){location.hash=hit.dataset.p;show(hit.dataset.p)}}});',\n // Everything is collapsed by default because three of these pages run past\n // 40,000px open. This is the way back to one long document, which is what\n // the browser's own find needs.\n 'xall.addEventListener(\"change\",filter);'\n ].join('\\n');\n}\n\nfunction MOD_wikiBuild() {\n var nav = '', body = '';\n MOD_WIKI_PAGES.forEach(function (pg) {\n nav += '' +\n MOD_WIKI.safe(pg.label) + '';\n var inner;\n /* One page throwing must not take the wiki down with it — the page says so\n and the rest still renders. */\n try { inner = pg.build(); }\n catch (e) {\n inner = '

' + MOD_WIKI.safe(pg.label) + '

This page ' +\n 'could not be built: ' + MOD_WIKI.safe(e.message) + '

';\n console.warn('[mod] wiki page \"' + pg.id + '\" failed: ' + e.message);\n }\n body += '
' + inner + '
';\n });\n\n var stamp = new Date().toLocaleString();\n return '' +\n '' +\n '' + MOD_WIKI.safe(MOD_WIKI.title) + '' +\n '' +\n '' +\n '
' + body +\n '
Generated from the running game at ' + MOD_WIKI.safe(stamp) +\n ' — mod ' + MOD_WIKI.safe(MOD.version) + '. Everything here is read from the ' +\n 'live data, including your own save, so it cannot fall out of step with the ' +\n 'game. The game is truezhangwei\\'s; the mod is not endorsed by the author.' +\n '
' +\n '