Skip to content
Ashaveth key art
Class 88 · Measurement · Free, no signup

46 Documents.
One Was Stale in 32 Minutes.

Ashaveth ships a better written record than most commercial projects — 46 markdown documents, 441,416 bytes. Measured against the code, nearly every authoritative one is wrong. This is that audit, document by document, and the pattern in the same repository that fixes it.

46 documents 23,011 lines of source 667 tests 15.5% understated bundle 32 min to stale ~45 min read

Quick answer

A well-documented project is not a safe project. Ashaveth carries 46 markdown documents, written carefully by people who cared, and measured on 2026-09-01 nearly every authoritative one disagrees with the code it describes — a self-declared "single source of truth" wrong on essentially every number, a balance audit that went stale 32 minutes after it was saved, and a bundle size published 15.5% under what the server actually sends. None of it is dishonest. The code simply moved faster than anyone could maintain the sentences about it. The cure is in the same repository: a handful of tests whose only job is to assert facts about the project itself. Prose decays silently. Assertions fail loudly.

Key takeaways

  1. Documentation quantity is not documentation accuracy. 441 KB of careful writing, and the numbers still drifted.
  2. A document can be stale in half an hour. Audit saved 16:18; the two fixes it demanded landed 16:50 and 16:52 the same day. Nobody went back.
  3. The number your bundler prints is not the number your server sends. 122 kB claimed, 140,856 bytes measured over the wire.
  4. Write the fact as an assertion, not a sentence. A test can hard-code a banned string and fail the day it reappears.
  5. Assert against source when behaviour isn't observable — one guard here reads the provider's own file and checks every call site.
  6. Count the exit paths before you trust a gate. Five of ten checks in this repo can actually stop you shipping. Five cannot.
Module 00

The game this is about

First Play it for two minutes. Everything after this is a claim about this game, and half the point of the class is checking claims.

The live build at ashaveth.ddsboston.com, embedded. Click-to-load on purpose — nothing fetches until you ask for it. Self-rated 18+.

Ashaveth is a free browser role-playing game: 50 levels across five biomes, 72 monsters, four gate bosses and a final boss, three endings, and live AI-written dialogue for the three residents of the last town. The world is a palimpsest — a page written on before, being scraped clean — and something has reached the last town.

On the name

Everything in Ashaveth is original to Design Delight Studio: the setting, the 72 monsters, the five bosses, the three named residents of Sallowmere, and the forge-only sword called Godsbane. It is not based on or affiliated with any other franchise. Its lineage is the 1990s bulletin-board door game as a genre — a daily budget of fights, a real-calendar reset, a death penalty measured in net worth — rebuilt with a story that takes those conventions seriously.

The Whispering Woods
Whispering Woods
The Drowned Host
Drowned Host
The Obsidian Peaks
Obsidian Peaks
The Void Edge
Void Edge
The Dragon's Domain
Dragon's Domain
23,011lines of .ts and .tsx
667tests, 32 files, 0 failures
192pre-generated art assets
4runtime dependencies

All four of those numbers are measured, and I will come back to two of them, because the project's own documents give different ones.

Module 01

The corpus, and what happened to it

The setup 46 markdown documents. 441,416 bytes. Written carefully. Nearly every authoritative one is wrong today.

Start with the thing that makes this interesting: this is not a badly documented project. MEASURED on 2026-09-01, excluding dependencies and build output, the repository carries 46 markdown documents totalling 441,416 bytes — an architecture doc, a save-format spec, a testing strategy, a deploy topology, a monster-art plan, a claims register, a build dossier, three separate audits, and a self-declared canonical fact sheet.

That is more written record than most commercial projects carry. It is also why the result is worth your time. A badly documented project having bad documentation teaches nothing. A carefully documented project whose documents are nearly all wrong within weeks teaches something structural.

Every authoritative document, measured against the code ASHAVETH v1.2.0 · MEASURED 2026-09-01 · 46 markdown documents, 441,416 bytes THE DOCUMENT IT SAYS MEASURED TODAY DRIFT ASHAVETH-CANONICAL-FACT-SHEET-v1.md “single source of truth for all marketing” 25,000 HP · 103 art · 154 tests 13,000 · 192 · 667 wrong on every number AUDIT_REPORT.md 59,716 B — the largest doc in the repo ~105 open defects 11 of 11 sampled already fixed 1 commit ever, the root commit AUDIT-GAME-MATH-CLAUDECODE.md the balance audit unwinnable · 2 findings open 60–72% win · both fixed stale by 32 minutes LAUNCH.md the launch runbook “fallback indistinguishable” banner added the next day stale by 1 day ashaveth-dossier/HANDOFF.md the handover note ~333 AI calls/day MAX_DAILY_TOTAL = 5000 off by 15× metrics.md · test-map.md the metrics pair 154 tests, 7 files 667 tests, 32 files off by 4× the live landing page public marketing copy “compresses to 122 kB” 140,856 B over the wire understated 15.5%
Every authoritative document, measured against the code. Each row is a document that a reasonable engineer would treat as a source of truth. The right-hand column is what a measurement on 2026-09-01 actually returns. Nothing here is dishonest — every one of these was accurate the day it was written.

The self-declared source of truth

One document opens by calling itself the "single source of truth for all Ashaveth marketing." Measured against the code it describes:

The fact sheet saysMeasured 2026-09-01
Final boss has 25,000 HP13,000wrong
103 art assets192wrong
154 tests across 7 files667 across 32wrong
Godsbane deals to the final boss2.5×wrong
Death costs half your carried gold15% of carried + bankedwrong
Entry bundle 327 kB370 kBwrong

Every one of those was true of version 1.0.0. The game shipped 1.2.0. The document was never re-run. A document that calls itself the single source of truth and is not regenerated is the most dangerous artifact in a repository, because its title discourages exactly the check that would catch it.

Interactive · Ledgerclaim 1 of 5

Five documents, five measurements

Each row pairs a claim from a repository document with a measurement taken on 2026-09-01. Decide whether the measurement supports the claim.

The canonical fact sheet is the single source of truth

Your answers stay in this browser. Nothing is sent anywhere. Reset clears them.

Six claims. Which hold?

Before reading on, work these yourself. Each is a real sentence from a real document in this repository.

Interactive · Decidercase 1 of 6

Is the document telling the truth about the code?

Six claims, quoted from the repository's own documents. For each: is it true of the code today?

The canned fallback is deliberately indistinguishable from working AI.

LAUNCH.md line 11, written 2026-07-12
the fix landed 2026-07-13

Your answers stay in this browser. Nothing is sent anywhere. Reset clears them.

Module 02

Stale in thirty-two minutes

The sharpest case An audit is saved at 16:18. The two defects it demands be fixed are fixed at 16:50 and 16:52. The audit is never touched again.

The repository contains a balance audit — a careful, specific document that names defects with file references and proposes fixes. Two of its findings concern exploits:

  • A character conversation offers a free, repeatable full heal, which undercuts the inn as a gold sink because talking costs no fights.
  • Forest encounter gates roll unseeded randomness while the outcome is seeded, so a player can reload the page to re-roll the gate.

Now the timestamps. MEASURED from file modification times:

TimeFileWhat happened
16:18AUDIT-GAME-MATH-CLAUDECODE.mdThe audit is saved. Both findings recorded as open.
16:50NPCInteractionScreen.tsxThe heal is gated to once per character per day. The comment cites the audit's own reasoning.
16:52ForestScreen.tsxThe gate roll is moved to a seeded generator keyed on day, health, gold, fights and biome. Comment: "NOT reload-rerollable."
AUDIT-GAME-MATH-CLAUDECODE.mdNever updated. Still reports both as open, today.

Thirty-two minutes. The developer read the audit, fixed both defects immediately, wrote comments in the code quoting the audit's own language — and did not go back to the document, because at that moment the document had already done its job. It had communicated. Its remaining life as a record was nobody's task.

This class's own research fell for it

I dispatched six read-only specialists over this repository. One was told, explicitly, to verify claims rather than accept them. It read the audit, could not check those two findings because they sat outside its assigned files, and reported them as current open risks. A second specialist, whose scope did include those files, found both fixed and produced the timestamps above.

So: a research process built specifically to catch stale documents was itself misled by a stale document, mid-research, in the class about stale documents. That is not an embarrassing aside. It is the finding. The failure mode does not require carelessness. It only requires a reader who has a document and does not have the file.

The same shape, four more times

Once you look for it, the pattern is everywhere in this repository, and every instance is innocent:

DocumentWent wrong whenBecause
LAUNCH.mdthe next dayA production outage forced the AI fallback to announce itself. The runbook still says it is undetectable.
ASHAVETH-ADS-AUDIT.mdwhen the banner was deletedIt describes a rotating advert that no longer exists. A test now asserts it stays deleted.
proxy/DEPLOY.mdon its second config changeOne commit, ever. Still documents a wildcard origin and model names that are gone.
AUDIT_REPORT.mdbefore version control existed59,716 bytes, the largest document here. Committed once, at the root commit. It describes a prototype deleted before git init ran.

That last one is worth sitting with. The largest document in the repository has exactly one commit — the first one — and has never been touched across 54 subsequent commits. It reads as a live defect list. Eleven of its findings were sampled against current source; eleven were already fixed or described code that no longer exists. Its subject is a 1,543-line component. That component is now 185 lines.

Module 03

The number the bundler prints

Measurement The landing page says the bundle compresses to 122 kB. Fetching that exact file returns 140,856 bytes. Both numbers are honest. Only one is about the user.

The public page states, twice, that Ashaveth's 370 kB entry bundle "compresses to 122 kB gzipped." That figure came from the build output, where it is printed in a tidy column next to the raw size. It is exactly right about what the bundler did.

Here is the same file, fetched from the live server:

measured 2026-09-01ashaveth.ddsboston.com
curl -s -o /dev/null -w '%{size_download}' \
     https://ashaveth.ddsboston.com/assets/index-DdQpyQ8e.js
371388

curl -s -H 'Accept-Encoding: gzip' -o e.gz \
     https://ashaveth.ddsboston.com/assets/index-DdQpyQ8e.js && wc -c < e.gz
140856          # content-encoding: gzip
The number the bundler prints is not the number the server sends /assets/index-DdQpyQ8e.js · MEASURED live 2026-09-01 Raw bundle on disk 371,388 B what the file weighs · stat -c%s What the page claims 122,000 B “compresses to 122 kB gzipped” · stated twice What the server sends 140,856 B curl -H 'Accept-Encoding: gzip' · MEASURED +18,856 bytes (15.5% more) than the published figure — because nginx.conf.template never sets gzip_comp_level, so it compresses at its weakest default.
18,856 bytes nobody accounted for. The gap is not compression failing — it is two different compressors. The bundler estimates with a strong setting; nginx serves with its weakest, because gzip_comp_level is never set in the configuration. Both numbers are correct about their own subject.
nginx.conf.templatelines 10–12
gzip on;
gzip_types text/plain text/css application/javascript application/json image/svg+xml;
gzip_min_length 1024;
# no gzip_comp_level — so nginx uses level 1: fastest, weakest

Nobody lied. A number was copied out of a build log into marketing copy, and the build log was measuring something the server does not do. Build-time output describes what a tool did. It does not describe what a user receives. The only way to know the second is to fetch the artifact from production and weigh it.

The same trap, in the opposite direction, on this very page

Every image on this page is the game's own art, uploaded byte-identical from the repository. Shopify's CDN then re-encoded them — and made several larger: the final boss plate went from 53,494 bytes on disk to 89,785 bytes served, dimensions unchanged. If I had captioned these with their repository sizes, I would have made exactly the mistake this module is about, in a module about not making it.

Interactive · Huntround 1 of 3

Which measurement is the one a user experiences?

Three rounds. In each, one of the numbers is the one that describes what actually reaches a person.

A JavaScript bundlewhich one is the download?
four numbers exist for the same file

Your answers stay in this browser. Nothing is sent anywhere. Reset clears them.

Module 04

The cure: documentation that executes

The answer One file in this repository cannot go stale, because it is not prose. It is a test whose only job is to assert facts about the project.

The game was rebuilt under a new name. A rename is exactly the kind of fact that gets written down once and then rots — and here it very nearly did. The file that fixed it opens by stating the failure plainly:

"The rebrand changed the marketing and the metadata — and stopped before the code. So the FINAL BOSS still rendered under the pre-rebrand name, in two places a player could see... Meanwhile Wren and Vosk's dialogue said 'Ashaveth', and the in-game Almanac said 'ASHAVETH, THE RED'. The game's own fiction and its own UI disagreed about the name of its final boss."

And then the trap that made it expensive:

"the image cache key for a monster is derived from its name... A naive rename ORPHANS THE SHIPPED PORTRAIT — assets are pre-baked into the repo and keyed by string. The fix is to PIN the art key in the data, so the display name is free to change and the image still resolves."

A markdown file saying "we renamed the boss; remember to pin the art key" would have been accurate on the day it was written and silent forever afterwards. Instead the fact was written as nine assertions:

services/ipClearance.test.tswhat it enforces
the final boss displays as Ashaveth, the Red
no banned name appears in any monster's display name
their ability names are grimdark too — no Pixel Bite, no Taser
every renamed monster pins an art key, and the pin != the display name
no provider rebuilds the art key from the display name   ← reads SOURCE
a save that killed the old-named boss is migrated on load
other boss kills are left alone
a save with no kills does not explode
every forge-stage boss still exists — the rename orphaned no gate

That is a complete, executable specification of a rename: the display rule, the art-key invariant, the transit invariant, the save migration, and the progression gate. It is about 7.6 KB. It replaces a paragraph that would have been wrong within a month, and it fails the moment any of those facts stops being true.

Interactive · Anatomyregion

The anatomy of a document that cannot go stale

The rename regression suite, trimmed. Click any highlighted line to see what that part is load-bearing for.

// IP-CLEARANCE REGRESSION SUITE.//// So the FINAL BOSS still rendered under the pre-rebrand name.

Your answers stay in this browser. Nothing is sent anywhere. Reset clears them.

The same pattern, four more times

Once you have the idea, this repository is full of it — each one a fact somebody refused to leave in prose:

FileThe fact it refuses to let rot
thinkingBudget.test.tsReads the AI provider's own source by regular expression and asserts every call site pins a configuration flag. It guards a shipped bug where hidden reasoning tokens ate the budget and truncated combat narration to four characters — invisible to an empty-string fallback, because the string was not empty.
endingContrast.test.tsAsserts the old colour would now fail contrast. A fix that only asserts the new value can be silently reverted by a restyle; one that asserts the old value cannot.
ads.test.tsAsserts a deleted component stays deleted, and reads source to confirm there is no third-party tracker or pixel.
check-secrets.mjsScans the build output for six credential patterns and exits non-zero. Wired as an automatic post-build step, so it also runs inside the Docker build.
AddToHomeScreen.test.tsxAsserts the service worker never caches the entry bundle, so a stale build can never be served to a returning player.

The rule

When a fact matters, write it as an assertion, not a sentence. Prose decays silently — it keeps its confident formatting while quietly becoming false. An assertion decays loudly: the day the fact changes, something red happens. You do not need to document everything this way. You need to do it for the claims you cannot afford to be wrong about.

What stops a bad rename

Push a change through this project's actual pipeline and see which check catches it.

Interactive · Gate Runnercandidate 1 of 5

Which check stops it?

Five changes entering the build. Pick the check you think refuses each one, then see how far it actually gets.

A rename that puts the pre-rebrand boss name back on screen

Your answers stay in this browser. Nothing is sent anywhere. Reset clears them.

Module 05

The tests lie too, in the other direction

The counterweight If documents overstate what is broken, tests overstate what is safe. Five of ten checks here cannot fail.

A class that only criticised prose would be half a class. The same repository shows the opposite failure just as clearly: a green suite that is narrower than it looks.

Ten checks. Five can stop you shipping. Five cannot. MEASURED from package.json, vite.config.ts and repo-wide grep · 2026-09-01 · there is no CI CHECK HOW IT RUNS WHAT IT ASSERTS ON FAILURE check-secrets.mjs postbuild, automatic scans dist/ for 6 credential patterns process.exit(1) ipClearance.test.ts in npm test 9 assertions: banned names, art-key pin, save migration fails the suite thinkingBudget.test.ts in npm test reads provider SOURCE, asserts every call site pins the budget fails the suite endingContrast.test.ts in npm test asserts the OLD colour would now fail fails the suite ads.test.ts in npm test asserts a deleted component stays deleted fails the suite verify-gate.mjs wired to NOTHING imports puppeteer from a hardcoded /tmp path never runs verify-reduced.mjs wired to NOTHING same hardcoded /tmp import never runs lh-title.mjs wired to NOTHING needs a lighthouse binary nobody installs never runs qa-images.py manual prints FAIL and falls off the end of main() always exit 0 _audit_sim.test.ts EXCLUDED from npm test 3 assertions, all expect(true).toBe(true) cannot fail
Ten checks, measured. Five can stop you shipping. Five cannot — and four of those five look exactly like the ones that can. There is no continuous integration in this project at all, so every green check runs on whoever remembers to type the command.

The balance harness that cannot fail

The combat engine's three tuning constants carry comments saying they were "tuned in the harness." The harness is real: it drives the shipped engine through 30,000 simulated fights and prints a win-rate table. Here is every assertion in it:

_audit_sim.test.ts3 of 3 assertions, MEASURED
line 185:    expect(true).toBe(true);
line 199:      expect(true).toBe(true);
line 237:    expect(true).toBe(true);

Three tests, three assertions, all tautologies. The file is structurally incapable of failing regardless of what the simulation produces — the balance signal exists only in console output a human has to read. And it is excluded from the test run by name, so even the printing does not happen unless someone invokes it directly.

This is not laziness. It is a scratch tool that was useful once and then sat in the tree looking like a test. The word test in a filename is not a guarantee.

Three more that look like gates

  • Two browser verification scripts — correctly written, with genuine non-zero exits — import their browser library from a hardcoded temporary directory path that appears in neither the dependency list nor the lockfile. They only ever worked on one machine, and a clean checkout cannot run them.
  • A performance script shells out to a tool nobody installs.
  • The image quality checker prints failures and exits successfully. Its only exit call guards a missing library, not a failed check.

And the gap inside the passing suite

667 tests pass. They are logic-only — no browser rendering — which the project's own testing document states honestly. But 17 of the 34 state-machine actions have no dedicated test anywhere, including the ones that heal you at the inn, buy a drink, revive you after death, and apply damage. A reader seeing "667 passing" would reasonably assume the inn's gold and health arithmetic is pinned down. It is not.

Two questions worth asking of any suite

Has this check ever been observed failing? A test never seen to fail is not known to work. One file here records that it was verified to discriminate — run against the broken source it reports failure, against the fixed source it passes. That sentence is worth more than the test count.

What does the number cover? "667 tests" is a count, not a scope. The honest form names the boundary: 667 unit tests covering the game logic, with no browser rendering and 17 state actions uncovered.

Module 06

What we would do differently

Still open Everything unresolved, stated plainly — including four corrections this class made to its own research while writing it.

Still open in the repository

  • Two governing documents have no supersession marker. The claims register and the canonical fact sheet are both stale against the shipped version. The retraction, where it exists at all, lives in a different file. The next reader opens the stale one.
  • The largest document has no banner saying it describes a deleted prototype.
  • There is no continuous integration. Five real gates exist and all of them run on whoever remembers to type the command.
  • Two verification scripts cannot run on a clean checkout — hardcoded path, undeclared dependency.
  • Seventeen state actions are untested, including four that move gold or health.
  • The handover note is 15× off on the AI spend ceiling, and contradicts a correct measurement in a sibling file.
  • The project calls itself open source with no licence file. The readme still carries a to-do to choose one. That claim is not currently true.

Four corrections this class made to itself

A class about verification that hides its own misses is worth less than one that shows them. All four were caught by measuring rather than reading.

What a researcher reportedWhat is trueThe method that produced the error
Two exploits are current open risks Both fixed — 32 minutes after the audit was written Trusted a checked-in document over the source, because the relevant files were outside its assigned scope
The balance harness has seven vacuous assertions Three — and they are the file's only assertions Counted test blocks from a skim instead of grepping. The real finding was starker than the reported one
The image checker has no exit call It has one, guarding a missing library — not the failure branch Grepped for the conclusion rather than for the token
The corpus is 41 documents / 413,470 bytes 46 documents / 441,416 bytes A prior measurement, carried forward without re-running — the exact failure this class is about

The last row is the one I would keep. That figure came from my own handoff note, written days earlier, and I nearly published it. The pattern does not spare the people who are looking for it.

Write one now

Pick a fact in your own project that you cannot afford to be wrong about, and turn it into something that fails. Fill this in and download the result.

Interactive · Forgelive

Turn a sentence into an assertion

The shape is the one this repository uses: name the incident, put the fact in data, then assert it where it can be checked.


Your answers stay in this browser. Nothing is sent anywhere. Reset clears them.

The five that would have caught all of this

Date every figure and name its method, or it cannot be audited, only believed. Mark a superseded document at the top of itself, not in a correction one directory away. Measure the server, not the bundler. Count the exit paths before calling something a gate. And put the claims you cannot afford to be wrong about into assertions — everything else can be prose, provided nobody treats it as current without checking.

Bottom line

Nothing in this repository was written carelessly. Forty-six documents, a real test suite, a credential scanner wired into every build, a regression suite that enforces a rename down to its art-cache keys — this is a project that took its own record seriously. And measured on a single afternoon, its self-declared source of truth was wrong on every number, its largest document described software that had been deleted, and one audit went stale in the time it takes to fix two bugs. The code did not betray the documentation. The code simply moved, and sentences do not move with it. The only documentation that kept up was the documentation that runs.

The AMBER ending
Amber — the palimpsest holds.
The MERCY ending
Mercy — the page, scraped clean.
The WRITER ending
Writer — the new hand.
Questions

Frequently asked

What is Ashaveth?

Ashaveth is a free browser role-playing game by Design Delight Studio. Fifty levels across five biomes, seventy-two monsters, four gate bosses and a final boss, three endings, and live AI-written dialogue for the three residents of the last town. Everything in it is original to the studio: the setting, the monsters, the bosses, the named characters and the forge-only sword called Godsbane. It is not based on or affiliated with any other franchise. Its lineage is the 1990s bulletin-board door game as a genre — a daily budget of fights, a real-calendar reset, a death penalty measured in net worth — rebuilt with a story that takes those conventions seriously.

How many documents does the project ship, and why does that matter?

Forty-six markdown documents totalling 441,416 bytes, measured on 1 September 2026 excluding dependencies and build output. That is a more complete written record than most commercial projects carry, which is exactly why it is worth auditing. The interesting result is not that a badly documented project has bad documentation. It is that a carefully documented project, written by people who cared, still ends up with nearly every authoritative document wrong within weeks.

What does it mean for a document to be stale in 32 minutes?

The repository contains a balance audit whose file modification time is 16:18 on 14 July 2026. It reports two open defects: an unlimited free heal from a character conversation, and a random roll that could be re-rolled by reloading the page. The two source files that fix those exact defects have modification times of 16:50 and 16:52 the same day. The fixes landed roughly half an hour after the document was saved, and the document was never updated to say so. Anyone reading it afterwards, including an AI agent doing research, would report two solved problems as current risks.

Why is the published bundle size wrong?

The landing page states that the game's 370 kB entry bundle compresses to 122 kB gzipped. Fetching that exact file from the live server with gzip encoding returns 140,856 bytes — 15.5 percent more. Nobody lied. The 122 kB figure is precisely what the bundler prints at build time. It is not what the server sends, because the nginx configuration enables gzip without setting a compression level, so nginx compresses at its weakest default while the bundler's estimate assumes a stronger one. A number was copied out of a build log into marketing copy, and the build log was measuring something the server does not do.

What is executable documentation?

A test whose only purpose is to assert a fact about the project rather than to check a feature. The clearest example in this repository is a regression suite covering a rename. It does not describe the rename in prose; it asserts that a set of banned strings never appears on any surface a player can read, that every renamed entity keeps a pinned art key, that no code path rebuilds that key from the display name, that old save files are migrated on load, and that the rename did not orphan a progression gate. A markdown file saying we renamed the boss would have been true the day it was written and silent forever after. The test fails the moment the fact stops being true.

Which checks in the project can actually fail a build?

Five of ten, measured. A credential scanner runs automatically after every build and exits with a failure code if it finds an API key inlined into the output. Four test files run as part of the suite and fail it: the rename regression suite, a guard that reads the AI provider's own source to confirm a configuration flag is pinned at every call site, a contrast test that asserts a previously used colour would now fail, and an advertising policy test that asserts a deleted component stays deleted. The other five cannot stop anything. Three browser verification scripts are wired into nothing and import a dependency from a hardcoded temporary path that exists on no other machine. An image quality checker prints failures and then exits successfully. A balance simulation has three assertions and all three are the literal expression expect true to be true, and it is excluded from the test run anyway.

Does the game actually use live AI, and can a player tell when it does not?

Yes, through a Cloudflare Worker that holds the API key server-side and enforces an origin allowlist, a two-model allowlist with image and speech quotas set to zero, per-session and per-address rate limits, and a hard global daily ceiling. For character conversation the player can tell when it falls back: each turn is tagged as live, cached or quota-exhausted, and the interface shows an explicit banner reading speaking from memory or today's improvised speech is spent. A comment in the fallback file states the principle directly: a fallback you cannot detect is a fallback that lies to you. For eleven other AI-written features, including monster descriptions and combat narration, there is no tag and no signal, so those still fall back silently.

How many tests does the project have, and what do they not cover?

667 tests across 32 files, all passing, run independently twice during this research. They are logic-only with no browser rendering, which the project's own testing document states plainly. Seventeen of the thirty-four state-machine actions have no dedicated test anywhere, including the ones that heal the player at the inn, buy a drink at the tavern, revive after death and apply damage. A reader seeing 667 passing tests would reasonably assume the inn and tavern gold and health arithmetic is pinned down. It is not. The correct way to describe this suite is 667 unit tests covering the game logic, never fully tested.

What is the practical lesson for someone building with AI agents?

Prose decays silently and assertions fail loudly, so put the facts that matter into assertions. Every document in this project was accurate when written; the code simply moved faster than anyone could maintain the sentences describing it. Writing more documentation does not fix that, and neither does a better model. What fixes it is converting the specific claims you cannot afford to be wrong about into checks that run: a banned string that must never ship, a constant that must be pinned at every call site, a deleted component that must stay deleted, a colour that must not regress. Everything else can be prose, provided you date it and never treat it as current without checking.

Is Ashaveth free, and is this class free?

Both are free. Ashaveth runs in a browser with no download, no account and no way to spend money inside it; the repository contains no payment or checkout code of any kind. This masterclass has no signup, no email capture, no paywall and no certificate. Every figure quoted on this page was measured from the repository or from the live server on 1 September 2026, including the figures that contradict the project's own documents, and including four corrections made to this class's own research while it was being written.

Keep going

The whole Academy is free

No signup, no email capture, no paywall, no certificate. Every class is measured from a real repository and cites its sources — including the mistakes, and including our own.

Browse all classes Play Ashaveth