feat(loldle): add ability and splash champion-guessing modules

Closes deferred phases 04 + 05 of loldle-new-modes plan.

- loldle-ability: 5 guesses, DDragon ability icon as photo. State pins
  slot (P/Q/W/E/R) so the same icon shows every turn. Abilities pulled
  from DDragon per-champion — same source loldle.net uses at runtime.
- loldle-splash: 4 guesses, random skin splash as photo. Skin pool
  scraped from loldle.net bundle (var Ad=[…] — 172 champs × 1939 skins,
  non-chroma, matches their splash mode exactly). URLs from Riot
  DDragon CDN (no version segment, stable across patches).
- fetch-ddragon-data.js: extended to write all four JSONs in one run.
  Shares a single DDragon per-champion fetch cycle (concurrency 10).
- Credits loldle.net + Riot Games in all loldle-family READMEs.

19 new tests (503 total). Lint clean. register:dry reports 12 loldle_*
commands with no conflicts.
This commit is contained in:
2026-04-24 23:58:42 +07:00
parent bd5626534b
commit 3ac06bffaa
24 changed files with 16998 additions and 5 deletions
@@ -0,0 +1,38 @@
import { beforeEach, describe, expect, it } from "vitest";
import { createStore } from "../../../src/db/create-store.js";
import {
clearGame,
loadGame,
loadStats,
recordResult,
saveGame,
} from "../../../src/modules/loldle-ability/state.js";
import { makeFakeKv } from "../../fakes/fake-kv-namespace.js";
describe("loldle-ability state", () => {
let db;
beforeEach(() => {
db = createStore("loldle-ability", { KV: makeFakeKv() });
});
it("round-trips a game with slot field", async () => {
const state = { target: "Ahri", slot: "Q", guesses: ["Akali"], startedAt: 1234 };
await saveGame(db, 42, state);
expect(await loadGame(db, 42)).toEqual(state);
});
it("clearGame removes the record", async () => {
await saveGame(db, 42, { target: "Ahri", slot: "P", guesses: [], startedAt: null });
await clearGame(db, 42);
expect(await loadGame(db, 42)).toBeNull();
});
it("recordResult tracks streaks + bestStreak", async () => {
await recordResult(db, 42, true);
await recordResult(db, 42, true);
await recordResult(db, 42, false);
const s = await loadStats(db, 42);
expect(s).toMatchObject({ played: 3, wins: 2, streak: 0, bestStreak: 2 });
});
});