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
45 changes: 45 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,51 @@ versions follow [Semantic Versioning](https://semver.org/).
Kotlin port against the TypeScript original recovered from git history.
Records six findings and an ordered remediation plan in
[`docs/post-migration-review.md`](docs/post-migration-review.md).
- **Abilities show their real name, not the raw PokéAPI slug.** Picking a
species used to fill the ability field with `sap-sipper` instead of
"Sap Sipper" — the ability picker one row below showed the correct
name, so the same ability read two different ways on the same screen.
Every ability and move name now comes from PokéAPI's own English name
data instead of a naive hyphen-to-space conversion, which also fixes
names that conversion gets wrong outright (Well-Baked Body, Double-Edge,
U-turn, Will-O-Wisp, ...).
- **The ability field is now a canonical picker with a custom fallback.**
Picking a species offers its real abilities (normal slots, then hidden)
by name; a "Custom ability…" option opens the full catalogue with free
text still accepted, so a ROM hack's non-canonical ability assignment
stays typeable. An option that actually changes the weakness/resistance
map is marked.
- **Ten previously unmodelled abilities now affect the coverage
calculation**: Heatproof, Water Bubble, Purifying Salt, Filter, Solid
Rock, Prism Armor, Primordial Sea, Desolate Land, Delta Stream, Tera
Shell. Dry Skin's missed Fire weakness (1.25×) is now applied alongside
its existing Water immunity. Wonder Guard is now a real effect instead
of a display-only badge — only a super-effective hit deals any damage,
matching Shedinja's actual mechanic. Scrappy and Mind's Eye now let
Normal/Fighting moves hit Ghost-types in the offensive coverage grid,
and Aerilate/Pixilate/Refrigerate/Galvanize/Normalize now rewrite a
Normal-type move's coverage the way they do in the real games.
- **Held items affecting type coverage can now be assigned.** A new item
field (free text, same "type it or pick it" contract as ability) models
Air Balloon, Iron Ball, Ring Target and one resist berry per type. Items
round-trip through Showdown export/import and local backups.
- **Suggestions on an already-strong team now lead with the strongest
alternative, not the lowest Pokédex id.** Once a team's type coverage is
complete every remaining candidate ties on the composite score, and the
ranking used to fall through straight to ascending catalogue id —
surfacing Raticate ahead of far stronger options for no reason connected
to team building. A tied ranking now breaks by base stat total first
(current-generation value, or the historical one for a chosen
generation filter), with catalogue id as the final tie-break only.
Suggestion cards show the candidate's base stat total and a plain-
language explanation of the score.
- **The number of suggestions shown is now configurable**, 5 to 10
(Settings → Team Suggestions), default 5 — previously hardcoded.
- **The dataset sync downloads four more small CSVs** (base stats,
historical base stats, English ability names, English move names),
~213 KB → ~578 KB total — still a handful of requests, still well under
a second on any real connection. See
[`docs/plan/reference-pokedata.md`](docs/plan/reference-pokedata.md) §2.

## [2.0.0] - 2026-09-04

Expand Down
68 changes: 47 additions & 21 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ before editing anything, then read the phase plan you are executing.
- [`docs/test-plan.md`](docs/test-plan.md) — manual, on-device verification.
One new section per phase; one "Known regressions" entry per real bug found.
- [`CHANGELOG.md`](CHANGELOG.md) — one entry per release, updated as you go.
- [`docs/plan/phase-7-accuracy-and-customization.md`](docs/plan/phase-7-accuracy-and-customization.md)
— the next phase's plan: what is wrong with the engines today (measured,
with the dataset evidence) and exactly what to build.

## What this project is

Expand Down Expand Up @@ -69,6 +72,8 @@ the app is native-only from here on.
- **Phase 4 — Suggestions and generator**: ✅ done
- **Phase 5 — Import/export and settings**: ✅ done
- **Phase 6 — Release**: ✅ done
- **Phase 7 — Engine accuracy, abilities/items, BST ranking**: ✅ done —
see [`docs/plan/phase-7-accuracy-and-customization.md`](docs/plan/phase-7-accuracy-and-customization.md)

Tick these off as phases land — here and in
[`docs/plan/README.md`](docs/plan/README.md). Do not implement anything not
Expand All @@ -88,8 +93,10 @@ explicitly asks for it.
tell users to export their teams to Showdown format first. See
`docs/implementation-decisions.md`.
- **The dataset sync reads PokéAPI's own CSV source data, not its JSON
mirror.** ~8 requests and ~208 KB instead of ~3875 requests and ~426 MB
— measured, not estimated. See `docs/plan/reference-pokedata.md`.
mirror.** ~12 requests and ~565 KB (8 requests/~208 KB through Phase 6;
Phase 7 added base stats and correct English ability/move names)
instead of ~3875 requests and ~426 MB — measured, not estimated. See
`docs/plan/reference-pokedata.md`.
- **Sprite URLs are derived, never stored**, from a Pokémon's id alone.
- **Species/type-override/ability/move values on a team slot are
denormalized snapshots**, not references into the cached catalogue.
Expand All @@ -107,7 +114,8 @@ explicitly asks for it.

## Architecture

The full six-phase shape, as it stands at the end of Phase 6: `ui/theme`,
The shape as it stands at the end of Phase 7 (the six-phase native rewrite
plus the engine-accuracy/customization phase that followed it): `ui/theme`,
`ui/navigation`, `ui/teams` (real CRUD, plus the dice icon reaching Surprise
Me), `ui/team` (team detail, the slot editor, `MoveSlotEditor`,
`SlotSummaryCard`), `ui/team/analysis` (`AnalysisScreen`'s seven sections,
Expand All @@ -116,23 +124,31 @@ Me), `ui/team` (team detail, the slot editor, `MoveSlotEditor`,
`SurpriseMeViewModel`, the team generator's own screen), `ui/roster` (real
CRUD, its own editor), `ui/importexport` (`ImportShowdownScreen`,
`ImportShowdownViewModel`, `ExportShowdownDialog`), `ui/settings` (theme,
language, dataset status, the Showdown import entry point, and local backup),
`ui/common` (`PokemonSprite`, `TypeBadge`, `SearchableDropdown`,
`EditableComboBox`, `TypeDropdown`, `DamageClassDropdown`, the
language, dataset status, the Showdown import entry point, local backup, and
the Phase 7 suggestion-count stepper), `ui/common` (`PokemonSprite`,
`TypeBadge`, `SearchableDropdown`, `EditableComboBox`, `TypeDropdown`,
`DamageClassDropdown`, `AbilityPicker`/`ItemPicker` (Phase 7's
canonical-plus-custom ability field and the item field), `StepperCounter`
(the shared `−`/`+` row Surprise Me and Settings both use), the
`PokemonType`/`DamageClass` `displayName()` extensions), `domain/coverage`
(the ported coverage engine), `domain/ability` (the ported `AbilityEffects`),
`domain/suggestion` (the ported suggestion engine + the shared `Scoring.kt`),
`domain/generator` (the ported team generator, injectable `Random`),
`domain/showdown` (`ShowdownFormat.kt`: export/import, contract-complete),
`domain/backup` (`BackupPayload.kt`: versioned DTOs + mapping),
`data/backup` (`BackupArchive.kt` zip read/write, `LocalBackupManager.kt`
SAF plumbing), `data/settings/SettingsPreferences.kt` (theme, language, the
persisted "Enable move slots" toggle, and every other app-wide setting —
include Mega/Dynamax, include legendaries, include customs in analysis),
`data/debug/DebugSeeder.kt` (seeds two teams and two roster entries, wired
from `CoverDexApplication`), and the full `data/pokeapi`, `data/local`,
`data/repository`, `domain/pokeapi`, `domain/sprite`, `domain/model`,
`domain/repository` and `di` packages the tree below describes.
(the ported coverage engine, extended in Phase 7 with the ability/item
effect pipeline), `domain/ability` (`AbilityEffects` — the ported table plus
Phase 7's ten added defensive abilities and the offensive gap), `domain/item`
(Phase 7's `ItemEffects` — the defensive-items-only subset),
`domain/suggestion` (the ported suggestion engine + the shared `Scoring.kt`,
now BST-tie-break-aware), `domain/generator` (the ported team generator,
injectable `Random`), `domain/showdown` (`ShowdownFormat.kt`: export/import,
contract-complete, items round-trip as of Phase 7),
`domain/backup` (`BackupPayload.kt`: versioned DTOs + mapping, format v2 as
of Phase 7), `data/backup` (`BackupArchive.kt` zip read/write,
`LocalBackupManager.kt` SAF plumbing), `data/settings/SettingsPreferences.kt`
(theme, language, the persisted "Enable move slots" toggle, and every other
app-wide setting — include Mega/Dynamax, include legendaries, include
customs in analysis, the Phase 7 suggestion count), `data/debug/DebugSeeder.kt`
(seeds two teams and two roster entries, wired from `CoverDexApplication`),
and the full `data/pokeapi`, `data/local`, `data/repository`, `domain/pokeapi`,
`domain/sprite`, `domain/model`, `domain/repository` and `di` packages the
tree below describes.

```
com.marcogn.coverdex
Expand All @@ -148,7 +164,8 @@ com.marcogn.coverdex
│ ├── pokeapi/ CsvParser, per-file parsers, dataset assembly, SyncStage
│ ├── sprite/ SpriteUrlResolver (pure, unit-tested)
│ ├── coverage/ the ported coverage engine
│ ├── ability/ AbilityEffects (ported verbatim)
│ ├── ability/ AbilityEffects (ported verbatim + Phase 7 additions)
│ ├── item/ ItemEffects (Phase 7, defensive items only)
│ ├── suggestion/ the ported suggestion engine + shared Scoring
│ ├── generator/ the ported team generator ("Surprise Me")
│ ├── showdown/ export/import, contract-complete
Expand Down Expand Up @@ -214,7 +231,7 @@ there.
- **Do not set `Accept-Encoding` on `HttpURLConnection`.** Left alone it
negotiates gzip and decompresses transparently; set it by hand and you
get raw gzip bytes. Matters more here than in the sibling app: CSV
compresses very well, so the measured ~208 KB in
compresses very well, so the measured ~565 KB in
`docs/plan/reference-pokedata.md` is what crosses the wire uncompressed.
- **kotlinx.serialization defaults do not cover an explicit `null`.** A
default value only fills a *missing* key; `"field": null` still throws
Expand Down Expand Up @@ -261,6 +278,15 @@ there.
`sourceSets["debug"].assets` instead, which is what actually makes
`Migration1To2Test` pass. See `docs/implementation-decisions.md`,
"Phase 2", for how this was verified rather than assumed.
- **`ALTER TABLE ... ADD COLUMN` needs a `DEFAULT` when the column is
`NOT NULL`.** SQLite (and therefore Room's own migration SQL) rejects a
`NOT NULL` column added this way with no default — every pre-existing
row would have nothing to put there. `MIGRATION_2_3`'s
`poke_species.baseStatTotal` column needs `DEFAULT 0` in the raw SQL
*and* a matching `@ColumnInfo(defaultValue = "0")` on the Kotlin field,
or `MigrationTestHelper`'s schema validation flags the mismatch. A
nullable added column (`team_member.item`, `custom_pokemon.item`) needs
neither — `NULL` is already a valid default for every existing row.

## Build/test commands

Expand Down
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ sync finishes, and never asks for your data.
every calculation adapts to match, without touching the real data.
- **Speaks Showdown.** Import a team you already built on Pokémon
Showdown, or export yours in the same format to share or battle with.
- **Actually offline.** Pokémon data downloads once — about 8 requests
and ~208 KB — then CoverDex never needs the internet again.
- **Actually offline.** Pokémon data downloads once — about 12 requests
and ~565 KB — then CoverDex never needs the internet again.

## Get CoverDex

Expand Down Expand Up @@ -66,9 +66,13 @@ on-device and every later launch is instant, fully offline included.
instant results as you type.
- **Per-slot type overrides** for ROM hack typings, kept separate from
the underlying species data.
- **Ability field** with known coverage effects (immunities,
multipliers) reflected directly in the analysis, plus free-text entry
for anything a randomizer throws at you.
- **Ability field** offering a species' real abilities by name, with
known coverage effects (immunities, multipliers) reflected directly in
the analysis, plus a "Custom ability…" free-text fallback for anything
a randomizer throws at you.
- **Held items** that affect type coverage (Air Balloon, Iron Ball, Ring
Target, type-resist berries), round-tripping through Showdown and
local backups.
- **Four move slots per Pokémon**, from the synced catalogue or entered
as custom moves, with move-aware offensive coverage when you use them.
- **A personal custom-Pokémon roster** for anything that doesn't exist
Expand Down
17 changes: 15 additions & 2 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ snapshot of what's implemented versus still open, see
[`docs/STATUS.md`](docs/STATUS.md).

CoverDex finished its native Android rewrite at the end of Phase 6 of
[`docs/plan/README.md`](docs/plan/README.md); `docs/plan/` stays in the
[`docs/plan/README.md`](docs/plan/README.md), and its engine-accuracy and
customization follow-up at the end of Phase 7. `docs/plan/` stays in the
repository as the record of how it was built and why. The items below are
this app's actual, current out-of-scope list — see
[`docs/plan/native-spec.md`](docs/plan/native-spec.md), "Explicitly out of
Expand Down Expand Up @@ -35,8 +36,20 @@ scope", for the full reasoning behind each one.
- **Migrating data from the old Capacitor build.** Decided against,
explicitly, in Phase 0 — see `docs/implementation-decisions.md`.

- **Generational type charts and generational typings.** The pinned
dataset has `type_efficacy_past.csv` (Gen-1 Ghost/Psychic and Bug/Poison
interactions, the pre-Gen-6 Steel/Dark resistance to Ghost, ...) and
`pokemon_types_past.csv` (pre-Fairy-retcon typings — Clefairy et al.
were Normal-type through Gen 5) already downloaded as part of Phase 7's
base-stat sync, and neither is read. The app has no "which
game/generation's rules am I analysing against" concept anywhere else
(the existing suggestion generation filter only restricts *which
species* are eligible, it doesn't change the type chart), and adding
one is a genuine feature, not a bug fix — see
`docs/plan/phase-7-accuracy-and-customization.md` §0.6/§7.4.

## Ideas not yet committed to

Nothing currently — the native rewrite (`docs/plan/README.md`, Phases
0–6) covered everything in `docs/plan/native-spec.md`. A genuinely new
0–7) covered everything in `docs/plan/native-spec.md`. A genuinely new
idea belongs in a GitHub Issue first, not here.
Loading