Repository navigation
Conversation
The mod is a single mod.js loaded by one appended script tag in index.html, so removing that line disables it and `git checkout index.html` reverts it. Sections: speed and skill-xp control, extended milestones, 113 added skills with a story-tied level cap, enemy and area scaling plus three new areas, selling, enemy coin drops, titles and Renown, skill-panel sections and parent skills, and settings-menu multiplier boxes. Verified: 752/752 perks pass the audit, save is 19.3 KB fully maxed and reloads intact, balance sits at kill 2-10 / die 5-42 across every tier. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hvauCeK8WKVAPwnyWsNHp
The design record had accumulated large verbatim duplicate spans from regenerated content being appended rather than merged: - Removed a second full copy of the "Selling -> Full perk audit -> Interference checks -> Two latent problems" run. - Removed two back-to-back mega-duplicates (one identical to the "Skills now do what their text says" .. "Parents start at level 1" span, one identical to the Selling..latent-problems span). - Promoted the surviving "Selling" heading from H3 to H2; it was the only section in that run nested a level too deep, reading as if it were part of the tooltip-wording fix above it. File goes from 2209 to 1171 lines with no unique content lost (verified: the result equals the original minus the duplicate spans). Also moved "Balance, fourth pass" to sit directly after "Balance, third pass" so the four balance passes read in order through the document; it had been placed far ahead of the passes it builds on. Verified as a pure relocation (identical line multiset). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…caling Three changes, plus the test coverage that found the last one. Skill consolidation (v2.0). The ~100 "discovered by playing" skills are merged down to 10, with the per-stat effect budget and the permanent / conditional split preserved exactly, so player power is unchanged by construction. Each survivor keeps its v1 id: skill level and milestone flags are stored by id, so v1 saves still load and merged-away skills simply do not restore. Renumbering them would have collided ids onto skills with fewer milestone slots and thrown on load. A reachable level 110 (v2.0). The base game's skill curve, expnext = 50 + (lvl+1)^ln(9*lvl+1), is super-exponential: 109 -> 110 alone costs 1.16e14 xp, so the cap ladder was unclimbable by ~10^13. Section 19 replaces it per skill instance with a geometric curve tuned so a steadily-ticking skill reaches 110 in about six months at 1x. Only skills are re-curved; character level is untouched. The three added areas are now gated behind golem arena IV and open in order. Enemy scaling rewritten (v2.1). The old model multiplied strm/aglm/intm, which is wrong: dmg_calc is subtractive, so an enemy's STR is its armour and scaling it clamps your damage to zero rather than lengthening the fight. Measured through the game's own math, the tutorial needed 63,000 swings and killed you in one, and tiers 3-9 were all kill 1 / die 1. The old grid search had been scoring enemyHP/playerSTR, a proxy that ignores the subtraction entirely. Fights now have three independent fields: hpm for length, a dmg_calc wrapper for threat (offence without armour), aglm solved for a hit rate. Nothing is replicated -- the base game's defence term goes negative at high skill levels (shdc at 110 makes its last multiplier -12.3, so your defence is added to the enemy's damage) and is ill-conditioned besides, so each spawn runs the real dmg_calc a few times in both directions inside MOD_probe and scales against what came out. Targets are ratios anchored to your power at spawn, because within tier 0 alone the player goes from STR 1 to STR 265. Save format untouched. Verified: 2,490 matchups -- every area (21), every creature, both ends of every level band, ten story tiers, three skill builds -- all winnable, whiff 0% throughout, twelve consecutive clean runs. Real battles through attack() resolve and are won. 474/474 perks pass. v1 saves load clean. Two measurement bugs found on the way, both recorded in MOD-NOTES: dmg_calc grants skill exp, so sampling it was levelling the player mid-sweep; and player damage is two tight clusters rather than one spread, so MOD_meanDamage stratifies on the crit roll instead of taking a sample mean that is mostly "how many crits landed". realbal.mjs, tierfit.mjs and sweep5.mjs are deleted -- they measured the stat-ratio proxy or swept dials that no longer exist, and would report confidently on a model the mod no longer has. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The game keeps exactly one save: save() writes localStorage "v0.3" and load() reads it, both by name, and load() runs once from a window load listener. There is no unload and no reset. So rather than edit either function, "v0.3" always holds whichever slot is live, with a mirror beside it (p23_slot_N, p23_slotmeta_N, p23_slot_active). save() is wrapped to refresh the active slot's mirror, so it stays current whatever triggered the save -- button, autosave, or an area's own call. Switching writes "v0.3" and reloads the page, which is not laziness: the game builds its world at startup and reads the save once. "Start new save" is the same move with the slot cleared first, so the game finds nothing and starts a fresh character. A "saves" button in the save bar opens a panel listing the three slots with the character, level, cap and save time in each; a "new save" button starts a fresh character in the first free slot, or opens the panel to choose when all three are in use rather than picking one to destroy. Console equivalents: modSaves(), modSwitchSave(n), modNewSave(n), modDeleteSave(n). Two things it is careful about. Boot adopts but never overwrites: a missing mirror is filled from "v0.3", so a character from before this existed is picked up rather than orphaned, while the live key is never written from a mirror that could only be staler. And nothing destructive happens without the target in front of you -- overwriting or deleting a slot names the character and level in the confirm, and leaving a slot saves it first, abandoning the move if that save throws. The base game's "delete the save" was localStorage.clear(), which would have taken all three slots and the mod's settings with it. Rebound to the active slot only, by cloning the node, since the game's handler is anonymous. tests/saveslots.mjs drives the real UI and the real save()/load() through the whole cycle: three characters coexisting, switching both directions, starting fresh without touching the others, deleting a slot you are not in, settings surviving a deletion, and a pre-slot save being adopted. Also verified by hand in a browser. Full suite green, 13 scripts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two changes to how the mod's four actions work. They are earned now. Circulate Qi, Forage, Practice Calligraphy and Endurance Drill used to be granted 2.5 seconds after load and re-granted on a 5-second timer, so a new character had four unexplained abilities before leaving the tutorial. Each now comes from a milestone on the base-game skill it grows out of, which is the game's own mechanism -- skl.walk level 1 is what grants "Run": Toughness 4 Endurance Drill taking hits teaches you to condition Harvesting 4 Forage gathering teaches you to search Temperance 5 Circulate Qi letting go teaches you to hold Literacy 8 Practice Calligraphy read before you write All four skills train through ordinary play. Which skills could be used was constrained: milestone flags are stored by array index, so entries must be appended with ascending levels, and section 3 had already put level-50 milestones on Walking, Meditation, Foraging, Patience, Fighting and Sleeping. Meditation was the obvious home for Circulate Qi and is one of the blocked ones; Temperance had no milestones and fits better anyway. A milestone rather than a tick watching for the condition, because the requirement then shows in the skill panel like any other perk and the load ordering is already solved -- milestones re-fire before the action list is rebuilt from the save, so a replayed grant is overwritten a moment later and a save from before the unlock comes back without it. tests/audit.mjs needed teaching about this: snap() did not track acts, so a perk granting only an action counted as doing nothing. With acts.length added it also surfaces a base-game text gap -- walk lv1 says "AGL +1" and quietly grants Run too. Second, a settings checkbox: "Unrestricted actions". Several sustained actions at once, started anywhere -- in a fight, indoors, in the dark. Off by default, since both are deliberate limits. The game enforces one-at-a-time twice: activateAct deactivates whatever was running, and every activate() overwrites the single shared timers.actm. Nothing is reimplemented -- each action's own activate/deactivate is wrapped and the timer SLOT is swapped around the call, so the action's own clearInterval/setInterval end up operating on its own interval. That works unmodified for the base game's Run and Investigate as well as the mod's four. activateAct treats a click on an already-running action as "stop it", since the click handler can otherwise only toggle off the most recent one. Unchecking stops everything, because the game only understands one. Guarded: use() is wrapped so a tick running somewhere it was never meant to cannot throw every second forever (five failures stops it), and load() clears the per-action intervals, since it resets every active flag. New tests actionlock.mjs and freeactions.mjs, 44 checks between them. Also verified by hand in a browser. Full suite green, 15 scripts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Section 19 made level 110 reachable. Nothing was waiting up there.
Measured across all 94 skills: 25 had no milestones at all, 12 had
nothing past level 10, 44 had nothing past 50, and of the 13 with
anything higher only 10 went past 60. So 81 of 94 skills gave nothing
whatsoever between level 51 and 110 -- the entire top half of a ladder
the seventh pass had just spent its effort making real.
One rule now: every skill gets the rungs it does not already have, from
10/25/50/60/75/90/110, appended above its current highest milestone.
Uniform, no per-skill list, and always ascending -- which the save format
requires, since "granted" flags are keyed by array index. 447 perks
across 93 skills; Renown is skipped, being driven by titles not levels.
The rungs are the cap ladder's own, so reaching a story cap and pushing a
skill into it pay off together.
What each perk does comes from the skill's type, so a resistance skill
gets resistance and an absorption skill gets elemental defence rather
than everything getting the same stat. Two deliberate avoidances:
skl.<x>.p is never touched, because skill xp multipliers restore AFTER
milestones fire and a new perk raising one would be overwritten on the
load that first granted it; and absorption perks use you.caff rather than
you.res, since section 13 already drives you.res for those skills and
reconciles it through global.flags.mod_aff.
The balance suite then caught two things. Critical damage had to come
down to a quarter: cpwr is shared by 13 combat skills and compounds
across seven rungs, so at full value it went 1.2 -> 13.0 by cap 110, a
crit worth 23x a normal swing landing a fifth of the time. And allareas,
combat and earlybal were still taking a plain sample mean of a
distribution whose tail had just got heavier, which produced three false
failures out of 2,490 with the familiar signature -- die exact, kill
twice its target. They now stratify on the crit roll the way
MOD_meanDamage does, written out in each rather than calling the mod's
copy. savecompat was also asserting a hardcoded seven-element milestone
array; it now checks the invariant that actually matters, that saved
flags restore into the slots they came from.
Separately, the changelog. The game links to it from the version number,
but the handler is window.open('/changelog/changelog.html') -- absolute
from the server root, so it 404s from a file:// URL or any subdirectory,
which is how a local copy usually gets opened. There is a labelled
"changelog" button in the bottom bar now, the path resolves against
document.baseURI, and the version number still works. The mod's own
entries sit at the top of that file under a gold header, above the
author's, which stay untouched below a divider. The file also gained an
explicit white background: it sets no colours of its own, and the
author's palette assumes a white page, so dark-mode browsers rendered
half of it unreadable.
Every change from here on gets an entry there.
New test perkcoverage.mjs. Full suite green, 16 scripts, 932 perks.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…em do something Three problems, all confirmed by measurement first. Rank meant nothing. `rar` ran 0-5 and was assigned by feel, so the game handed out rank 3 titles at skill level 8: Runner at Walking 10, Rookie at Fighting 15, Dissector at Disassembly 8. Eight of them, all early. There were too few. 120 titles across 93 skills, and 73 of those come from story events rather than skills, so most skills granted none at all. And they did almost nothing. Four titles in the entire game carry a `talent`. The rest are flavour: the selection window sets `you.title`, which is the name printed beside your level and nothing else. Rank now runs 1-10 and is derived from the level that earns the title -- 1 at lv 1, 3 at 18, 5 at 45, 7 at 70, 9 at 90, 10 at 110. For the 47 titles a milestone grants, that level is read out of the milestone itself (f.toString() scanned for a giveTitle call), so it stays correct if a grant moves. The 73 story titles have no level to read, so the author's 0-5 is stretched over the ranks the skill rungs leave free; nothing story-granted reaches 9 or 10, which is the point. Every skill grants five titles, at levels 25/50/75/90/110 -- ranks 3/5/7/9/10. 465 new titles, pool 120 -> 585, all ten ranks populated. The grant is folded into the milestone already at that level, because two milestones at one level are a duplicate the save cannot tell apart by index. Names are formulaic and the rank words are nouns: the form is "X of Y", and the first draft produced "Peerless of Fighting". Each title raises its own skill's exp rate, 5% at rank 3 to 30% at rank 10. That is skl.<x>.p, which is saved and is restored AFTER milestones fire, so it is not written from milestone code -- it is reconciled on the tick against global.flags, the same shape section 13 uses for you.res. tests/titles.mjs checks it against stacking over thirty ticks and across a reload, which is the failure that pattern exists to prevent. Base-game talents were already permanent -- giveTitle fires talent() once and sets tget, and clicking a title only ever changed the displayed name. These new bonuses are the opposite by default: only the title you are WEARING applies, which gives the selection window a reason to exist. Renown then buys that away -- at renown level N every title of rank N or lower applies permanently, worn or not, so renown 10 makes the whole collection passive. Renown's thresholds are rescaled to the larger pool: level 10 wants 550 titles rather than 100. Also fixes modActions(), which a bad text substitution had left calling an undefined variable. New test titles.mjs. Full suite green, 17 scripts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Section 24 took the pool from 120 titles to 585, which turned the game's
flat picker into a scroll of hundreds of near-identical rows in a 300px
column, most of them superseded by the one below.
Titles from one skill now collapse to a single row showing the best you
hold, with a caret to open the rest:
> "Champion of Fighting" (8)
> "Aegis of Cold Resistance" (4)
"Thrasher"
"Wolf Slayer"
The caret expands without selecting; clicking the row itself wears the
best one. Story titles have no skill to group under and stay as plain
rows below the groups.
The hook is a SECOND listener on dom.d3 rather than a replacement. The
game's picker is built by an anonymous listener there, so it cannot be
removed, and cloning the node -- the trick used for "delete the save" and
the version number -- would drop dom.d3.update and the addDesc tooltip
the game attached to it. Listeners fire in the order they were added, so
the game builds dom.ttlbd and this rebuilds its contents immediately
after, before a frame is drawn.
Grouping needs to know which skill a title belongs to. The 465 generated
ones say so themselves; for the base game's, the milestone that grants
the title is found by scanning f source for a giveTitle call, the same
trick section 24 uses to date them. So Civilian, Trained Civilian,
Fighter and Rookie group under Fighting alongside the generated five
rather than being stranded.
New test titlepicker.mjs, 13 checks. Also verified by hand in a browser:
36 titles collapse to 10 rows, Fighting opens to all 8. Full suite green,
18 scripts.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Answering "are there new colours for the titles too": the game already had them, in the type 5 branch of dscr() -- grey 0, cyan 2, lime 3, yellow 4, orange 5, purple 6. Two problems, neither of which could surface until section 24 started using the upper ranks. Rank 7 THREW. Its branch sets this.dl.style where every other branch sets this.label.style; this.dl is assigned in the type 6 and 7 branches of the same function, so in a type 5 call it is undefined and the tooltip dies half-built -- name placed, description never. The base game topped out at rank 5, so that branch had never once run. There are now 93 titles at rank 7. Ranks 1, 8, 9 and 10 had no branch at all, so 220 titles rendered with no colour, including every rank 10 in the game. Rather than patch a switch inside a 200-line function that builds six tooltip layouts, the title's rank is parked at 1 for the duration of the call -- a value the switch has no branch for, and so cannot throw on -- and the label is coloured afterwards from a ten-rank table. The author's five colours stay exactly where they were; the ramp only extends past where they stopped. The tooltip now also states the rank and the level it was earned at, which is worth saying now that rank means a level. The picker rows use the same table, which is the point of having it: at five titles per skill, rank is what you are reading. That took the colour slot away from marking which title is worn, so worn is now weight plus a background tint -- not a glyph, since the caret already owns the one that reads best and two triangles in one column are unreadable. tests/titlepicker.mjs caught that collision on the first run. titles.mjs gains four checks: no rank throws when hovered, every populated rank renders a colour, the tooltip states the rank, and the ten ranks are ten distinct colours. Full suite green, 18 scripts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The dojo already has a reward ladder, and the instructor already promises it continues -- "After every 5 levels you reach, come here and receive your share!" -- and then it stops at character level 30 (dj1rw1..6, levels 5 to 30). The mod's endgame is around character level 92, so the promise went unkept for the last eighty levels. Sixteen more rungs finish it, 35 through 110 in fives, sequential like the base six. The pills had to be rebuilt first. Character exp per level is 4*lvl^3 + lvl^2, which is 5,336,100 for one level at 110, against a best-in-game pill worth 15,000 -- the ladder would have been handing out 0.3% of a level. Four grades continue the author's own progression at roughly his ratio: Superior 100,000, Refined 600,000, Sublime 3,500,000, Transcendent 20,000,000. The last is worth 3.7 levels at 110; the first is not a fifth of one at 50. And a third grade of skillbook at +30%, offered as a CHOICE at the 50, 75 and 100 rungs -- the same shape as the one the instructor gives the first time you clear the dummies, which is the moment the request pointed at. Named in the author's register (Sword Saint, Nightblade, Headsman, Dragoon, Earthbreaker, Iron Body) because the grade below is already called "Bladesman Manual". They write skl.<x>.p the way the base books do, which coexists with section 24's title bonuses since that reconciler only ever adds and removes its own tracked delta. Worth recording what the first draft got wrong: I built a ladder of new golem trials before reading far enough into chss.t3.sl, and only found Level Advancement -- and the second grade of skillbook that already existed -- when a probe printed a lobby screen full of manuals I had not written. The trials were thrown away. Same lesson as the catacombs and the dead affinities: read the whole handler before adding a parallel one. The continuation is its own lobby entry rather than an extension of the original's screen, because the base rungs are drawn by an anonymous handler created inside sl() and cannot be appended to. It appears only once dj1rw6 is set, so the two never show at once. New test dojo.mjs, 18 checks. Full suite green, 19 scripts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The repository had none. It is a fork of someone else's game, so the README leads with that: index.html is truezhangwei's work, unchanged apart from the single script tag, and removing that line reverts everything. Beyond the usual (what it changes, how to run it, how to test it) it carries one thing forward deliberately: dmg_calc is subtractive in both directions, so a defender's STR is their armour and scaling an enemy's STR clamps your damage to zero rather than lengthening the fight. That mistake cost a full rewrite of the enemy model, and it is the first thing anyone touching combat needs to know. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
I had said every change gets a changelog entry, then left the README out on the reasoning that the in-game changelog is for things a player would notice and a repo README is not one of them. That reasoning is defensible but it was applied silently, which is the part that is wrong -- a rule with an unstated exception is not a rule. So: the README gets an entry, marked as not a game change, and CLAUDE.md now says the rule is literal on purpose, so there is never a judgement call about what counts. Still purely additive against the author's file (92 insertions, 0 deletions); their 19 version blocks are untouched below the divider. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The mod's entries had been prepended to changelog/changelog.html, above
the author's, under a gold header. It read well and it was wrong: the mod
was editing a file that was not its own.
They now live in changelog/mod-changelog.html, which restores the
property the whole design rests on -- the single script tag in index.html
is the only change the mod makes to anything of the author's.
git diff origin/main -- changelog/changelog.html comes back empty, and
CLAUDE.md now says to check that before committing.
The bottom-bar button opens the mod's file; the version number opens the
game's, which is what it always did. It is still rebound, because the
game's own handler is window.open('/changelog/changelog.html') -- an
absolute path from the server root, so it 404s from a file:// URL or any
subdirectory. It resolves against document.baseURI now and goes to the
same place.
The author's file keeps its dark-mode problem: it sets no colours and its
palette assumes a white page, so half of it is unreadable in a dark
browser. The mod's file pins a white background for itself; his is his to
fix.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
proto23 is already a cultivation game in its furniture -- Qi Circulation, spirit pills made from "condensed Ki", a dojo, meridians in the skill text -- without the thing the genre is about: a realm you advance through, a wall that stops you, and a breakthrough that costs something. Seven realms, Mortal through Ascendant. The ladder, the dantian, bottlenecks and tribulations are genre conventions shared across hundreds of works; nothing here is taken from any particular novel -- no names, no settings, no text -- which is both the legal answer and the right one, since borrowed specifics would sit badly against proto23's own world. The bottleneck is the point. Reaching the Qi Circulation level does NOT advance the realm, it puts you at a wall, and breaking through costs a pill you had to find. The attempt can fail and spends the pill either way; odds start at 55% and improve 5% per level trained past the requirement, capping at 95% -- the genre's advice to consolidate before pushing, made mechanical. Failure drops you to 1 HP but never kills, because the game has a real death system and a breakthrough should not touch it silently. Each realm multiplies every stat, up to x6. Safe to make that large because section 8's enemy model measures the player rather than assuming a curve. The bonus is applied in allbuff and never written into the stat fields, so it cannot compound across loads -- the test runs twenty consecutive allbuff calls and checks the number does not move. Then six elemental masteries, because the six Absorption skills were purely defensive: trained by being hit, with no way to use an element at all. Combat is automatic -- fght calls battle_ai, there is no ability picker -- so a technique is a proc on the swing, capped at 45% so a weapon still matters. Each is an Ability with stt 2, routing through the INT branch of dmg_calc, so it scales with INT and its element rather than with STR and a weapon. Nothing fires at Mortal; the channels are not open. Two things worth keeping: the wrapped battle_ai swallows any error from the proc and falls through to the ordinary attack, so a bug there can never cost a swing (the test forces the picker to throw and checks all 20 swings still land). And the six skills had to be registered with MOD_KEY_BY_ID and MOD_PARENT_OF -- maps sections 10 and 11 build before these skills exist. A skill created late is invisible to them and nothing complains. rnwn needed the same fix in section 17. New test cultivation.mjs, 19 checks. Full suite green, 20 scripts, 974 perks. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ten realms instead of six, sitting on MOD_RANK_AT -- the same thresholds section 24 derives title ranks from -- and DERIVED from it rather than listed again. Realm 3 is exactly the Qi Circulation level that earns a rank 3 title, and each realm's own title falls out at its own rank without being told to. Retune the ranks and the realms follow; there is no second copy to forget. 1 Qi Refining Qi 1 6 Soul Transformation Qi 58 2 Foundation Establishment Qi 8 7 Void Refinement Qi 70 3 Core Formation Qi 18 8 Body Integration Qi 82 4 Nascent Soul Qi 30 9 Great Ascension Qi 90 5 Spirit Severing Qi 45 10 Tribulation Transc. Qi 110 Ten breakthrough pills to match, one per realm. The multipliers were respread over ten steps rather than six, so each is a smaller jump while the top stays x6 on stats and x8.5 on the body. The technique proc's per-realm term dropped 0.02 -> 0.012, since it was tuned when there were six realms and would otherwise have eaten most of the 45% cap on realm alone. The still-extended ladder is the genre's own -- Spirit Severing, Void Refinement, Body Integration, Great Ascension, Tribulation Transcendence are as widely shared as the first five, and none of it comes from any particular work. cultivation.mjs now asserts the alignment directly (r.qic === MOD_RANK_AT[r.n-1], MOD_rankForLevel(r.qic) === r.n, and the realm title's own rar), which is what will stop the two ladders drifting next time either is touched. Four of its assertions had hardcoded "realm 1 needs Qi 10" and broke the moment the ladder was rescaled -- the same staleness in miniature -- so they read the thresholds out of MOD_REALMS now. Full suite green, 20 scripts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two things. Circulate Qi used to hang off Temperance 5, like the other three added actions. That was fine until section 28 made it the way into the cultivation system -- realms are levelled through Qi Circulation -- at which point gating it behind a skill you train by throwing your possessions away is an obscure lock on the most important thing in the mod. It now comes from clearing the dojo's three tutorial fights: the difficulty select's "Easiest", "Easy" and "Normal", which set tr1_win, tr2_win and tr3_win. That is the moment the dojo has finished teaching you to fight, which is the right moment to hand over what the rest hangs off. A tick check rather than a milestone, because the condition is story flags rather than a skill level -- the same way the base game grants act.scout from a story beat. The skill is revealed on the sheet at the same moment, since the game otherwise only shows a skill once it first levels and you would hold an action whose skill is nowhere. Temperance keeps a perk at level 5, now a plain one. Removing the milestone would have shifted every later index in skl.rccln.mlstn, and those indices are what the save keys its granted flags to. Second, the bottom bar was 8px taller than it should be and covering the bottom of the panel above it. The mod's three buttons were display:inline-block where the game's own are plain inline; both render 22px tall, but only an inline-block contributes its full box to the line box. #sl is position:fixed at bottom:0, so those 8px came straight off the game screen. Measured rather than guessed -- hiding just those three buttons took the bar from 32px back to 24px -- and it is 24px either way now. actionlock.mjs gains seven checks for the new unlock, and its modUnlockAll assertion no longer depends on test order. Full suite green, 20 scripts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The realm system shipped with no way to obtain a single one. All ten
existed as items and nothing gave, dropped or sold them, so the entire
ladder -- the thing sections 28 and 29 both hang off -- was reachable
only from the console.
cultivation.mjs passed throughout, because it called MOD_breakthrough
directly with { amount: 1 } as the pill. It tested the mechanism and
never asked where the input came from. That is the blind spot of a test
that supplies its own inputs, so the fix is a test that asks the world
instead: it walks the Herbalist's stock, the dojo's rungs and the
Qi-unlock path, and fails if any realm's pill is orphaned.
realm 1 the instructor, when the dojo finishes teaching you
realms 2-5 the Herbalist, 900 to 38,000 copper
realms 6-10 the dojo's Level Advancement rungs at 45, 60, 75, 90, 105
The Herbalist rather than a new alchemist shopfront: it is already the
marketplace's plants-and-medicine vendor, already stocks sp1/sp2/sp3, and
xianxia alchemy is herbalism with qi. Appending to vendor.pha1.items uses
the game's own restock and purchase machinery, so there is no second
screen to keep in step. It also picks up the Superior and Refined Spirit
Pills, which had the same problem in miniature.
Realm 1 is deliberately not from the Herbalist. The marketplace is gated
behind reading a flyer the Paper Boy hands you at 40% per Village Center
visit, and only after dj1end, while realm 1 opens at Qi Circulation 1 --
sourcing the first pill there would have put the whole ladder behind a
random encounter.
Save-safe: vendor stock is stored by item id and restored by scanning
itemgroup[(id+1)/10000|0], which for the 912x ids is `item`, where these
are defined. Verified end to end -- the pill appears in real restocked
stock, survives the round trip, and advances the realm when used.
Full suite green, 20 scripts.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two places, both of which the game was already pointing at. The mod had
spent a lot of sections adding systems and none adding world.
The catacombs were finished and unreachable. 26 locations exist, fully
written, with their own ambient text table and a bestiary entry for the
ghouls that says they live there. grep catamn returns three hits: its
definition, its own handler, and one smove(chss.catamn) from inside cata1
going back. Nothing links in -- and its own exit leads to the Village
Center, which is where the entrance was always meant to be.
So the entrance is one chs() there. Not a word of the content is the
mod's; the entrance is the whole change, and places.mjs asserts it (zero
mod_cata* keys).
It is gated, and not for flavour. Visiting sets mod_t_cata, the cap-40
rung in MOD_TIERS -- the rung that has been dead all along precisely
because nothing could reach it. An open door would hand a fresh character
cap 40 straight out of the tutorial, past the forest's 20 and the deep
forest's 30. Gated on mod_t_deep, the rung below it, so the ladder keeps
its order: forest 20 -> deep 30 -> catacombs 40 -> arena 50. That rule is
now in CLAUDE.md for any future area that sets a tier flag.
The Pill Tower is the author's own abandoned idea. On the Village Center,
in their file:
// chs('"=> Visit Pill Tower"',false).addEventListener('click',()=>{
// smove(chss.pltwr1);
// });
The choice was written and commented away; the location was never built.
An alchemists' tower is exactly what a cultivation game wants and exactly
what this mod lacked, so this completes their intention rather than
inventing a different shopfront. It is chss.mod_pltwr, not chss.pltwr1 --
the two must not collide if they ever finish theirs, and the test checks
chss.pltwr1 is still undefined.
It carries its own vendor with the grades a village herbalist cannot
source (realms 6-10, plus the Sublime and Transcendent Spirit Pills), and
a spirit vein you can sit in once a day for Qi Circulation exp scaling
with your realm -- somewhere for cultivation to go, which the ladder
otherwise entirely lacked. Gated on having a realm at all: they do not
let mortals past the door.
New test places.mjs, 20 checks. Full suite green, 21 scripts.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A `wiki` button on the bottom bar and a "Game wiki" row in settings, both opening an eleven-page reference in a tab of its own: Start here, Progression, Skills, Cultivation, Elemental mastery, Titles, Areas & enemies, Items, Actions, The dojo, Mechanics. It is generated from the running game every time it opens, and that is the whole design. A hand-written wiki for 100 skills, 595 titles, 371 items and 19 areas would be wrong within a week, and a reference nobody can trust is worse than none. Reading skl, item, area, ttl, act and the mod's own tables means it cannot disagree with the game. It reads the save too, so it doubles as a progress sheet, and it fetches nothing, so it works from file://. Groups collapse. Listed flat, three pages ran past 40,000px — Items 53,723, Titles 46,890, Skills 40,538. Collapsed the tallest is 5,826 and Skills opens as ten lines. Search opens the groups it matches inside, since a match hidden in a collapsed group reads as no match at all. Anything past 60 entries gets an alphabetical sub-split; the story titles split by rank instead, that being the axis they are already sorted on. Generating it rather than writing it turned up two things: * Spawn chances are not pop[i].c. z_bake normalises those weights into area.popc, the [lo,hi] bands mon_gen rolls against, and they need not sum to 1 — the Southern forest's .35/.45/.25 are really 33/43/24%. * Nothing can ever spawn in the Damp cellar (base game). Its pop entries carry no weight, so z_bake produces NaN bands and no comparison in the spawn loop can be true. Nothing travels to area.clg either. Left as it is and reported in the wiki rather than quietly fixed — it is the author's unfinished content, like chss.pltwr1 was. area.nwh and area.tst are excluded outright: the first is where current_z parks when you are not fighting, the second hangs off a location with id -1 that is in no sector. tests/wiki.mjs, 57 checks. Coverage is counted from the game rather than from a list, so adding a skill without adding it to the wiki fails the suite on its own. The built document is parsed in a real browser, and the last check clicks the actual button and asserts a working tab comes up. Full suite green: 22 scripts, 2,490 matchups, 974 perks. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two reports, one root cause each, both the mod's own doing. ## A hundred Fire Masteries skl.par_10 "Companions Discipline" is 2000 + section 10 (section 11). skl.mod_fire "Fire Mastery" was 2010 + 0 (section 29). Two numbering schemes, each sensible alone, that met at exactly one value. An id is the save's key for a skill, and the loader matches on it in a loop with no `break`, so one shared id pushes both skills for both saved entries and the next save writes all of them. Measured: 1, 2, 4, 8, 16 rows per cycle. Seven save/loads is 128 of each. MOD_KEY_BY_ID is id-keyed too, so the clash had also quietly taken the Companions parent's level cap. Only Fire Mastery moves (2010 -> 2016). Water through dark keep 2011-2015, the ids they were saved under, so nobody loses those levels, and the ambiguous value goes back to the skill that claimed it first. The six are an explicit table now rather than arithmetic. Fixing the id only stops it getting worse — the duplicates are in the save. MOD_dedupeSkills prunes you.skls by identity on load and redraws the panel: a damaged 138-row sheet comes back as 12 through a real reload, levels intact. Scanning every namespace turned up one more of the mod's own: chss.mod_hollow and chss.mod_pltwr both had location id 976, from MOD 2.9. The tower is 980. ## Getting into the marketplace Exactly one item in the game sets mkplc1u — the "Pamphlet" — and the Paper Boy who hands it over is retired the moment it is given rather than read. Section 15 added selling and never marked it a key item, so it could be sold at the food stand, outside the marketplace, before being read. That takes the marketplace, its three shops, the guard-duty quest and the realm 2-5 breakthrough pills away permanently. Reproduced before touching anything: 500 visits, no Paper Boy. * Key items cannot be sold. Derived, not listed: the game is one inline script, so its source is readable at runtime; a flag it never sets back to false is a one-way unlock and the item that sets one is the only copy of something. Six qualify. A Smoke Bomb sets a flag the game does clear, so it stays sellable — which is why the rule is derived rather than "sets a flag". * The Paper Boy comes back if you no longer have a Pamphlet. That is what gets an already-stuck save out without a console; a fresh character still meets him exactly once, through the author's own code. * The village center says WHY the door is missing instead of omitting the line. "It seems like I can't" was the report; an invisible gate was the bug. The Herbalist inside gets the same treatment. Two layout constraints found doing it: .chs is height:22px with no overflow rule, so a wrapping choice spills into the next row (added lines stay under 50 characters, sentence in the tooltip); and chs() appends, so MOD_chsAboveBack puts the line above the location's "<= Return". tests/ids.mjs (32 checks) and tests/marketplace.mjs (30). Full suite green: 24 scripts, 2,490 matchups, 974 perks. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`you.rank()` is the base game's Power rank, derived from live stats on every read. Measured on a character built to each rung of the story ladder, rank 1 IS reachable — but only with every skill at 110. At 108 across the board you are still rank 3, and all ten ranks live inside the last fifteen skill levels, after a whole game in which the number is an unreadable ten-digit blob. Section 34 adds a second way to hold one. The Hall of the First Gate, off the Old Path trailhead, holds ten named challengers from rank 10 down to rank 1, fought strictly in order and gated on character level so the ten fights span the endgame. Winning writes the rank to global.flags and `you.rank` is wrapped to floor at it — it never caps, so your own stats can still beat it. Each duel is a protected one-creature area, which MOD_scaleEnemy already reads as an arena boss, so every rung is anchored to the player's power at spawn. The per-rung step goes into fight length rather than incoming damage, and the kill<die margin is re-derived from the targets MOD_scaleEnemy stored rather than hoped for — scaling both targets in opposite directions loses the guarantee by a step of 1.16. tests/ranks.mjs: 31 checks, including all 100 rung x tier matchups through the real dmg_calc. allareas is now 2,790 matchups across 31 areas. Full suite green across 25 scripts. Also records a pre-existing bug found while testing, deliberately NOT fixed here: hpTrack assigns you.hpmax at the end of the allbuff wrapper, but allbuff opens with stat_r(), which recomputes it from the base. Sampled 43 times across six seconds of real play, you.hpmax was 10,050 every time and the hpTrack value of 184,754 never appeared — so enemy damage is sized for ~18x the health the character actually has. Written up in MOD-NOTES.md and CLAUDE.md; it moves a number all 2,790 matchups are measured against, so it needs its own change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Found by auditing, not reported. `giveTitle` pushes each earned title onto two arrays — `global.titles` and `global.titlese` — and `load()` rebuilds `global.titles` from the save by index and then appends the whole of `titlese` on top. Everything earned that session therefore lands in the array twice. Measured on a character holding 241 titles: 241 before a load, 481 after, 241 distinct. Reproduced in the UI: with seven titles held, "Safehouse" rendered twice in the picker after one save/load. It does not compound — the next load finds `titlese` empty — but one load is enough, and `save()` writes the inflated array straight back out. Both the game's own title screen and the mod's grouped picker iterate `global.titles`, so every title got two rows. A base-game bug, but the mod is what makes it loud: vanilla hands out around a hundred titles and section 24 grants five per skill. Section 17 already dodged it once for renown by counting from `ttl`; this repairs it properly. MOD_dedupeTitles prunes by identity on load and at boot, the same way and for the same reason as MOD_dedupeSkills — `global.titles` holds references to the objects in `ttl`, so the same object twice is always wrong whatever put it there. Deliberately not fixed at the source: `titlese` is the author's mechanism and the mod has no business changing what it means. Also hardens MOD_scaleRankDuel with the explicit player / creature.default guard MOD_scaleEnemy has, rather than relying on neither carrying a spawn target. tests/ids.mjs gains 8 checks: distinct before a load, after one, after two, no holes, the worn title still in the list the picker reads, and an already-doubled list repaired. Confirmed they fail without the fix (85 entries for 43 distinct). The rest of the audit found nothing to change: all 75 locations draw, no choice row wraps its 22px line, all 12 wiki pages build with no horizontal overflow at 375/768/1200px, no id clashes, no interval leaks over 15s at 20x, and acts / you.skls / furn / qsts stay distinct across repeated save/load cycles. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The picker groups by skill. Measured across the whole pool: 595 titles, 512 attributed to a skill, and 83 in a flat list at the bottom — which is the wall of rows the grouping existed to remove, just smaller. Reading them, almost none were really one-offs. Two more attributions, both derived rather than typed out: - MOD_FLAGSHIP already pairs a skill with the title it grants, but those milestones are built from the table so the closure holds a variable and the existing source scan finds no literal to match. Reading the table attributes 10 (Executioner, Deathless, Sage...). - Around thirty of the author's titles are keyed <skill><n> — tghs1/2/3 under Toughness, srd3/srd4 under Swordsmanship, dth4 under Death, rtr1 under Retreating. Most are granted by nothing at all, so no milestone scan can see them; they are unfinished tiers like area.clg, and they group correctly whether or not they are ever earned. That attributes 9 more. The trailing digit in that rule is load-bearing. ttl.thr is "Thrasher", for smashing the dojo's equipment; its bare stem is skl.thr, Throwing. Only the numbered convention is safe to read this way, and an ambiguous prefix is left alone rather than guessed at. tests/titlepicker.mjs asserts both directions. The remaining 40 are ladders of their own — the ten cultivation realms, five for creatures killed, four each for items gathered, titles collected and punch power, three each for time indoors, shields and jobs. They share a key stem the same way the numbered tiers do, so membership is derived; MOD_TITLE_FAMILY holds only a label per family (eleven of them), with the lowest-ranked member's name as the fallback. A ladder's best title does not say what the ladder is — "Nameless (4)" could be punch power or titles collected — so the label sits on the row, not only in the caret's tooltip. 595 titles held now renders as 126 collapsed rows, and the flat tail is down from 83 to 23 genuine story one-offs. The wiki's title page had the same problem solved worse (everything skill-less under "From the story", split into rank buckets) and now takes the same families, nested under one "Ladders of their own" group — eleven more top-level summaries cost ~530px on the page wiki.mjs already caps at 6,000. Also found five titles the author never finished: ttl.ddcd carries the literal string "null" as both name and description, and shpt2/shpt3/mone3 are blank. Nothing grants any of them. The picker skips them rather than drawing a blank row; the wiki lists and tags them "unfinished", the same call area.clg got. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The question was whether anything above one star can be crafted. Measured
across all 62 recipes, twice — by the rarity of what they produce, and by
whether any giveRcp call anywhere can actually teach them:
stars recipes reachable
1★ 43 35
2★ 17 7
3★ 1 0 <- rcp.trr, Trinity
4★ 1 1 <- rcp.clrpin, Clover Pin
5★ 0 0
So the literal answer is yes, narrowly. But the 3-star tier is one recipe
nothing can teach, 5-star is empty, and the single 4-star result is a Clover
Pin you get for holding seven clovers. Crafting effectively stops at two.
Section 35 adds four rungs, 2★ to 5★, each a full set (weapon, body armour,
shield, accessory, tonic) — twenty craftable things from twelve materials along
three lines, gathered at four nodes behind one door on the Village Center.
Each rung consumes the rung below it. The Gather action comes from the location
rather than the action, so one action serves all four nodes, and it trains
Mining, Geology and Foraging, three skills the base game defines and barely
uses. The first material out of a seam teaches that rung's blueprints, the way
the base game teaches rcp.wfar from three wolf fangs.
Three things this had to get right:
- Ids are blocked by namespace and load() depends on it — a saved id resolves
through itemgroup[(id+1)/10000<<0]. Items 9200+, weapons 10101+, armour
20101+, shields 30101+, accessories 40101+.
- rar cannot exceed 6: equip() does global.text.wecs[w.rar][0] and that table
has seven entries, so a 7-star anything throws when worn.
- The value model needed re-indexing. Section 15 builds MOD_VAL.madeBy in an
IIFE at load, so new gear fell past rule 2 (price from inputs) to rule 3
(price from stats): a 5★ sword priced at 1,161 while each of the eight ingots
it eats priced at 13,000, and selling the ore paid ninety times better than
using it. Materials are anchored (6/18/54/162) rather than priced on the
equipment rarity scale, sized so gathering pays ~6 coin/s sold against ~1
from endgame combat, and the gather rate came down from 55% a tick to 14%.
Also reports a base-game finding: 19 of the 62 recipes have no giveRcp
anywhere — defined, priced, complete, unteachable. Listed in the new wiki page
rather than wired up, like area.clg and the unfinished titles.
tests/crafting.mjs: 27 checks including both traps above. Full suite green
across 26 scripts.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two things that break quietly, neither of which errored. NAMES. Ids are the save's key and tests/ids.mjs guards them; names are the player's key and nothing guarded those. Section 4 added skl.frg named "Foraging" when the base game already had skl.hvt under exactly that name. Two identical rows in the skill panel — and ten identically named titles, because section 24 derives a title's name from its skill's, so mod_t_hvt_25 and mod_t_frg_25 both came out "Gleaner of Foraging", five rungs each. The mod's is Wildcraft now. Key and id untouched, so nobody loses a level: names are not saved, ids are. "Wildcraft" rather than something invented — the author's register is plain single words (Foraging, Harvesting, Topography, Elusion, Temperance) and this skill reads the wild rather than harvesting it. Swept the rest: all 140 things the mod names are unique, including across the whole inventory bag, since item/wpn/eqp/sld/acc share one list. The author's own repeats are reported and left alone — Chashu Ramen twice, Bandage on both an item and an armour, two Blue Slimes, two Namelesses, nine Training Grounds. THE HUNTER'S QUEST. quest.hnt1 wants ten Raw Meat held at once, and Raw Meat rots: planner.chkrot runs once an in-game day, divided by a season modifier that makes summer twice as fast, and destroys amount*randf(.1,.2)+1 when it fires. A perishable stock converges rather than accumulating, so there is a ceiling and the quest needs it above ten. The mod never touched the drop table. It changed how long a rabbit takes to kill: MOD_ENEMY.kill targets eight landed swings, and at the point the quest is offered a Wild Rabbit has 3,574 HP against the base game's 93, so one that died in a single hit now takes ten. Ten times less meat an hour against an unchanged rot clock. Measured at the old 6%, taking a conservative fifteen seconds an end-to-end kill: steady state 15.6 normal, 51 winter, and 4.3 in summer against the ten required. Completable in three seasons and impossible in the fourth is a bug, and it is ours. Raised to 18%, solved backwards from the requirement (a steady state of 25 in the worst season and the first hunting area) rather than picked, and applied to every creature carrying rwmt1 — found by scanning the drop tables, not by naming the rabbit and the wolf. Worst case is now 26 against 10. Not an income either: a kill in the first hunting area yields 0.28 coin of meat. tests/names.mjs covers both. Verified it fails without either fix — the name half reports the skill clash and all five title collisions, the meat half a summer steady state of 4 against 10. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An item id is two schemes at once and the mod only knew about one. The
namespace block it knew: load() resolves a saved id with
itemgroup[(id+1)/10000<<0], so an item has to sit under 10000. The other is the
CLASS, which dscr type 1 reads straight off the number with no field for it:
id < 3000 Food + a "Tried:" footer
3000 <= id < 5000 Medicine/Tool
5000 <= id < 9000 Material/Misc
id >= 9000 Book + a "Read:" footer
stype is not a proxy — the author's stype 4 spans Food, Medicine and Book.
Everything the mod added was parked at 9100+ to clear his maxima, so 26 of its
32 items read as Books with a "Read: Never" line under them: the twelve
crafting materials, the four tonics, the ten breakthrough pills and the four
spirit pills. Only the six skillbook manuals were right, by accident.
Moved: tonics 4201-4204, breakthrough pills 4210-4219, spirit pills 4220-4223,
materials 5200-5211, manuals where they were. MOD_ITEM_IDS now holds the ranges
with the class table beside them, so the next addition picks from it rather
than from whatever is free above the author's highest.
Moving them was not free. The inventory saves {id, am, data} and restores by
matching id, so an entry whose id no longer exists is dropped without a word —
and a Tribulation Pill is seven million coin at the Pill Tower. Section 37
reads the blob before the game's load runs (that is what clears and rebuilds
inv), notes what the moved ids were carrying, lets the original load run
untouched, and gives those stacks back afterwards under the new ids. The save
is never rewritten, and anything unexpected in it makes the migration do
nothing — a migration that fails is better than a load that fails.
tests/ids.mjs gains 7 checks: the class of every added item, derived from the
mod's own tables rather than from the ids; that nothing which is not a book
gets the "Read:" footer; and a round trip through a save deliberately rewritten
back to 3.6's ids, which must come back with the same amounts and must not
double-grant on the next save. Verified they fail before the fix.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Tooling only — no change to mod.js, index.html, or any save. Every balance figure in this suite is a sample mean of something heavy-tailed: crit rate reaches 33% and a crit is ~8x a normal swing. The scripts reported point estimates — fightsmoke medians nine duels, allareas prints one number per matchup — with nothing to say how much of a gap between measurement and target was real. tests/lib/stats.mjs is the first shared code in tests/. Confidence intervals on Student's t rather than the normal approximation, because fightsmoke fights nine times per area and z at n=9 is ~15% too narrow — the one direction that turns noise into a confident wrong answer. Between table rows the critical value takes the lower-df (wider) row, never the narrower. Plus withinBand(), which asks the question that usually matters — is this practically on target — and fails two ways, by real drift or by an interval too wide to conclude anything, saying which. fightsmoke now prints a CI beside the median, and that was immediately useful: The Long Vigil's median of 2 swings carries an interval of [0.46, 4.87], ±83%. That number was being read as a measurement and it is noise. Flagged in place, not failed — nine fights in a heavy-tailed distribution are allowed to be imprecise. tests/targets.mjs is new and answers a question nothing else here asks. Every other balance script checks a RELATION — kill < die, whiff 0, nothing unwinnable — and none checks whether the model delivers its own dials, which a single matchup cannot tell you. It samples 45 spawns an area and compares against the target THAT SPAWN was solved for (_modKillT/_modDieT), not the flat dial. Result: every area tracks its per-spawn kill and die targets to within 1%, and kill < die holds 45/45 in all four areas sampled. Verified it fails — a 15% drift injected into _modDmg is caught at 0.869 of target. Four corrections along the way, every one to the new test and none to the mod: comparing against the flat dial rather than the per-spawn target (killT = kill * band^hpSpread * creatureShape, so a basement rat is legitimately a five-swing kill); omitting the hit rate, which pinned every die ratio at exactly 0.45 — MOD_ENEMY.hit, the tell; a plain sample mean instead of the crit stratification CLAUDE.md already demands, which put two of forty-five rank-duel spawns the wrong side of kill < die on noise alone; and testing significance rather than equivalence, which at ±0.1% precision fails on a 1% quantisation bias that means nothing in play. No third-party skill was vendored for this. The whole computation is mean ± t·sd/√n, and adding a Python dependency to a repo with 0 Python files and 27 Node scripts to do arithmetic the test scripts can do inline would be a dependency, not an improvement. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… given hpTrack (sections 9/10) raised max HP with offence by ASSIGNING you.hpmax at the end of the allbuff wrapper. The assignment did not survive: stat_r recomputes hpmax as (hp_r + hpa) * hpm * hpe, and the tick, fght, update_d and allbuff itself all call it constantly. Measured on a cap-60 character, a single stat_r() took max HP from 1,492,947 to 72,377, and sampling every 137ms across six seconds of real play found the low number in all 43 samples. The HP bar agreed: hp: 2,438/10,050. MOD_scaleEnemy reads you.hpmax in the instant after allbuff, while the assignment still stands, and sizes _modDmg against it. So every fight was built for a character with ~18x the health the character actually had — harmless early, worst at the cap — and all 2,790 allareas matchups were validating against a number the player never held. It writes you.hpm now, which IS an input to stat_r and therefore survives. Max HP holds through 200 consecutive stat_r calls. Three things needed a correction each, all caught by measurement: - hpm is SAVED (hpm:you.hpm in the save object), so the mod's factor is tracked in global.flags.mod_hpm and divided back out before re-applying — the MOD_applyMoneyDrops pairing, and the trap you.res once fell into. Verified across six save/load cycles and 20,000 allbuff calls: zero drift. - The recovered base needs flooring at 1. freshYou() resets you.hpm without clearing the flag, so the division recovered 1/18.86 and max HP collapsed to 12; allareas caught it at once with six rank duels flipped to kill > die. Flooring is an invariant, not a patch — only milestones touch hpm, and they only add. - Math.ceil, not Math.round: stat_r ceils, and one unit apart is enough for the durability check to fail. stat_r() is deliberately not called from inside the wrapper — the mod's skill use() calls add straight into you.str, and stat_r would recompute those from (r + a) * m * e and wipe them. tests/targets.mjs gains 5 checks, verified to fail against the old behaviour (1,492,947 -> 72,377 on one stat_r). All 2,790 allareas matchups still pass: they were measuring the right number all along, it was play that had the wrong one. Also adds -c-1 to tests/run.sh. Without it http-server sends max-age=3600 and a browser can be handed a mod.js from before the edit under test — which surfaced during this work as an allareas failure that would not reproduce standalone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sourced from Wuxiaworld's "General Glossary of Terms in Wuxia, Xianxia &
Xuanhuan Novels" -- the genre's own reference work -- and from the book
structure of two canonical long works there. Its sentence on bottlenecks
was the brief: cultivators "may require new Insights, the aid of
medicinal pills, or even harsher training" to break through. The mod
shipped with one of those three.
Section 38 adds the rest:
Spiritual Root (灵根) five grades by purity, rolled once at the
dojo, never a dead end -- the worst costs
cultivation speed, not access
Nine layers (层) derived from Qi Circulation between a
realm's threshold and the next, so a
breakthrough lands you at layer 1 on its
own. "Half a step to Nascent Soul" is the
genre's name for what this mod called a
bottleneck
Insight (参悟) the half of the price that cannot be
bought, from the three sources the glossary
names and no fourth of ours
Closed Door Training (闭关) a fifth action that refuses to start unless
you are at a wall. Consolidates, and
shelters: breaking through from inside it
cannot deviate you
Qi Deviation (走火入魔) what failing in the open costs now -- half
an in-game day unstable, never a death
Heavenly Tribulation (天劫) realm 10 was named Tribulation
Transcendence with no tribulation in it
Impurities (杂质) the genre's signature note at the moment
Fixed on the way: a realm's body multiplier did nothing at all. Section
28 assigned you.hpmax and you.satmax -- the same mistake v3.9 fixed for
hpTrack, still sitting in the realm code -- and stat_r recomputes both
from their inputs. Measured at realm 10: hpmax 332 after allbuff and 39
after one stat_r, satmax 1700 then 200.
Nothing writes hpm or satm now. Two delta-trackers on one field cannot
coexist, since each recovers its base by dividing out its own factor and
finds the other's inside it. Contributors publish a factor to MOD_BODY
and one idempotent applier writes the product, one flag per axis.
Verified: survives stat_r, zero drift over 200 calls and 5,000 allbuff
calls, Mortal untouched.
tests/actionlock.mjs counted four actions by literal and failed on a
legitimate fifth; the count is derived now. Full suite green, including
all 2,790 allareas matchups.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every skill gets a rarity, ten of them from Novice to Transcendent, and
the skill list paints each row in its colour. It is the rank of the
level you have taken the skill to -- MOD_rankForLevel against
MOD_RANK_AT, the same thresholds and colours titles use. Derived, not
assigned: a per-skill rarity would be a hundred numbers to keep in step
with a ladder that moves.
Jade Slips (玉简, from the same Wuxiaworld glossary section 38 used) fold
a skill into its section. Fold every skill in a section and it is one
line on the sheet instead of up to twenty-five. Sold at the Slip
Archive on the dojo lobby, at a price that climbs with use.
Folding HIDES a skill; it never removes it from you.skls. That is the
design, not a detail. Shortening you.skls breaks three things at once:
* the game's panel only redraws when the list grows, and the size it
compares against is captured once and never updated, so a removed
row stays on screen forever;
* giveSkExp pushes a skill back onto the list at its next level-up,
banner and all, so a fold would undo itself;
* you.skls is saved by id.
It works because you.skls was only ever the display list: the mod's
allbuff wrappers walk skl by key, and most base-game skills pay out
through milestones already written into stats. So a folded skill keeps
every level, perk and buff. Measured: every stat after folding all
hundred is exactly its unfolded value times the convergence bonus.
A converged section is worth x1.03 on every stat, compounding to x1.34
with all ten; a partial one pays a share in proportion.
The ten converged skills are created unconditionally at load, at the end
of skl, because exp and p are saved positionally over it. They are
aggregates -- no milestones, never trained -- so they take no titles and
perkcoverage skips them the way it already skips Renown.
Also a Skill handbook page in the wiki, beside the skills page.
tests/convergence.mjs, 43 checks. Full suite green, including all 2,790
allareas matchups.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v4.2 -- brought back in line with the original, measured.
tests/vanilla.mjs loads the game twice, once with mod.js blocked (the
author's game exactly), and runs the same code on both.
* Skills below level 10 now cost exactly what the original charges --
checked against HIS expnext, not a reimplementation. The old curve was
364x faster at level 10, inside the range he designed (all 22 of his
perk ladders end between 1 and 15). From 10 it rises ~3%/level at a
solved ratio, so 110 is exactly as far away as before.
* Skill xp 1x and enemy coin drops off by default -- the original's
values. Both settings boxes still work and a saved value still wins.
* Fixed: the six masteries were stuck on the original's curve (level
20 cost 7,474,050 instead of 375) because the curve was installed
before they existed. It is installed again at the end of mod.js.
* Fixed: a damage sample with no crit fell back to a plain mean that
DROPS the crits (30% of damage at cap 30). ~5% of spawns came out
~20% weak, and the tests read fights 40% long 0.15% of the time,
which is what intermittently failed the rank duels in allareas.
Empty strata are topped up now, in the mod and all five test copies.
* Fights are the one axis deliberately not matched: the original's
early fights take ~1 swing because you out-level fixed creatures.
The test prints the gap instead of asserting a parity.
* Number formats: short / sci / myriad / as the original. Up to 9,999
every mode prints what the original prints -- his own threshold, from
a printDamageNumber that called formatw and discarded the result.
v4.3 -- small fixes.
* The realm sits on the rank line and is the breakthrough button at a
bottleneck. The panel is a full 310px box, so no new line, and ASCII
only: a triangle and a warning sign each grew it, measured.
* A "What next" wiki page reading the save, built on each system's own
gate so it cannot say a locked door is open.
* Rarity named in the skill tooltip; sort by rarity; a rarity filter.
* The skill panel rebuilt every row every second forever once the list
grew (the game's sklsize is never updated). Replaced its updater.
* The Damp cellar had no spawn weights, no entrance and an exit into
screens never written. Notice 23html#1 on the Message Board now.
* Four unfinished titles finished from the author's own intent; ddcd
left alone, nothing says what it was for.
* Rank-duel margin 1.25 -> 1.4; allareas gains a fully merged build
(3,720 matchups); three dead allbuff wrappers removed.
Full suite green, including tests/vanilla.mjs and tests/polish.mjs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Documentation, mostly. MOD-NOTES.md opens with an overview of the mod as it stands at 4.3 and gains the missing chapters for 4.0-4.3; older passages that later versions changed carry a dated note rather than being rewritten. README.md and tests/README.md describe 43 sections, 31 test scripts and 3,720 matchups. Two errors corrected on the way: the top realm is Tribulation Transcendence, not "Ascendant", and there are ten realms above Mortal, not seven. CLAUDE.md: the author's changelog is now checked against upstream/main (origin is the fork since the remotes were renamed), plus current action, id-range, matchup and line counts, and the dead wrappers it said were left in place are gone since 4.3. modHelp() listed six commands from the first version and said skill exp resets on reload. It lists all fifty-four now, grouped by purpose, and tests/polish.mjs reads every mod-/set-/get-/reset- function out of mod.js and fails if one is missing from the help. The comments in sections 38 and 39, and two lines of the 4.0 changelog entry, quoted Wuxiaworld's glossary at length. They paraphrase and cite it instead. No behaviour changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v4.4. Section 0: the first time a new version of the mod loads, the live save and all three slots are copied into p23_backup_1..3 before the game has read anything. It is the first code in mod.js and depends on nothing below it, because an update that breaks is exactly when the backup is wanted, and a section that throws stops every line after it while the game goes on loading and autosaving. Three kept, oldest replaced first by sequence number (the clock ties within a millisecond). Section 44: the saves panel lists the backups, restores one, or backs up on demand. Restoring puts every slot back, backs up the present first so it can be undone, and reloads. It restores saves, not code. tests/backups.mjs caught a real bug before it shipped: with no room, the first version cleared every older backup, retried, and lost them anyway when the retry failed. It now frees only the one it is replacing. Section 45: a Pacing setting. Original (the default since 4.2) or Mod before 4.2 -- 50 x 1.106^level exactly, via a new MOD_XP.fixedRatio that skips the solve, plus 2x exp and a 15% coin drop. Level 110 is the same distance away in both, and fights are unchanged. The preset is stored and its curve re-applied at load; changing a box reads Custom. tests/pacing.mjs checks every level 0-109 against the old curve. Docs, changelog and CLAUDE.md updated. Full suite green, 33 scripts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
….5, v4.6) v4.5 - Three bugs in the author's code fixed: Death took more energy as it levelled (negative past 10), Shield Mastery could add defence to damage taken, and luck never reached the crit roll (now capped at 50% from luck) - Breakthrough history; daily backups and backups as downloadable files; shop prices follow the number format - Tests for the cultivation road and the three fixes, and a committed balance baseline (tests/baselines/balance.json) v4.6 - Fights setting (section 47): Original, the default, leaves the author's areas exactly as he wrote them (all 70 spawns match his page) with his 6% Raw Meat; Scaled is the mod's model everywhere. The mod's own areas are always scaled - Realm walls sized to the climb: insight, seclusion and deviation scale with the Circulate Qi time to each realm; meditation earns insight only at a wall - Pill Tower realm 8-10 pills repriced for no coin drop; tests/econ.mjs holds every required price to 10% of the climb it gates - Userscript to run the mod on the hosted game (tools/, userscript/) - Safari: skill rows no longer wrap under a classic scrollbar, dropdowns match the panel, -webkit-user-select; skill panel writes only what changed - Realm tooltip showed source code instead of text - Tooling: tests on GitHub (Chromium and WebKit), BROWSER=webkit, parallel run.sh with timings, tests/authorfiles.sh Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…v4.7) - Qi Settling Pill costs the breakthrough pill of the realm at the wall (never under 5,200), in the listing and in rows already stocked - The rank line shows the wall's insight, have/need - The mod's settings are written into the save and adopted by a browser that has none of its own; a browser's own settings are never overwritten - The log says when the mod was updated, and where things are on a first install; Safari is reminded to download a backup; the userscript says in the game when the author's game is an untested version - No spinner arrows on the mod's number boxes; a real monospace in the wiki; the Mod before 4.2 preset labelled legacy - Wiki says where near-death insight happens on Original fights - Tests: tooltips.mjs renders every tooltip and fails on code or missing values; small.mjs covers section 48; econ.mjs prices the optional pills; fights.mjs measures the brink; tests wait two game ticks instead of 4.5s Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…in CI Settings are adopted from a save once per browser, so a slot switch cannot pull another slot's snapshot in. tests/docs.mjs checks the counts the docs state. Firefox joins the GitHub matrix. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Rank 2 and up shows its number after the skill name, drawn by CSS from an attribute so colour is not the only signal. CI caches the Playwright browsers. Whole suite verified on Firefox locally. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Pill Tower chances for realms 8/9/10 go to 26/20/15% (mean wait 4.6/6/8 real hours; realm 10 was ~20). tests/cultivation.mjs holds 8 hours. README explains serving the fork from GitHub Pages; .nojekyll added. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
It was gitignored, so npm ci, the npm cache and the browser cache key all failed at the first step of every GitHub run. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The page list shows the match count per page, dims pages with none, and Enter jumps to the first page with a match. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ction 49) Browsers slow a hidden tab's chained setTimeout to about once a minute. The mod replays up to an hour of missed ticks plus the action's seconds, never combat. setCatchUp(sec), persisted and carried in the save. tests/catchup.mjs. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…llow step Section 50 hands the author's unteachable dishes over as Cooking levels rise. tests/screens.mjs draws every mod screen with a capped character and long name; it found the gate's two locked hints wrapping, now shortened with the sentence in a tooltip. fights.mjs holds the step into the Hollow winnable with margin. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
All 48 creatures held the one shared eqp.dummy, onto which the author wrote 32 per-creature affinity lines, and the player's fists (the same object) leaked their level into every creature's damage. Each creature now owns its gear. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…l once The dojo pays those pills at character levels, the walls are Circulate Qi levels; the two clocks differ. Also adds the missing test README rows. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Hello — this is a mod of proto23 built on top of your game. Opening it as a PR because it turned up a handful of genuine bugs in the base game that you may want regardless of what you think of the rest, and it seemed better to show you than not.
I would not expect you to merge this as-is. It is 15k lines and it makes opinionated design changes to your game. It is probably most useful as something to read, cherry-pick from, or ignore. The bug findings near the bottom are the part I would actually flag.
How it is put together
Everything lives in
mod.js, loaded by one line appended to the bottom ofindex.html:That tag is the only change to any file of yours. Deleting the line reverts everything.
mod.jswraps rather than edits: it loads last, shares global scope, and preserves every return value it intercepts. Nothing in your file is rewritten.The mod's own changelog lives in a separate
changelog/mod-changelog.html.changelog/changelog.htmlis yours and is byte-identical tomain— an earlier revision of this branch prepended to it, and that was moved out precisely so the script tag stays the only edit.What it changes
skl,area,item,ttl,act) rather than written, so it cannot disagree with the game.MOD-NOTES.mdis the full design record with the reasoning behind each decision.Things in your code you may want to know about
These are base-game findings, independent of the mod:
dscr()type 5, rank 7 throws. The branch setsthis.dl.style; every other branch setsthis.label.style, andthis.dlis only assigned in the type 6/7 branches. It has never run because no base-game title exceedsrar5, so the tooltip would die half-built the first time one did.chss.catamn— the onlysmove(chss.catamn)is from insidecata1going back.area.clg(Damp cellar) can never spawn anything. Itspopentries carry noc, soz_bakeproduces NaN bands and no roll can ever match. Left as-is and flagged in the wiki rather than fixed.eqp.dummyis shared by every creature, andlvlupwrites the player's unarmed progression into it (aff[0] = you.lvl/5,cls[2] = you.lvl/4). So every monster silently inherits it; by character level 20 that is a 2.1x multiplier on their damage.dmg_calc'sbmultiplier never reachesctr_r— both branches that set it shadow it with aletinside their own block, so theyou.luck/25+1crit bonus is dead.shdchigh, the final multiplier(100 - (aff*5*(1+shdc/20) + cls*5*(1+shdc/20)))/100inverts, so the player's defence is added to incoming damage instead of subtracted.enmondren, and thedflsell rates are defined but never wired up.Testing
25 Playwright scripts under
tests/, driving a real browser against a served copy of the game.npm testruns them. The broadest checks every area, every creature, both ends of every level band, ten story tiers and three skill builds — 2,790 matchups, all required to be winnable.tests/ids.mjsexists because the mod shipped an id collision that doubled the skill sheet on every save/load, which is worth knowing about if you ever add skills programmatically: the loader matches ids in a loop with nobreak.Thanks for making the game. Happy to split any of this up, drop most of it, or close it if it is not something you want near the repo.
🤖 Generated with Claude Code