Skip to content

fix: duel domain plumbing + manual fame_score overrides#66

Merged
Calixteair merged 2 commits into
mainfrom
fix/duel-domain-and-fame-overrides
May 13, 2026
Merged

fix: duel domain plumbing + manual fame_score overrides#66
Calixteair merged 2 commits into
mainfrom
fix/duel-domain-and-fame-overrides

Conversation

@Calixteair
Copy link
Copy Markdown
Owner

What

Two related fixes after the phase 3 multi-domain ship.

  1. Duel page now respects the duel's actual domain instead of hardcoding the page default. A RER duel link opened from a paris-metro page used to mount Grid in paris-metro mode and clobber the wrong localStorage key. Also adds a domain picker on /duel landing so the user can intentionally create RER duels.
  2. Manual fame_score overrides for the outliers the auto-ingest got wrong (Saint-Sulpice eclipsed by the église, Auber / CDG / Antony dropped by the topic filter, etc.). Curator decisions survive future re-ingests thanks to a new domains/<id>/fame_overrides.json convention.

Why

These are the two real-world bugs that surfaced once the phase 3 multi-domain UI hit prod. The duel one was structural (any future RER duel was broken); the fame one was cosmetic but visible — Saint-Sulpice at fame=1 on a station obviously well-known.

How

ceafeae fix(web): duel uses duel.domain, not the page default

`DuelView.svelte` now tracks two state vars:

  • `viewDomain` ← `GET /api/duels/{id}.domain`. Drives the playing screen.
  • `createDomain` ← page prop initially, then mutated by a new fieldset picker on the landing screen.

The fieldset (`kd-domain-picker`) renders one pill per DOMAINS entry above the Create CTA. Hidden when only one domain is configured. Active pill = primary background.

`` is now mounted with `domain={viewDomain}` — never `{domain}` (the page prop). i18n: `duel_create_pick_domain` key fr + en.

ESLint's `svelte/valid-compile` flags both prop captures as initial-only references; that's intentional and disabled inline with a comment.

`7bf409f feat(domains): manual fame_score overrides`

New file `domains//fame_overrides.json` shape:

```json
{
"_doc": "...",
"_rationale": { "": "explanation" },
"overrides": { "": 80 }
}
```

Loaded by both ingest scripts (`fame_score_paris_metro.py`, `fame_score_rer.py`) after the percentile-rank pass:

  • Validates each value is `int in [0,100]`
  • Merges into `fame_by_id` before patching entities
  • Annotates the audit report with an `"override": true` flag

8 entries shipped:

  • paris-metro: `saint-sulpice` → 80 (was 1, eclipsed by the church article)
  • rer: `auber` 92, `aeroport-charles-de-gaulle-1` 95, `antony` 85, `cergy-prefecture` 75, `issy` 70, `issy-val-de-seine` 60, `saint-michel-notre-dame` 95

The `entities.json` files are patched in this commit too so the change is visible in prod without waiting for the next manual ingest run.

Checklist

  • Conventional commit titles, no AI / Claude mention
  • `cargo fmt --check && clippy --all-targets -- -D warnings && test --workspace` passes (109 tests)
  • `pnpm --dir web lint && typecheck && test && build` passes (25 tests, 17 pages)
  • No new `unsafe`, no `unwrap()` in non-test code
  • No new secret committed
  • No contract change (`duel.domain` was already declared in OpenAPI)
  • Mobile-first respected (picker pills wrap to a 2nd row on 360 px)

Test plan

  • Create a duel on `/duel` → pick "RER d'Île-de-France" pill → CTA → share link points at /duel?id=…&sig=… → opening the link plays a RER grid
  • Open the link in another browser → 'Play this grid' starts a RER game, localStorage key is `kalidoku.game.rer.v1`
  • Open the same paris-metro daily on /paris-metro/ → solve Saint-Sulpice → fame chip says 80 (was 1)
  • Open the RER daily on /rer/ → if it features Auber / Antony / CDG → their fame chips reflect the override values

DuelView used to mount Grid with the page's prop domain (hardcoded
paris-metro on /duel) regardless of which domain the duel actually
belonged to. A RER duel link opened from /paris-metro therefore ran in
paris-metro mode and clobbered the wrong localStorage key.

Two state vars now back the screen:
- viewDomain: read from GET /api/duels/{id}.domain, drives the playing
  screen
- createDomain: drives the landing screen's POST /api/duels body,
  starts at the page prop but the new fieldset picker lets the player
  change it

The fieldset (kd-domain-picker) sits above the 'Create' CTA on the
landing screen and renders one pill per DOMAINS entry. Hidden when
only one domain is configured.

i18n: duel_create_pick_domain key, fr + en.

The Svelte rule svelte/valid-compile flags the prop captures as
initial-only references, which is the intent — the page is a single
mount and the picker mutates the local state from there. Disabled
inline with a comment explaining.
Saint-Sulpice (paris-metro) and 7 RER stations (Auber, CDG, Antony,
Cergy, Issy + Val-de-Seine, Saint-Michel Notre-Dame) were either
eclipsed by a more popular Wikipedia article (église Saint-Sulpice,
commune d'Antony) or dropped by the topic-keyword filter (Auber,
Issy, CDG terminus). Their fame_score read as 1 or null even though
they are unambiguously major stations.

Add a domains/<id>/fame_overrides.json convention: a JSON file with
{overrides: {entity_id: int 0..=100}} that the ingest scripts merge in
after the percentile-rank pass. Curator decisions survive a re-ingest
because the override is applied last.

Rationale lines stay in the JSON _rationale block so a future
re-ingest can audit why each override exists. The entities.json values
are patched in this commit too so the change is visible without
re-running the ingest scripts.
@Calixteair Calixteair merged commit 21d4bb1 into main May 13, 2026
7 checks passed
@Calixteair Calixteair deleted the fix/duel-domain-and-fame-overrides branch May 13, 2026 23:51
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