YaKyoLife
A text-only Taiwanese baseball career sim: high-school tournaments, the draft, CPBL → NPB → MLB, Tommy John surgery, free agency and the Hall of Fame vote — all driven by a seeded PRNG, so the same seed plus the same choices replays the same life.
The seven dimensions
memoryTechnical Depth 7.5/10
No renderer to show off — the depth is in the domain model. career.js converts positional difficulty into runs and rescales it to each league's actual season length (120/143/162 games) rather than hardcoding 162, then applies ERA/WHIP and AVG/OPS quality factors so stat-padding cannot outrank suppression. season.js derives rate stats from ability-minus-league-par with normal noise and enforces physical constraints (SV+HLD+W+L ≤ games, steals capped by times on base). injury.js models ligament load as pitch-quality × effort × role-normalized workload with post-surgery scar multipliers. Around it: a seeded mulberry-style PRNG (rng.js), deterministic team-strength hashing from SEED|season|year|team so a whole league can be rebuilt without storing it, a two-pass measure-then-paint Canvas renderer for the settlement image, WCAG relative-luminance math to pick readable text on all 48 team colours (dom.js:29), hand-rolled iOS double-tap zoom suppression that exempts drags (main.js:20-37), and a PWA manifest built at runtime as a Blob URL (main.js:148-165).
stadia_controllerGame Design 8.5/10
The strongest design dimension in this audit, because it is the only one measured. The changelog shows full-flow Monte Carlo runs (11 career paths × 1,400 lives; one pass over 12,600 careers) used to re-anchor the whole evaluation ladder to explicit targets — Hall of Fame = 15% of players, historic-tier = 25% of inductees. Those runs found real breakage: HOF thresholds calibrated against ability 68–80 when the potential generator's median pitcher ceiling is 61.0, so the NPB and MLB pitcher lines sat *above any reachable player*; closers whose 10 save titles converted to 5% of their career score; MLB being a dead end (4.1% HOF) because it was penalized twice; and induction year drawn as ri(2,6) random, making the vote independent of how good you were. Each fix is argued with before/after rates. The loop itself — three-phase years, 92 event cards with three risk modes each, 34 hidden traits, a dice-allocation cost curve, contract/FA/posting rules — has genuine trade-offs rather than pure stat stacking.
securityBackend & Security 5.5/10
There is no backend by design: no accounts, no server, nothing leaves the browser, and no secrets exist to leak. Input validation on the start screen is careful — name and jersey are checked with setCustomValidity and a half-filled form is rejected rather than silently mixed with defaults. The gap is the seed: rng.js:2 reads ?seed= straight from the URL with no sanitizing or length limit, and the retirement screen passes it into choose(), which assigns innerHTML (dom.js:127). A crafted link therefore injects markup — in exactly the share-a-seed-link feature the game promotes, though only for a visitor who plays a career through to retirement. The player name takes the same unescaped path into card(), but that one is self-supplied and capped at 10 characters.
codeCode Quality 7.5/10
The best-commented codebase in this audit. 45 files in a clean core / data / engine / flow / ui split, and the comments explain why with numbers attached: why team colours became chip backgrounds (29 of 48 fell below 3:1 contrast), why old year-blocks are no longer pruned from the DOM (it broke timeline scroll and was never the saving it looked like), why a hard third-TJ deadline was removed (it made the player's decisions meaningless). Real PR flow too: 25 merges, feature branches from a second contributor, a DEV → main release ritual. The debts: newState() returns a single ~100-field god object with a 40-boolean traits map (state.js:8), season.js still imports what its own comment calls a 'temporary scaffold' from contract/awards/intl, a few files degrade into single-line walls (dom.js:86), and every one of the 173 ?v=1.5.8 import tokens has to be bumped by hand on each release.
experimentTesting 2.5/10
No test files, no CI, no linter, no package.json — nothing runs automatically. And yet this project verifies more than any other here: the changelog reports simulated populations (N=250 per configuration, up to 12,600 careers per calibration) with before/after induction rates for every balance change. That is empirical validation, just not reproducible by anyone else — the simulation harness is not in the repository, so no reviewer can re-run a single number, and no regression can be caught automatically. The pure functions most worth testing (careerScore, honorScore, tjAccrue, the stat-consistency clamps) are exactly the ones the simulations exercise informally and the repo tests not at all.
verifiedCompleteness 8.5/10
Shipped like a product, not a prototype: live on its own domain, installable as a PWA, four themes, separate desktop and mobile layouts, a large-text mode, an accidental-reload guard, a Canvas settlement card with a system share sheet, and a replay link. Around the game sit a 506-line wiki, a changelog covering 32 released versions in 18 days, a Discord community and a sponsorship page. Content is deep too — 92 event cards (each with three risk modes and separate success/failure text), 34 traits, 48 fictional teams across three leagues. Every one of the 18 days from first commit to last has commits on it.
emoji_objectsOriginality 7.5/10
Baseball career sims exist (Power Pros' Success mode, OOTP), and the year-loop-plus-event-cards skeleton is a known shape. What is genuinely scarce is this subject: a Traditional-Chinese sim built around the Taiwanese path specifically — high-school tournaments, the CPBL draft, going to Japan or the US, international call-ups, the posting fee, coming home. The 48 team names are parodies close enough to read instantly and far enough to stay clear of trademarks. The seeded-life framing (a shareable seed as a replayable life) and a Hall-of-Fame vote whose ballot share is derived from how far you cleared the line are both more than genre furniture.
trending_upHighlights
- Balance calibrated by simulation — full-flow Monte Carlo runs re-anchored the evaluation ladder to explicit targets (HOF = 15% of all players) after proving the old thresholds were unreachable (
CHANGELOG.mdv1.5.8) - Season-length-aware positional value — defensive difficulty is converted to runs and rescaled per league (120/143/162 games) instead of a hardcoded 162, so shorter leagues do not silently lose positional credit (
career.js) - A ligament-load model, not a dice roll — TJ risk accrues from pitch quality × effort × role-normalized workload, with scar multipliers after each surgery, tuned against real-world surgery frequency (
injury.js:9-33) - Deterministic worlds from one string — team strength is hashed from
SEED|season|year|team, so an entire league's history is reproducible without being stored (contract.js:104) - Contrast math for 48 team colours — jersey primaries became chip backgrounds with luminance-picked text after 29 of 48 measured below 3:1 as text (
dom.js:26-33)
trending_downWeaknesses
- Unsanitized seed reaches
innerHTML—?seed=is read raw (rng.js:2) and rendered throughchoose()at retirement (dom.js:127), so a crafted share link can inject markup - The simulation harness is not in the repo — every balance claim in the changelog is unverifiable and unrepeatable by anyone else
- One ~100-field god state —
newState()returns every counter, flag and 40-boolean trait map in a single object (state.js:8) - 173 hand-maintained cache-bust tokens — every
?v=1.5.8import specifier must be bumped in lockstep at each release; one miss ships a mixed-version module graph - No tests, no CI, no linter — not even a
package.json; nothing at all runs before a release goes live
The changelog that runs the numbers
Most hobby games tune balance by feel. This one re-derived it. The v1.5.8 entry reports that the Hall-of-Fame thresholds in every previous version had been calibrated against static ability values of 68–80 — a range no player can reach, because the potential generator caps a pitcher's three power tools at a median ceiling of 61.0 and a theoretical maximum of 69.3. The NPB (69.8) and MLB (72.2) pitcher lines were therefore above any attainable player, and the measured Hall-of-Fame rate across the whole player population was 1.2%.
The fix is equally characteristic: rather than lowering the visible line, the league coefficients were scaled by 1.8834 — mathematically identical to dividing the thresholds, but it keeps the number players already recognize (7,500) on screen. The same entry re-rates closers (a 10-save-title career was scoring 354 of ~7,000 points), stops MLB from being a Hall-of-Fame dead end (4.1% → 20.9%), and replaces a random ri(2,6) induction year with one derived from how far the player cleared the line.
It is the clearest case in this audit of a hobby project doing something professional studios often skip — and the clearest example of the audit's own recurring gap, because none of that simulation code is in the repository.
smart_toyAI-assisted development
Openly co-developed with AI, but not AI-run: Claude is the commit author on 39 of 267 commits and co-signs 30 more, alongside four human identities — the owner (also credited on the title screen as 最先生 Mr.TheMost), a second developer contributing through pull requests, and merge commits from a DEV branch. The human fingerprints are the interesting part: events.js opens by naming the spreadsheet its 92 cards were generated from, notes exactly which five IDs the source workbook was missing, and instructs that they must not be invented without source text — a guardrail against exactly the failure mode AI content generation invites.
summarizeFacts
| Stack | Vanilla JS (ES modules) · No build step · Canvas 2D · CSS custom properties · PWA · GitHub Pages |
|---|---|
| Genre | Text career life-sim |
| Period | 2026-08-07 → 2026-08-24 |
| Active dev days | 18 days |
| Commits | 267 |
| Lines of code | ~8,736 |
| Author | Leo Chang · 最先生 (LeoGGcat) |
Verdict
The deepest simulation in this audit and the most rigorous balance work, shipped as a real product in 18 days with a changelog that argues its own numbers. Its ceiling is set by process, not craft: no tests, no CI, a calibration harness that lives outside the repo, and one unsanitized URL parameter reaching innerHTML in the very feature built for sharing.