204 lines
15 KiB
Markdown
204 lines
15 KiB
Markdown
# PLAN – Ninelands für Reddit
|
||
|
||
Basic-Version von Ninelands als tägliches Reddit-Spiel (Devvit Web) für den Jam **Reddit Game of the Day 2026**.
|
||
Abgabe: **9. Dezember 2026, 03:00 MEZ**. Eingereicht wird in „Best Daily Game“ und als Portierung in
|
||
„Best Ported Daily Game“.
|
||
|
||
## Entscheidungen des Besitzers (2026-11-13)
|
||
|
||
- **Nur 9×9**, **nur Englisch**.
|
||
- **Das Spiel steht im Vordergrund:** kein Menü, kein Footer, kein Feedback-Knopf. Ausnahmen sind ein schlanker
|
||
Startbildschirm (Name, Datum, eigene Statistik/Streak) und eine Ergebniskarte am Ende. Beides ist Teil des Spiels
|
||
und wird von der Jury ausdrücklich verlangt.
|
||
- **Kern: Karte des Tages mit Bestenliste.** PvP höchstens später als KI-Duell-Bonus (wenig Spieler gleichzeitig
|
||
online, viel Aufwand, kostet Polish).
|
||
- **Freischaltbares:** Themen und Banner aus dem Hauptspiel, gekoppelt an Streak und Sterne statt an Herrscher-Stufen.
|
||
- **Lizenz GPL-3.0** (Besitzer ist alleiniger Urheber der Engine). Das Gerüst der Devvit-Vorlage bleibt BSD-3-Clause
|
||
(`LICENSE-TEMPLATE.md`).
|
||
- **Musik ja**, mit einer kleinen Credits-Stelle (CC BY-SA 4.0 verlangt die Nennung). Alle Sprites stammen aus
|
||
„Mini World Sprites“ (CC0). Nachweise in `CREDITS.md`.
|
||
|
||
## Phase 0 – Grundlagen
|
||
|
||
1. Sync-Skript `scripts/sync-engine.sh`: holt `src/core` von einem festen Ninelands-Tag (Vorschlag: `v0.11.0`) nach
|
||
`src/engine/` und notiert den Tag in `src/engine/VERSION`. Die Kopie wird nie von Hand geändert. Die Campaign- und
|
||
Puzzle-JSONs nur mitnehmen, wenn der Import sonst bricht. Besser: prüfen, ob esbuild sie per Tree-Shaking weglässt.
|
||
2. Prüfen, ob die Engine mit TypeScript 7, Biome und esbuild der Vorlage baut. `src/engine/` von Biome ausnehmen,
|
||
denn fremde Formatierung soll nicht zu Änderungen an der Kopie zwingen.
|
||
3. Assets übernehmen: die Tile-Sprites, die das 9×9-Spiel braucht, sowie die Themen und Banner für Phase 2. Bei der
|
||
Musik auf die App-Größe achten: das Hauptspiel hat 21 MB (6 Titel, Opus + MP3). Vermutlich nur 1–2 Titel und nur
|
||
ein Format. Das Größenlimit von Devvit vorher in der Doku prüfen.
|
||
|
||
## Phase 1 – Kern (Tagesrätsel)
|
||
|
||
1. **Tagespost:** Ein Scheduler-Job legt jeden Tag um 00:00 UTC einen neuen Post an („Ninelands #N – <Datum>“).
|
||
Jeder Tag bekommt so eigene Kommentare, in denen über die Karte diskutiert wird.
|
||
2. **Tageskarte:** Seed aus dem Datum, 9×9, mit den drei Sternzielen wie im Hauptspiel (★ Gold, ★ Länder, ★ beides),
|
||
berechnet aus Bot-Partien auf derselben Karte.
|
||
3. **Spiel:** Brett im Post, Ziele-Zeile live, Rückgängig wie im Hauptspiel.
|
||
4. **Abgabe und Prüfung:** Der Client schickt die Züge, der Server spielt sie mit derselben Engine nach und speichert
|
||
nur selbst berechnetes Gold (wie im Hauptspiel). So kann niemand die Bestenliste mit erfundenen Werten fälschen.
|
||
5. **Bestenliste** je Tag in Redis (Sorted Set): Sortierung wie bei den Ninelands-Ranglisten, also **Sterne ↓,
|
||
Gold ↓, früher abgegeben zuerst**. Gewertet wird nur der erste Versuch; danach darf man weiterspielen, das zählt
|
||
aber nicht. _Entscheidung Besitzer._
|
||
6. **Streak:** aufeinanderfolgende Tage mit gespielter Tageskarte.
|
||
7. **Startbildschirm:** Name, Nummer und Datum, eigene Streak, „Play“.
|
||
8. **Ergebniskarte:** Sterne, Gold, Länder, Platz, Streak, Teilen-Zeile (`Ninelands #N ★★☆ · 9/9 lands · 85 gold`)
|
||
und der Anstoß, sie in die Kommentare zu posten.
|
||
9. **Devvit Journeys:** Ereignisse für Start, Abgabe und Streak (Jury-Kriterium).
|
||
10. **Credits:** ein kleines „ⓘ“ auf dem Startbildschirm mit Musik- und Sprite-Nachweis.
|
||
11. **Erstes Spielen:** Hinweise im Zusammenhang statt Textwand (Jury-Kriterium „Intuitive Onboarding“).
|
||
|
||
## Phase 2 – Wiederkommen
|
||
|
||
1. Themen freischalten über die Streak (z. B. 3 / 7 / 14 Tage), Banner über gesammelte Sterne. Die Auswahl erfolgt
|
||
direkt auf dem Startbildschirm, ohne eigenes Menü.
|
||
2. **User-Flair** im Subreddit (z. B. „🔥 12-day streak“, Tagessieger). Vorher in der Doku prüfen, wie Devvit Web
|
||
Flair setzt.
|
||
3. Winter- bzw. Winterfest-Thema passend zur Bewertung im Dezember.
|
||
|
||
## Phase 3 – optional
|
||
|
||
- KI-Duell gegen den Computer als Bonus nach dem Tagesrätsel. Nur, wenn Phase 1 und 2 poliert sind.
|
||
|
||
## Phase 4 – Einreichung (alles nach Rückfrage beim Besitzer)
|
||
|
||
- Eigenes Subreddit für das Spiel; öffentlicher Demo-Post (der einzige öffentliche Schritt).
|
||
- App-Listing auf developers.reddit.com (Upload, **kein** `devvit publish`).
|
||
- Auf Devpost angeben, dass es eine Portierung ist, und erklären, was seit dem 11. Nov. neu entstanden ist (Pflicht
|
||
für bestehende Projekte); optional die Feedback-Umfrage.
|
||
- **`README.md` (Englisch) ersetzen:** Spiel und Spielanleitung – Pflicht laut offiziellen Regeln.
|
||
- Subreddit öffentlich (oder „Restricted“: lesbar für alle) und < 200 Mitglieder.
|
||
- Video (< 1 min) und Screenshots für die Beschreibung – die Jury darf allein danach urteilen.
|
||
- **Bewertung 9.–16. Dez.:** Tagesposts müssen weiterlaufen; in der Zeit keine riskanten Uploads.
|
||
|
||
## Grober Zeitplan
|
||
|
||
| Bis | Was |
|
||
| :------- | :--------------------------------- |
|
||
| 16. Nov. | Phase 0 |
|
||
| 29. Nov. | Phase 1 spielbar im Test-Subreddit |
|
||
| 4. Dez. | Phase 2, Polish |
|
||
| 8. Dez. | Puffer, Demo-Post, Einreichung |
|
||
|
||
## Weitere Entscheidungen (2026-11-13)
|
||
|
||
- **Eigener Seed für Reddit** (Präfix ungleich `hivedoku-daily-`): Sonst wäre die Tageskarte dieselbe wie auf der
|
||
Website, und „Watch the best game“ dort verriete die Lösung.
|
||
- **Nur der erste Versuch zählt** für die Bestenliste; Weiterspielen ist erlaubt, wird aber nicht gewertet.
|
||
- **Subreddit:** Entwickelt wird im Test-Subreddit, das `devvit playtest` selbst anlegt. `r/Ninelands` (falls frei)
|
||
legt der Besitzer erst für den Demo-Post an. Hochgeladene, nicht veröffentlichte Apps lassen sich nur in Subreddits
|
||
mit < 200 Mitgliedern installieren – für die Jam-Demo reicht das.
|
||
|
||
## Devvit-Vorgaben, die das Design betreffen (Doku, 2026-11-13)
|
||
|
||
- **Ton:** erst nach einer Nutzeraktion abspielen, **Stumm-Knopf** im Spiel, bei `visibilitychange` stumm schalten.
|
||
- **Gesten:** nur Tippen/Klicken; kein Scroll-Abfangen, kein Zoom/Pan – man muss am Post vorbeiscrollen können.
|
||
- **Expanded Mode** nur auf ausdrückliche Aktion (Knopf), nie automatisch; die Standardansicht bleibt im Post.
|
||
- **Responsive**, die meisten Spieler sind mobil.
|
||
- **`localStorage` wird bei jedem App-Update gelöscht** → Streak, Freischaltungen, gewähltes Thema, Stumm-Schalter in
|
||
Redis speichern.
|
||
- Server: serverlos, max. 30 s je Anfrage, 4 MB Anfrage, 10 MB Antwort; kein `fs`. Client: keine externen Abrufe.
|
||
|
||
## Risiken
|
||
|
||
- Devvit ändert sich schnell: Scheduler, Redis, Flair und Journeys vor dem Bauen in der aktuellen Doku lesen
|
||
(`https://developers.reddit.com/docs/llms.txt`, die Seiten gibt es als `.md`, per `curl` abrufbar).
|
||
- App-Größe durch die Musik.
|
||
- Die Engine kennt viele Modi (Kampagne, Puzzles, Duell). Unnötiges darf nicht ins Bundle wandern
|
||
(Metafile von esbuild prüfen).
|
||
|
||
## As built
|
||
|
||
### 2026-11-13 – Repo, CI, Lizenz
|
||
|
||
- Repo `DieLustigeTierwesen/ninelands-reddit` (privat) angelegt.
|
||
- CI: Die `parallel:`-Stufe der Vorlage kennt der Forgejo-Runner nicht („Invalid run/uses syntax“). Ersetzt durch
|
||
einzelne Schritte nacheinander.
|
||
- Lint der Vorlage war rot: Das Biome-Schema 2.6.4 passte nicht zur installierten CLI 2.6.6, und `package.json` hatte
|
||
keinen Zeilenumbruch am Ende.
|
||
- Lizenz auf GPL-3.0 umgestellt (`LICENSE` aus dem Hauptspiel, `package.json` → `GPL-3.0-only`). Der BSD-Text der
|
||
Vorlage bleibt als `LICENSE-TEMPLATE.md`, denn BSD verlangt, dass der Hinweis erhalten bleibt. `CREDITS.md` angelegt.
|
||
|
||
### 2026-11-13 – Phase 0: Engine und Assets
|
||
|
||
- `scripts/sync-engine.sh <tag>` holt per `git archive` (nur lesend) `src/core`, `src/campaign`, `src/puzzles` nach
|
||
`src/engine/` und Sprites (Tiles, Brett-Themen) sowie Musik nach `public/`. Stand in `src/engine/VERSION`
|
||
(`v0.11.0`). Avatare nicht übernommen (braucht die Basic-Version nicht).
|
||
- `src/engine/` hat eine eigene `tsconfig.json` (strict, `noUncheckedIndexedAccess` aus, `resolveJsonModule`,
|
||
`lib` WebWorker für `crypto`/`structuredClone`). **`erasableSyntaxOnly` aus**, weil `duel.ts` Parameter-Properties
|
||
nutzt. Biome ignoriert `src/engine/`.
|
||
- **Fallstrick:** Die Engine importiert ohne Dateiendung (`'./Board'`). `node --experimental-strip-types` kann das
|
||
nicht auflösen. Deshalb bündelt `test:unit` die Tests jetzt erst mit esbuild nach `dist/test/` (`--packages=external`,
|
||
damit `@devvit/web` als ein Modul geladen wird und die Redis-Attrappen im Server-Test greifen), dann `node --test`.
|
||
Der Glob `src/**/*.test.ts` läuft unter `sh` ohne globstar = nur eine Ebene tief (`src/*/…`).
|
||
- `src/shared/daily.ts`: Tageskarte mit Seed `fnv1a('ninelands-reddit-daily-' + UTC-Tag)`, Nummer ab 2026-11-13 (#1),
|
||
Welt-Regel wie im Hauptspiel `rolledTrait(seed)`. **Präfix und `FIRST_DAY` nie ändern** (sonst andere Karten und
|
||
Nummern rückwirkend); Schutztests in `daily.test.ts`.
|
||
- Ziele (`computeGoals`, 7 Bot-Partien) plus Nachspielen dauern in Node ≈ 80 ms – unproblematisch für den Server.
|
||
Für die Prüfung **nicht** `verifyProgress` aus `progress.ts` nehmen: das zieht Kampagnen-JSONs ins Bundle und kennt
|
||
nur die Website-Tageskarte. Eigene Prüfung aus `applyMoves` + `computeGoals` + `starsOf`.
|
||
- Musik: ein Titel („Peaceful Village“), vom Sync-Skript aus der Opus-Datei des Hauptspiels als **MP3 64 kbit/s**
|
||
kodiert (1,0 statt 1,6 MB; Besitzer hat Varianten verglichen). MP3 läuft auch in iOS-Webviews. Ein Größenlimit für
|
||
Webview-Assets steht nicht in der Doku; weitere Titel in `scripts/sync-engine.sh` (`musik=(…)`) ergänzen.
|
||
- Biome: Ordner-Ausschluss als `"!src/engine"` schreiben – `"!src/engine/"` mit Schrägstrich griff nicht.
|
||
|
||
### 2026-11-13 – Phase 1, Server
|
||
|
||
- Endpunkte: `GET api/day` (Karte, Ziele, eigenes Ergebnis, Streak, Top 10, Spielerzahl) und `POST api/submit`
|
||
(Züge). Der Tag eines Posts steht in den Post-Daten (`postData.day`); ohne Daten gilt heute (UTC).
|
||
- **Prüfung:** Der Server spielt die Züge nach (`src/server/verify.ts`) und wertet nur **fertige** Partien (kein Zug
|
||
mehr möglich) – sonst ließe sich früh abbrechen. Gold, Länder und Sterne rechnet er selbst; der Client schickt nur
|
||
Züge.
|
||
- **Erster Versuch:** `hSetNX` auf `results:{day}` ist atomar; ein zweiter Versuch liefert `counted: false` und das
|
||
alte Ergebnis.
|
||
- **Bestenliste:** Sorted Set mit `Sterne·100 000 + Gold + 1/(seq+1)` – `seq` ist die laufende Nummer der Abgabe,
|
||
dadurch gewinnt bei Gleichstand die frühere, und die Werte sind eindeutig (Platz = `zCard − zRank`).
|
||
- **Streak** zählt nur, wenn der Post von heute gespielt wird; alte Posts kann man nachspielen (Bestenliste ja,
|
||
Streak nein). Angezeigt wird 0, wenn der letzte Tag älter als gestern ist.
|
||
- **Tagespost:** Scheduler `1 0 * * *` (00:01 UTC), dazu bei Installation und per Moderator-Menü. Hash `posts`
|
||
verhindert doppelte Posts.
|
||
- `devvit.json`: **`redis: true` muss gesetzt sein** (Standard ist `false`). Startbildschirm inline (`height: tall`),
|
||
Spiel als Entry `game` (Expanded).
|
||
- **Fallstrick:** `hMGet` ist laut Doku nicht für jede App freigeschaltet → einzelne `hGet`.
|
||
- Offen: Löschungen von Nutzern respektieren (Devvit-Regel) – Ergebnis und Name bei Kontolöschung entfernen.
|
||
|
||
### 2026-11-13 – Phase 1, Client
|
||
|
||
- **Brett aus dem Hauptspiel:** `BoardView.ts`, `atlas.ts`, `geometry.ts`, `board.css` und `env.ts` kommen per
|
||
Sync-Skript nach `src/engine/app/` (unverändert). `import.meta.env` und `__API_ORIGIN__` setzt esbuild per
|
||
`--define`; Typen dafür in `src/types/ninelands-env.d.ts`. Das Symbol `i-warn` (Warn-Badge der Vorschau) steht
|
||
direkt in `public/game.html`.
|
||
- **Startbildschirm** (`splash`, inline, `height: tall`): Name, Nummer, Datum, Mini-Karte, Gesetz, Ziele bzw. eigenes
|
||
Ergebnis, Spielen-Knopf, Streak, Top 3, „i“ mit Credits.
|
||
- **Spiel** (`game`, Expanded): Ziele-Leiste mit Sternen live, Gesetz (antippbar → Regeln), Brett, Hinweiszeile,
|
||
Figuren mit Kosten, Rückgängig (1 Schritt wie im Hauptspiel). Touch: erster Tipp zeigt Vorschau und Grund, zweiter
|
||
setzt. Nach der Burg automatisch Bauer. Ende → Abgabe → Ergebniskarte (Sterne, Gold, Länder, Platz, Streak, Top 10,
|
||
Teilen über `showShareSheet`, „Play again“ als Übung, „Done“ schließt den Expanded Mode).
|
||
- Gäste spielen ohne Wertung (Hinweis + `showLoginPrompt`); wer schon gespielt hat, sieht „Practice“.
|
||
- **Layout-Fallstrick:** Brett und Hinweis liegen in einem Container mit `container-type: size`; das Brett ist
|
||
`min(100cqw, 100cqh − 3.2em)` breit. Mit `flex: 1` + `aspect-ratio` allein blieb viel Leerraum zwischen Brett und
|
||
Hinweis.
|
||
- Schrift Pixelify Sans über `@fontsource/pixelify-sans` (OFL), von esbuild nach `public/fonts/` kopiert – externe
|
||
Schrift-Abrufe sind im Devvit-Client gesperrt.
|
||
- Musik: Start beim ersten Tippen, Stumm-Knopf, Pause bei `visibilitychange`. Stumm-Schalter vorerst in
|
||
`localStorage` (geht bei App-Updates verloren – vertretbar; Redis erst, wenn es stört).
|
||
- Lokale Vorschau ohne Reddit: kleine Server-Attrappe + Playwright-Screenshots (Skripte im Scratchpad, nicht im Repo).
|
||
- Biome ergänzt bei `--write` Dateiendungen an Engine-Importen (`'../engine/core/Game.ts'`) – ist so gewollt.
|
||
|
||
### 2026-11-13 – Spielende und Ziel-Hervorhebung (Rückmeldung Besitzer nach erstem Playtest)
|
||
|
||
- Problem: Man wusste nicht, wie man die Partie beendet – sie endete nur, wenn kein Zug mehr möglich war (Bauern
|
||
sind gratis, das dauert).
|
||
- **„End game“-Knopf** wie im Hauptspiel, mit zweitem Tipp zur Bestätigung (3 s). Der Server wertet jetzt auch früh
|
||
beendete Partien; abgelehnt wird nur eine leere (`EMPTY`). Kein Vorteil durch frühes Aufhören: Gold zählt vor der
|
||
Abgabezeit.
|
||
- **Kein automatisches Ende bei 3 Sternen** – unter Spielern mit 3 Sternen entscheidet das Gold; stattdessen Hinweis
|
||
„All 3 stars! Keep going for more gold – or end the game.“
|
||
- Erreichte Ziele werden **golden** hervorgehoben (wie `goal-chip.done` im Hauptspiel).
|
||
- `devvit playtest` trägt `dev.subreddit` (`ninelands_game_dev`) in `devvit.json` ein.
|
||
- Rückmeldung Besitzer: Bei „One farm per land“ bleiben am Ende oft nur Ritter, die Gold kosten. Darum prüft der
|
||
Client nach jedem Zug, ob noch ein Zug Gold bringt (`gainLeft`, alle gültigen Züge per `preview`, < 1 ms). Wenn
|
||
nicht: Hinweis „None of your remaining moves earns gold – end the game to keep your gold.“ und „End game“ leuchtet golden.
|
||
- Fix: Im goldenen Ziel-Feld waren Stern und „/ Ziel“ ebenfalls gold (Gold auf Gold, unsichtbar) – beim Umsortieren
|
||
der CSS-Regeln für Biome (`noDescendingSpecificity`) waren die Deklarationen vertauscht. Jetzt alles in Tinte.
|