344 lines
26 KiB
Markdown
344 lines
26 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.
|
||
|
||
### 2026-11-13 – Katalog-Assets (Entwurf)
|
||
|
||
- `npm run capture` (`scripts/capture/`): baut eine Aufnahmeseite nur mit dem Brett, ein Bot (`smart`) spielt eine
|
||
Partie mit Vorschau, Playwright nimmt auf, ffmpeg macht daraus `dist/marketing/featuring.mp4` (1500×1000, 30 fps,
|
||
≈ 11 s, ≈ 1,7 MB), `fallback.gif` (1000×667, ≤ 2 MB), `fallback.png` (1500×1000) und `tile.png` (560×256 mit
|
||
Schriftzug). Maße laut Developer Portal; Größen werden bei Bedarf automatisch heruntergeregelt.
|
||
- **Karte vom 2026-10-10** (vor dem ersten Spieltag): läuft nie als Tageskarte – eine künftige Tageskarte im Video
|
||
wäre ein Spoiler. Der Bot holt dort in 26 Zügen alle 3 Sterne.
|
||
- Playwright als devDependency in der Version des Hauptspiels (1.55.1), damit die vorhandenen Browser im Cache passen.
|
||
- Vor der Einreichung neu aufnehmen (z. B. mit Winter-Thema aus Phase 2).
|
||
|
||
### 2026-11-13 – Devvit Journeys
|
||
|
||
- `@devvit/analytics` 0.15.3 (passend zu Devvit 0.15.3), `permissions.journeys: true`.
|
||
- **Fallstrick:** Der fertige `createTelemetryRouter` ist ein Express-Router; unser Server hat kein Framework. Darum
|
||
bauen `src/server/journeys.ts` die fünf Endpunkte (`api/telemetry/journey/start|progress|interaction|end|app-ready`)
|
||
nach und reichen an `telemetry` aus `@devvit/analytics/server/reddit` weiter. Das Paket zieht Express als Peer mit
|
||
ins Server-Bundle (nur importiert, nicht benutzt).
|
||
- Ereignisse: `App.Ready` in Start- und Spielansicht; `Journey.Start` beim **Setzen der Burg** (bewusste Aktion, nicht
|
||
das Öffnen – Vorgabe der Doku); `Journey.Progress` je erreichtem Stern (1/3, 2/3, 3/3); `Journey.End` mit
|
||
`win` = mindestens ein Stern, `score` = Gold. Alles „feuern und vergessen“.
|
||
- Reddit leitet Ereignisse erst nach Freischaltung in der App-Review weiter (Allowlist); bis dahin „dropped“.
|
||
- Katalog (Besitzer): App-Icon aus dem Hauptspiel, Texte wie vorgeschlagen, Kategorie Game → Strategy.
|
||
|
||
### 2026-11-13 – README und Löschregel
|
||
|
||
- **`README.md`** (Pflicht laut Jam und Devvit-Regeln, Großschreibung): Überblick, Spielanleitung, Sterne/Bestenliste/
|
||
Streak, Einrichtung für Moderatoren, Datenschutz, Entwicklung, Credits, Lizenz. Die Vorlage hieß `readme.md`.
|
||
- **Devvit-Regel „Enable and respect user deletions“:** Für Kontolöschungen gibt es keinen Trigger. Deshalb laufen
|
||
alle Schlüssel mit User-ID oder Namen nach **30 Tagen** ab (`RETENTION_S`), jede Abgabe verlängert die Frist.
|
||
Spielerdaten liegen jetzt je Spieler unter `player:{userId}` statt im Hash `players` (einzelne Hash-Felder können
|
||
nicht ablaufen). Folge: Bestenlisten älterer Posts sind nach 30 Tagen leer; wer 30 Tage nicht spielt, verliert
|
||
Statistik und Freischaltungen (Phase 2 – beim Entwurf der Freischaltungen bedenken). Schutztest in
|
||
`server.test.ts`.
|
||
- **Playtest-Fallstrick:** Mehrere Änderungen in kurzer Folge (vor allem an `devvit.json`) lösten zwei Uploads mit
|
||
derselben Versionsnummer aus („This AppVersion already exists“), `devvit playtest` brach ab → neu starten.
|
||
|
||
### 2026-11-13 – Phase 2: Aussehen, Flair, Kommentar
|
||
|
||
- **Freischaltungen** (`src/shared/look.ts`, Browser und Server gleich): Farbe frei; Thema über die **beste** Streak
|
||
(3 Herbst, 7 Winter, 14 Nacht); Banner je 10 Sterne (Kreuz 50, Stern 60, Krone 80); Volk über Sterne (Orks 20,
|
||
Piraten 40). **Winterfest der Reddit-Fassung 1. Dez. – 6. Jan.** (Hauptspiel: ab 15. Dez.) – damit Winterfest-Thema
|
||
und Schneeflocken-Banner während der Jury-Bewertung (9.–16. Dez.) für alle frei sind.
|
||
- Auswahl über **„🎨 Realm“** auf dem Startbildschirm (einziger zusätzlicher Knopf, Besitzer einverstanden). Gesperrtes
|
||
zeigt die Bedingung. Speichern per `POST api/look`; der Server prüft gegen die Freischaltungen (`sanitizeLook`) –
|
||
Gesperrtes fällt auf den Standard zurück. Angewendet über `setBoardTheme/Banners/Look` aus der Brett-Kopie.
|
||
- Ergebniskarte: **„🎁 Unlocked: …“** bei neuen Freischaltungen (Vergleich der Werte vor und nach der Partie).
|
||
- **Ergebnis als Kommentar** (`POST api/comment`): `reddit.submitComment({runAs: 'USER'})`, braucht
|
||
`permissions.reddit.asUser: ["SUBMIT_COMMENT"]` (Reddit prüft das in der Review). Text baut der Server aus dem
|
||
gespeicherten Ergebnis, einmal je Spieler und Tag (`comments:{day}`, hSetNX).
|
||
- **Flair**: nach jeder gewerteten Partie `🔥 N-day streak` (ab 2); beim Tageswechsel bekommt der Sieger von gestern
|
||
`👑 Ruler of the day` (`crownDay`, gilt am Folgetag). Fehler beim Flair werden nur protokolliert. Reddit-Vorgabe:
|
||
kein Komma im Flair.
|
||
- **Fallstrick:** Jede Änderung an `devvit.json` lässt `devvit playtest` die Konfiguration neu laden; zusammen mit dem
|
||
Build gab es zweimal doppelte Versionsnummern und Abbruch. Bei Änderungen an `devvit.json` Playtest stoppen und neu
|
||
starten.
|
||
- Folge der 30-Tage-Regel: Freischaltungen gehen nach 30 Tagen ohne Spiel verloren.
|
||
|
||
### 2026-11-13 – Nummer je Subreddit
|
||
|
||
- „Ninelands #N“ zählt **je Subreddit ab dem ersten Tag dort** (`firstDay` in Redis, beim ersten Aufruf per `nx`
|
||
gesetzt, enthält keine Nutzerdaten und läuft nicht ab). So beginnt `r/Ninelands` mit #1, auch wenn es erst im
|
||
Dezember startet. Die Karte hängt weiter nur am Datum (alle spielen am selben Tag dieselbe). `FIRST_DAY` entfällt.
|
||
- Plan Besitzer/Claude: `r/ninelands_game_dev` bleibt Test-Subreddit; `r/Ninelands` (Name frei) etwa eine Woche vor
|
||
der Abgabe anlegen (≈ 1. Dez.), öffentlich oder „Restricted“, < 200 Mitglieder bis zur Bewertung.
|
||
|
||
### 2026-11-13 – Trailer, Galerie, Vimeo-Cover
|
||
|
||
- `npm run capture:trailer` (`scripts/capture/trailer.mjs`, Bühne `stage.html/.ts/.css`, Partie `plan.ts`): baut den
|
||
Client, startet eine API-Attrappe und lässt das **echte** Spiel (`public/`) in einer Bühne laufen – Untertitel links,
|
||
Handy (420 × 840, skaliert) rechts, ein „Finger“ zeigt jeden Tipp. Der Bot (`smart`) spielt die Karte vom 2026-10-10;
|
||
die Partie wird beim besten Stand abgeschnitten und per „End game“ beendet (zeigt die Funktion gleich mit).
|
||
Ergebnis: `trailer.mp4` (1920 × 1080, ≈ 47 s, Musik mit Ein-/Ausblenden), `vimeo-cover.png`, `gallery/*.png` (3:2).
|
||
- Szenen laufen dreimal: Video mit Pausen, dann ohne Pausen für Galerie (1800 × 1200) und Cover (1920 × 1080).
|
||
- **Fallstricke:** `UNIT_TYPES` ist `Burg, Ritter, Bauer, Läufer` – die Knopf-Reihenfolge kommt deshalb aus der Engine,
|
||
nicht von Hand. Pixelify Sans verbindet „fi“ zu einem Zeichen, das wie „A“ aussieht („Arst“) → auf der Bühne
|
||
`font-variant-ligatures: none`. „Play“ im Startbildschirm ruft `requestExpandedMode` (gibt es außerhalb von Reddit
|
||
nicht) → nur angetippt, die Bühne wechselt selbst ins Spiel. Playwright-Chromium spielt kein H.264 → eigene
|
||
Aufnahmen werden vorher nach VP9/WebM umgewandelt.
|
||
- Reddit selbst (Feed, Kommentar im Thread, Flair) nimmt der Besitzer mit seinem Konto auf (Reddit blockt
|
||
Automatisierung); optional als `dist/trailer/input/feed.*` / `comment.*`.
|
||
|
||
### 2026-11-13 – Realm im Vollbild (Bug vom Besitzer)
|
||
|
||
- **Bug:** Am Handy öffnete „🎨 Realm“ das Fenster im Inline-Post (512 px hoch). Die Liste ist höher, „Done“ lag
|
||
außerhalb, und scrollen darf ein Inline-Post nicht (Devvit-Regel „Scrolling within inline webviews is prohibited“,
|
||
Scroll-Falle im Feed) → man saß fest.
|
||
- **Lösung:** eigener Einstiegspunkt `realm` (`public/realm.html`, `src/client/realm-page.ts`), geöffnet mit
|
||
`requestExpandedMode(ev, 'realm', {title: 'Your realm'})`; „Done“ ruft `exitExpandedMode`. Der Knopf klebt unten
|
||
(`position: sticky`), damit er auch bei langer Liste sichtbar bleibt. Der Startbildschirm lädt beim `focus`-Ereignis
|
||
(Rückkehr aus dem Vollbild, laut Doku der Ersatz für `addWebViewModeListener`) Tag und Aussehen neu – damit zeigt er
|
||
nach dem Spielen auch gleich das eigene Ergebnis.
|
||
- Credits-Fenster im Post geprüft: passt auch bei 320 px Breite (Knopf endet bei 413 von 512 px).
|
||
- Pixelify-Ligatur „fi“ (sieht aus wie „A“) jetzt für die ganze App aus (`font-variant-ligatures: none`).
|
||
- **Fallstrick:** `devvit.json` geändert → laufenden Playtest stoppen und neu starten.
|
||
|
||
### 2026-11-13 – Bilder für die Community-Posts
|
||
|
||
- `npm run capture:community` (`scripts/capture/community.{mjs,ts,html,css}`): zwei Bilder 1080 × 1350 (4:5, im Feed
|
||
am Handy groß) – „How to play“ (vier Figuren mit beanspruchten Feldern, Gold je Gelände, Sterne) und „Choose your
|
||
realm“ (Völker, Bretter, Banner mit Freischalt-Bedingung). Muster kommen aus `UNIT_DEFS` (Strahlen/Ring, Budget),
|
||
Bedingungen aus `look.ts` – stimmen also nach Regeländerungen. Seite 540 × 675 mit Faktor 2 aufgenommen, Pixel scharf.
|
||
- Das Skript meldet, wenn der Inhalt höher als das Bild ist („… px zu hoch“).
|
||
- Texte der drei Posts: `docs/community-posts.md`.
|
||
|
||
### 2026-11-13 – Realm: „Play“ direkt ins Spiel (Rückmeldung Besitzer)
|
||
|
||
- Realm im Vollbild klappt am Handy. Aber „Done“ führte zurück in den Post, und für „Play“ ging es wieder ins Vollbild –
|
||
umständlich. Jetzt steht neben „Done“ ein „Play“ (bzw. „Play again“), das mit `requestExpandedMode(ev, 'game')`
|
||
direkt ins Spiel wechselt; laut Doku geht das auch aus einer Vollbild-Ansicht heraus. „Done“ bleibt der Weg zurück.
|
||
- Besitzer bestätigt im Test-Subreddit: Ergebnis als Kommentar funktioniert. Video-Posts sind in r/Ninelands aus
|
||
(gewollt) → Go-live-Post mit Cover-Bild und Vimeo-Link.
|
||
|
||
### 2026-11-14 – „Faction“ statt „People“, Logs vom ersten Tageswechsel
|
||
|
||
- Wortwahl (Besitzer): Die Völker heißen auf Englisch überall **Faction/Factions** (Realm-Zeile, Freischalt-Hinweis
|
||
auf der Ergebniskarte, Community-Bild und -Post, Trailer-Untertitel, Einreichungstext). Interne Namen (`faction`,
|
||
CSS `.peoples`) bleiben.
|
||
- `devvit logs ninelands_game_dev --since 1d`: keine Fehler (weder `server error` noch `flair`/`crown flair`), der
|
||
Tageswechsel lief also ohne Ausnahme. Sonst nur „Telemetry event dropped: event was valid, but Journey telemetry is
|
||
not recorded for playtest app versions.“ → **Journeys werden gültig gesendet, aber nur für installierte
|
||
(Nicht-Playtest-)Versionen gezählt.** Auswertung also erst nach Upload + Installation in r/Ninelands sichtbar.
|
||
|
||
### 2026-11-14 – Ich-Form, keine langen Gedankenstriche
|
||
|
||
- Entscheidung Besitzer: Alle englischen Texte (Devpost, Community-Posts, README, CREDITS, Spiel, Bilder, Trailer) in
|
||
der **Ich-Form** und **ohne „–“/„—“**: stattdessen Punkt, Komma, Doppelpunkt oder Semikolon; Datumsbereiche als
|
||
„Dec 1 to Jan 6“. Die Devpost-Überschriften („How we built it“ usw.) sind fest vorgegeben und bleiben.
|
||
- Deutsche interne Doku und Code-Kommentare bleiben unverändert. Prüfen:
|
||
`grep -rn '[–—]' src/client src/shared src/server scripts/capture | grep -vE ':\s*(//|\*)'` muss leer sein.
|
||
|
||
### 2026-11-14 – Fehler in den Regeltexten: Gold gibt das Feld unter der Figur
|
||
|
||
- Beim Abgleich mit dem Leitfaden des Hauptspiels gefunden: Gold bringt **nur das Feld, auf dem eine Figur steht**
|
||
(`Game.resolveYield`: Weide 5/7, Erz/Kräuter 20, Ritter ×2), **nicht** jedes beanspruchte Feld. Falsch stand es im
|
||
Hilfe-Fenster des Spiels, in der README, im Community-Post „How to play“ und auf dessen Bild. Alles korrigiert, das
|
||
Bild neu erzeugt. „7 next to a lake“ heißt richtig „7 in a land with a lake“.
|
||
- Lehre: Regeltexte nie aus dem Gedächtnis schreiben, sondern gegen die Engine (`src/engine/core/Game.ts`,
|
||
`units.ts`, `WorldRules.ts`) oder `hivedoku/guide/chapters/` prüfen.
|