Skip to content

feat(domain-clash-royale): first non-transport domain — 121 cards#67

Merged
Calixteair merged 3 commits into
mainfrom
feat/domain-clash-royale
May 14, 2026
Merged

feat(domain-clash-royale): first non-transport domain — 121 cards#67
Calixteair merged 3 commits into
mainfrom
feat/domain-clash-royale

Conversation

@Calixteair
Copy link
Copy Markdown
Owner

What

PR A of the Clash Royale arc. Adds the clash-royale domain pack alongside paris-metro + rer. First non-transport domain in kalidoku — validates that the engine is truly domain-agnostic.

  • 121 cards (every card in the official Clash Royale list)
  • 33 predicates spanning rarity / type / elixir / combat stats / names
  • 100/100 seed test passes at the worker's max_attempts=1000
  • Worker auto-discovery + server auto-upsert handle the rest

No card images yet — PR B will surface them inside CellButton + AutocompleteModal. This PR is text-only.

Why

phase 3 of the roadmap originally listed rer / transilien / sncf-grandes-lignes / world-airports. We're skipping the rest of the transport packs and jumping straight to a "fun" domain (phase 5) to prove the moteur isn't quietly coupled to lines/stations/geography. Clash Royale is also a recognisable brand that gives kalidoku something more sharable than yet-another-metro grid.

How

Two layers, three commits.

3157b2f feat(scripts): ingest tooling for clash-royale

scripts/ingest/build_clash_royale_dataset.py — stdlib-only Python. Joins two sources by normalised name:

  • Supercell /v1/cards — canonical list + rarity + elixirCost + iconUrls. JWT read from ~/.config/kalidoku/clash_royale_api_key (mode 600, outside the repo).
  • RoyaleAPI cards_stats.json — combat stats (damage, hit_speed, range, speed, hitpoints, attacks_air, attacks_ground, flying_height, unlock_arena) via the troop / building / spell / characters tables. Troops follow summon_character into the characters table to get the actual unit stats.

fame_score is rarity-driven (common=100 .. champion=5). No Wikipedia pageviews — they're not meaningful per-card for game data.

ceda44e feat(domain-clash-royale): seed pack

The artefacts the script produces, committed as-is:

  • entities.json — 121 cards, sorted by id, with rarity / type / elixir / dps / hitpoints / range / speed / attacks_air / attacks_ground / flies / arena.
  • predicates.json — 33 predicates curated to keep coverage > 8 entities on each (CSP-solver-friendly).
  • metadata.json — version 0.1.0, both ingestion sources tracked.

9979964 feat(server,web): register clash-royale + i18n + domain list

  • server/src/main.rs declares clash-royale; `bootstrap::upsert_active_domains` auto-inserts the row.
  • web/src/lib/domains.ts adds the entry; `getStaticPaths` bakes `/clash-royale/` and its sub-pages.
  • grid_domain_clash_royale i18n key in fr + en, wired into Grid's domainLabel switch.

Known data gaps

50/121 cards have partial stats (dps=0, type=Unknown). Tracked in the wiki under [[kalidoku-domain-clash-royale]]. Causes:

  1. RoyaleAPI uses internal names (Bandit → `Assassin`, Executioner → `AxeMan`) — a renaming pass could rescue ~2 cards.
  2. Public spells (Fireball, Rocket, Arrows, Mirror, The Log, etc.) are absent — RoyaleAPI ships effect entries (`Morph_SkeletonsDamage_AOE`) rather than the public card name.
  3. Recent cards (Goblin Demolisher, Spirit Empress, Suspicious Bush, …) likely haven't propagated to RoyaleAPI yet.

These cards still match the rarity / elixir / name_* predicates. Enrichment path: `domains/clash-royale/manual_stats.json` à la `fame_overrides.json`, populated from the Fandom wiki. Out of scope for this PR.

Checklist

  • Conventional commit titles, no AI / Claude mention
  • `cargo fmt --check && clippy --all-targets -- -D warnings && test --workspace` passes
  • `pnpm --dir web lint && typecheck && test && build` passes (21 pages now, was 17)
  • 100-seed generator run on clash-royale: 0/100 failures @ max_attempts=1000
  • No new `unsafe`, no `unwrap()` in non-test code
  • No new secret committed (API key stays in ~/.config)
  • No contract change

Test plan

  • After merge + deploy, the worker's next nightly run publishes a clash-royale daily grid
  • `GET /api/grids/clash-royale/today` returns a PublicGrid
  • `POST /api/games {domain:"clash-royale", mode:"solo"}` returns a fresh seed + grid
  • `POST /api/duels {domain:"clash-royale"}` works
  • Hub at `/` shows a third "Clash Royale" card
  • `/clash-royale/play` lets you solve a 3×3 of cards (e.g. "Epic", "Cost ≥ 6", "Targets air" intersections)

scripts/ingest/build_clash_royale_dataset.py — stdlib-only Python.

Pulls from two sources:
- Supercell official /v1/cards for the canonical card list + iconUrls +
  rarity + elixirCost. Key read from
  ~/.config/kalidoku/clash_royale_api_key (mode 600, outside the repo).
- RoyaleAPI cards_stats.json for the combat stats Supercell doesn't
  expose (damage, hit_speed, range, speed, hitpoints, attacks_air,
  attacks_ground, flying_height, unlock_arena).

Match by normalised name (lowercase + ASCII + strip non-alphanumerics).
Troops/buildings expose a summon_character pointer into the characters
table where the real combat stats live.

50/121 cards land with partial stats — RoyaleAPI uses internal names
that differ from Supercell's public names (Bandit -> Assassin,
Executioner -> AxeMan) and doesn't publish entries for public spells
(Fireball, Rocket, Arrows, Mirror, The Log, etc.). They still ingest
with dps=0, hitpoints=0, type=Unknown; only the rarity/elixir/name_*
predicates operate on them. Acceptable for v1 — list tracked in the
wiki under [[kalidoku-domain-clash-royale]].

fame_score is rarity-driven (common=100, rare=80, epic=50,
legendary=20, champion=5). No Wikipedia pageviews — they're not
meaningful per-card for game data.

NOTE: icon_url from Supercell is intentionally not stored on the
entity in this PR — entity-schema.json forbids extra top-level keys.
PR B (card images) will either extend the schema or stash the URLs in
a sibling icons.json.
Bootstrap of the clash-royale domain pack via the new ingest script.
First non-transport domain in kalidoku, validates that the engine is
truly domain-agnostic.

domains/clash-royale/entities.json (121 cards)
- Pulled from Supercell /v1/cards + RoyaleAPI cr-api-data join.
- Attributes: rarity (str), type (str: Troop/Building/Spell/Unknown),
  elixir, dps (computed: damage * 1000 / hit_speed), hitpoints, range,
  speed, attacks_air, attacks_ground, flies, arena (parsed from
  TrainingCamp/ArenaN).
- fame_score directly maps rarity (common=100 .. champion=5).
- 50/121 entries have partial stats (dps=0, type=Unknown) — known
  upstream-data limitation, see [[kalidoku-domain-clash-royale]].

domains/clash-royale/predicates.json (33 predicates)
Spans every usable axis given the partial data:
- rarity: 5 (one predicate per tier)
- type: 3 (Troop / Building / Spell)
- elixir: 4 (<=2, =3, =4, >=6 — bucketed to keep coverage > 8)
- target/movement: attacks_air, flies
- combat: dps >= 100 / 200, hitpoints >= 1000 / 2000, range >= 5000 /
  <= 1200 (melee), speed >= 90 (fast) / <= 60 (slow)
- progression: arena >= 7
- letters/words: starts_with G/S, ends_with R/N, contains Y/K,
  word_count = 1/2, name_length <= 6 / >= 12

Predicates with coverage < 8 on the dataset were intentionally
dropped to keep the CSP solver healthy (100/100 seed test at
max_attempts=1000).
server:
- main.rs declares clash-royale in active_domains. Same shape as
  paris-metro/rer entries. The Dockerfile already COPYs domains/ into
  the image, worker auto-discovers — once this lands the next nightly
  cron publishes a clash-royale daily grid automatically.

web:
- src/lib/domains.ts: new entry in DOMAINS so the /domain picker and
  the /[domain]/* getStaticPaths route a new set of pages
  (/clash-royale/, /clash-royale/play, /clash-royale/leaderboard,
  /clash-royale/archives).
- grid_domain_clash_royale i18n key (fr + en), wired into Grid's
  domainLabel switch.
@Calixteair Calixteair merged commit f41bd28 into main May 14, 2026
7 checks passed
@Calixteair Calixteair deleted the feat/domain-clash-royale branch May 14, 2026 00:35
@Calixteair Calixteair mentioned this pull request May 14, 2026
12 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant