Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ npm run build # production build into dist/
npm run preview # serve the built output
npm run refresh-dex # re-bake the full dex from PokéAPI (~5-10 min)
npm run refresh-flags # re-bake move flags (contact, punch, sound...)
npm run refresh-species # re-bake the Pokédex details: bios, sizes, evolutions,
# ability text (~5-10 min; LIMIT=20 for a quick check)
```

Before you say you're finished: `npm run test:run && npm run build`. Both must
Expand All @@ -61,13 +63,17 @@ src/
natures.js ← all 25 natures.
storage.js ← persistence. Async by design (see below).
feedback.js ← in-app report → pre-filled GitHub issue link. Tested.
dex.js ← the Pokédex tab's arithmetic: defensive matchups off the
type chart, unit conversions, evolution steps. Tested.
data/
index.js ← picks generated dex if present, else sample
sample-pokedex.js ← 52 hand-checked entries, committed
sample-moves.js ← 44 damaging moves, committed
pokedex.json ← generated by refresh-dex. COMMIT IT.
moves.json ← generated by refresh-dex. COMMIT IT.
move-flags.json ← generated by refresh-flags. COMMIT IT. See below.
species.json ← generated by refresh-species. COMMIT IT. See below.
abilities.json ← generated by refresh-species. COMMIT IT.
changelog.js ← what the What's new tab shows. See below.
features/
DamageCalc.jsx ← the centrepiece
Expand All @@ -77,6 +83,7 @@ src/
BattleRunner.jsx ← the shared turn-by-turn battle screen
BattleSim.jsx ← the Battle tab: team picker + BattleRunner
StatDex.jsx ← sortable stat leaderboard
Dex.jsx ← the Pokédex tab: one Pokémon, everything about it
News.jsx ← the What's new tab, rendered from data/changelog.js
Report.jsx ← the Report tab: compose in-app, file on GitHub
components.jsx ← TypeChip, Field, StatBar
Expand All @@ -85,6 +92,9 @@ src/
scripts/
build-dex.mjs ← PokéAPI → static JSON, run manually
build-move-flags.mjs ← move flags → static JSON, run manually
build-species.mjs ← Pokédex details → static JSON, run manually
version-groups.mjs ← which game a learnset comes from. Read it before
touching either bake. See below.
```

## Hard constraints
Expand All @@ -99,6 +109,26 @@ manually, commit the JSON, never fetch at runtime. `move-flags.test.js` guards
against a bake that quietly produces an empty file — that failure mode would
switch Static off without anything else complaining.

**A version-group id is not a date.** `scripts/version-groups.mjs` decides which
game a learnset comes from, and both bakes share it so a move's level and the
learnset it sits in always agree. It exists because ranking PokéAPI's version
groups by id — the obvious thing — is wrong twice over: the Japan-only Gen 1
re-releases were added to the API years late and carry ids *above*
Scarlet/Violet, and the Legends games and battle-only spin-offs rebuilt moves
from scratch, so a Zubat in Legends: Arceus knows seven moves and there are no
TMs at all. Left alone, that stranded 187 Pokémon on their Red/Blue movesets.
A new main-series game needs nothing doing; a new spin-off needs a line in that
file, or half the dex quietly forgets how to battle.

**The Pokédex bio is a quotation, not a description.** `species.json` stores the
flavour text verbatim from the games *and* which game it came from, and the tab
prints the attribution under it. That is the only reason it is safe to show:
nothing on that page is something we wrote about a Pokémon. If you add a field
there, it holds either a number out of the dex or the game's own words. Don't
summarise, don't paraphrase, don't fill a gap with something plausible.
`species.test.js` guards the bake, including the "every bio names its game"
rule.

**Never call PokéAPI at runtime.** It is free but explicitly asks consumers to
cache locally, and 1300 sequential fetches is unusable on a phone. Run
`npm run refresh-dex` once, commit the JSON, ship it static. This also makes the
Expand Down
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Joseph's Battle Lab

A Pokémon damage calculator, stat explorer, and family league tracker.
A Pokémon damage calculator, battle sim, Pokédex, stat explorer, and family
league tracker.

**Live: https://jbl.opsvibe.systems**

Expand All @@ -18,7 +19,8 @@ npm run dev
Runs on the bundled 52-Pokémon sample set. For the full dex:

```bash
npm run refresh-dex # ~5-10 min, writes src/data/*.json
npm run refresh-dex # ~5-10 min, writes src/data/pokedex.json + moves.json
npm run refresh-species # ~5-10 min, the Pokédex tab's bios, sizes, evolutions
```

Commit the generated JSON. The app never calls PokéAPI at runtime.
Expand All @@ -33,6 +35,7 @@ Commit the generated JSON. The app never calls PokéAPI at runtime.
| `npm run build` | Production build to `dist/` |
| `npm run preview` | Serve the built output locally |
| `npm run refresh-dex` | Re-bake the full dex from PokéAPI |
| `npm run refresh-species` | Re-bake the Pokédex details (bios, sizes, evolutions, abilities) |

## Deploying

Expand Down Expand Up @@ -60,10 +63,10 @@ workflow fails the build if it goes missing.
## Project layout

```
src/lib/ battle math (tested), type chart, natures, storage
src/lib/ battle math (tested), sim engine, type chart, natures, storage
src/data/ dex data — sample set committed, full set generated
src/features/ DamageCalc, League, StatDex
scripts/ build-dex.mjs — PokéAPI → static JSON
src/features/ DamageCalc, BattleSim, League, StatDex, Dex, News, Report
scripts/ build-*.mjs — PokéAPI → static JSON, run by hand
```

`src/lib/battle.js` is the reference implementation for all game maths and is
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"test": "vitest",
"test:run": "vitest run",
"refresh-dex": "node scripts/build-dex.mjs",
"refresh-flags": "node scripts/build-move-flags.mjs"
"refresh-flags": "node scripts/build-move-flags.mjs",
"refresh-species": "node scripts/build-species.mjs"
},
"dependencies": {
"react": "^18.3.1",
Expand Down
14 changes: 5 additions & 9 deletions scripts/build-dex.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
*/

import { writeFile, mkdir } from "node:fs/promises";
import { rankVersionGroups } from "./version-groups.mjs";

const API = "https://pokeapi.co/api/v2";
const CONCURRENCY = 8; // be polite
Expand Down Expand Up @@ -87,17 +88,12 @@ function titleCase(slug) {
}

/**
* Version groups in release order, slug → rank. PokéAPI ids are already
* chronological, so the id doubles as the rank.
* Version groups in release order, slug → rank. Which games count, and why an
* id is not a date, is all in version-groups.mjs.
*/
async function buildVersionGroupRanks() {
const index = await getJSON(`${API}/version-group?limit=100`);
const ranks = {};
for (const vg of index.results) {
const id = Number(vg.url.replace(/\/$/, "").split("/").pop());
ranks[vg.name] = id;
}
return ranks;
const index = await getJSON(`${API}/version-group?limit=200`);
return rankVersionGroups(index.results);
}

async function buildMoves() {
Expand Down
Loading
Loading