docs: record the Slice 0.9 quest system and its deviations
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1354,13 +1354,22 @@ Erster NPC: **Borin, Quartermaster** an `south-gate` (Graufurt) mit
|
||||
DIALOGUE + MERCHANT + RESOURCE_EXCHANGE gleichzeitig — der Kompositionsfall
|
||||
aus §2/§26 in echt.
|
||||
|
||||
## Abweichung 1 — kein `NpcQuestAssignment`
|
||||
## Abweichung 1 — kein `NpcQuestAssignment` — **aufgelöst in Slice 0.9**
|
||||
|
||||
§34 listet es im V1-Scope, aber es gibt noch kein Questsystem (Slice 0.9).
|
||||
§34 listet es im V1-Scope, aber es gab in Slice 0.8 noch kein Questsystem.
|
||||
Eine Tabelle mit Fremdschlüssel auf eine nicht existierende `quests`-Tabelle
|
||||
ist nicht baubar, und ein `questKey`-String ohne Validierung wäre spekulative
|
||||
Architektur (AGENTS §1.7). Nachzuholen mit Slice 0.9, zusammen mit den
|
||||
Dialog-Actions `START_QUEST` / `COMPLETE_QUEST`.
|
||||
Architektur (AGENTS §1.7).
|
||||
|
||||
Slice 0.9 hat das nachgeholt: `NpcQuestAssignment` liegt unter
|
||||
`apps/api/src/quests/entities/npc-quest-assignment.entity.ts` und trägt die
|
||||
Rollen `OFFER` / `TURN_IN` / `PROGRESS` aus §14. Der Unique-Index umfasst die
|
||||
Rolle mit, damit derselbe NPC eine Quest sowohl vergeben als auch annehmen
|
||||
kann — genau der Fall aus §14, den die erste Questkette benutzt: der South
|
||||
Gate Warden vergibt und nimmt an, Borin steht als `PROGRESS` dazwischen.
|
||||
|
||||
Die Dialog-Actions `START_QUEST` / `COMPLETE_QUEST` bleiben offen, siehe
|
||||
Abweichung 4.
|
||||
|
||||
## Abweichung 2 — kein `NpcDialogueProfile`
|
||||
|
||||
@@ -1372,14 +1381,52 @@ neu zu schreiben.
|
||||
|
||||
## Abweichung 3 — Conditions, die (noch) nichts beantworten kann
|
||||
|
||||
`GameConditionType` enthält die vollständige V1-Liste aus §19, aber nur
|
||||
`REGION_REPUTATION`, `WORLD_RENOWN`, `FLAG_SET` und `HAS_ITEM` sind
|
||||
auswertbar. `QUEST_ACTIVE`, `QUEST_COMPLETED`, `BOSS_DEFEATED` und
|
||||
`LOCATION_DISCOVERED` haben noch kein System dahinter.
|
||||
`GameConditionType` enthält die vollständige V1-Liste aus §19, aber nicht jeder
|
||||
Typ hat ein System dahinter.
|
||||
|
||||
Sie werten **fail-closed** aus, also immer "nicht erfüllt". Für ein Gate ist
|
||||
die sichere Richtung eines Fehlers zu, nicht offen — ein Quest-Gate darf
|
||||
niemals aufgehen, nur weil es keine Quests gibt.
|
||||
Seit Slice 0.9 auswertbar: `REGION_REPUTATION`, `WORLD_RENOWN`, `FLAG_SET`,
|
||||
`HAS_ITEM`, `QUEST_ACTIVE`, `QUEST_COMPLETED`. Offen bleiben `BOSS_DEFEATED`
|
||||
(Slice 0.11) und `LOCATION_DISCOVERED` (wird pro Charakter nicht getrackt).
|
||||
|
||||
Die beiden Quest-Typen werten boolesch aus wie `FLAG_SET`, nicht numerisch:
|
||||
eine Quest läuft oder nicht, `actual` bleibt `null`. `value: false` ist die Art,
|
||||
wie Content die Verneinung ausdrückt — die Angebotszeile des Wardens braucht
|
||||
"noch nicht begonnen und noch nicht abgeschlossen", ohne dass die
|
||||
Condition-Sprache ein NOT bekommt.
|
||||
|
||||
Die verbleibenden Typen werten **fail-closed** aus, also immer "nicht erfüllt".
|
||||
Für ein Gate ist die sichere Richtung eines Fehlers zu, nicht offen.
|
||||
|
||||
## Abweichung 4 — Questschritte laufen nicht über Dialog-Actions
|
||||
|
||||
`DialogueActionType` enthält `START_QUEST` und `COMPLETE_QUEST`, aber beide
|
||||
bleiben auch mit dem Questsystem aus Slice 0.9 wirkungslos.
|
||||
|
||||
Sie auszuführen bräuchte zwei Dinge, die es nicht gibt: einen Endpoint, über
|
||||
den der Client eine Dialog-Response *auswählt*, und einen Action-Executor
|
||||
dahinter. Playable Slice 0.9 §10 verlangt ausdrücklich einen
|
||||
schrittbasierten Questautomaten und verbietet eine verzweigende
|
||||
Narrative-Engine — und ein Response-Endpoint plus Executor wäre genau deren
|
||||
Anfang, für eine Kette mit fünf Schritten.
|
||||
|
||||
Stattdessen:
|
||||
|
||||
```text
|
||||
POST /api/npcs/:npcKey/quests/:questKey/accept
|
||||
POST /api/npcs/:npcKey/quests/:questKey/advance
|
||||
```
|
||||
|
||||
Keine der beiden Routen nimmt einen Body. Welcher Schritt gerade dran ist, was
|
||||
er vergibt und was er verbraucht, entscheidet der Server (AGENTS §5). Der Satz,
|
||||
den der NPC zu einem Schritt sagt, ist Content auf dem `QuestObjective`
|
||||
(`npc_line`) und kommt mit der Antwort zurück.
|
||||
|
||||
Nachzuholen wäre das, sobald ein NPC wirklich verzweigende Antworten braucht:
|
||||
dann ein Response-Auswahl-Endpoint, ein Executor über
|
||||
`SUPPORTED_DIALOGUE_ACTIONS`, und die Questschritte können auf Dialog-Actions
|
||||
umziehen, ohne dass die Questtabellen sich ändern.
|
||||
|
||||
---
|
||||
|
||||
## Offene Designfrage — World Renown im Tausch
|
||||
|
||||
@@ -1398,3 +1445,40 @@ anzufassen.
|
||||
|
||||
Falls Renown später doch als Punktwährung gedacht ist, muss das zuerst in
|
||||
0.6.5 geändert werden — nicht hier.
|
||||
|
||||
---
|
||||
|
||||
# 41. Implementierungsstand (Slice 0.9)
|
||||
|
||||
```text
|
||||
QuestDefinition / QuestObjective apps/api/src/quests/entities/
|
||||
NpcQuestAssignment Rollen aus §14
|
||||
CharacterQuest Player-State, ein Row pro Charakter/Quest
|
||||
QuestProgressService leitet den aktiven Schritt ab (lesend)
|
||||
QuestService accept / advance, Effekte, Belohnungen
|
||||
QuestController / NpcQuestController apps/api/src/quests/quest.controller.ts
|
||||
```
|
||||
|
||||
Erste Questkette: **Trouble Beyond the Gate**, vergeben und angenommen von
|
||||
Halvik am Südtor, mit Borin als Zwischenschritt — der Mehr-NPC-Fall aus §14
|
||||
in echt.
|
||||
|
||||
Zwei Module statt einem: `QuestProgressModule` enthält nur den lesenden Teil,
|
||||
weil `NpcService` Questmarker braucht und `QuestService` umgekehrt `NpcService`
|
||||
für die Erreichbarkeitsprüfung. Der Schnitt hält den Modulgraphen zyklenfrei,
|
||||
ohne `forwardRef`.
|
||||
|
||||
Es gibt bewusst **keine** Tabelle für Objective-Fortschritt. Slice 0.9 §11
|
||||
verlangt, dass Sammelschritte ihren Stand aus dem aktuellen Besitz ableiten;
|
||||
genau das macht `QuestProgressService`, und dadurch fallen die Softlock-Fälle
|
||||
aus §11 von selbst weg, statt einzeln behandelt zu werden.
|
||||
|
||||
Was ein Schritt *tut*, steht als Content auf dem `QuestObjective`
|
||||
(`sets_flag_key` / `sets_flag_npc_key`, `grants_loot_bag_key`,
|
||||
`consume_on_complete`, `advance_when_blocked`). `QuestService` wendet an, was
|
||||
es vorfindet, und verzweigt nirgends auf einen Questkey (AGENTS §9).
|
||||
|
||||
Markerregeln für die Ortsansicht (§24, Slice 0.9 §12): höchstens ein
|
||||
Questmarker pro NPC, Reihenfolge `QUEST_TURN_IN` > `QUEST_AVAILABLE` >
|
||||
`QUEST_IN_PROGRESS`. Halvik hält für dieselbe Quest zwei Rollen; ohne
|
||||
Vorrangregel trüge er zwei Symbole und keines würde etwas aussagen.
|
||||
|
||||
@@ -314,9 +314,85 @@ Hide Capacity: 5
|
||||
- [ ] Quest grants no XP.
|
||||
- [ ] All dialogue/UI text is English.
|
||||
|
||||
### Verification status
|
||||
|
||||
Every criterion above is covered by automated tests: the quest chain in
|
||||
`quest-state.spec.ts` and `quest.service.spec.ts`, the content in
|
||||
`vertical-slice.seed.spec.ts`, the markers in `npc.service.spec.ts`, and the
|
||||
three UI requirements of §12 in the web specs for the journal, the NPC screen
|
||||
and the location hotspots.
|
||||
|
||||
The boxes stay unticked because two things have not been run: the migration and
|
||||
seed against a real PostgreSQL database, and a hand-played pass through the
|
||||
chain in the running app. Neither was possible in the environment this slice was
|
||||
built in — no database was reachable. Do both before ticking, following the
|
||||
walkthrough in the implementation plan
|
||||
(`docs/superpowers/plans/2026-08-22-first-quest-and-bag-tutorial.md`, Task 13).
|
||||
|
||||
---
|
||||
|
||||
## 15. Out of Scope
|
||||
## 15. Implementation Decisions
|
||||
|
||||
Recorded here rather than only in the implementation plan, so the next slice
|
||||
reads them from the specification.
|
||||
|
||||
### D1 — The Basic Hide Bag is granted, not sold
|
||||
|
||||
§6 has Borin say "Take this" while also describing the Slice 0.8.5 offer, which
|
||||
costs 35 Silver. A player standing at that step has close to none, so the
|
||||
purchase path alone would stall the chain.
|
||||
|
||||
Borin's quest step therefore hands the bag over directly and idempotently. The
|
||||
referral flag is still written to the character's Borin state, so the 0.8.5
|
||||
`bypassConditions` gate opens the offer as well — a visible consequence, and a
|
||||
way back to a bag that is somehow lost. Nothing about reputation changes.
|
||||
|
||||
### D2 — No World Renown milestone
|
||||
|
||||
§9 lists one as optional. The demo character sits at Renown 1 and the first
|
||||
trade-in takes them to 2; a quest milestone would reach 3 and unlock Borin's
|
||||
Bandit Blade, which Slice 0.8.5 deliberately placed out of reach until Slice
|
||||
0.11. The quest pays regional reputation instead: **+10 Border Watch**.
|
||||
|
||||
### D3 — No Silver reward
|
||||
|
||||
§9 warns against a Silver reward that undermines the merchant trade loop, and
|
||||
names the bag and system knowledge as the point of the chain. `rewardSilver` is
|
||||
a content column set to 0, so retuning it later is a seed change.
|
||||
|
||||
### D4 — Quest steps use their own endpoints
|
||||
|
||||
NPC spec §40 asked Slice 0.9 to also wire the `START_QUEST` / `COMPLETE_QUEST`
|
||||
dialogue actions. There is no dialogue-response endpoint at all, and building
|
||||
one plus an action executor is the branching narrative engine §10 rules out.
|
||||
|
||||
Steps run through `POST /api/npcs/:npcKey/quests/:questKey/{accept,advance}`.
|
||||
Neither takes a body — which step is current, what it grants and what it
|
||||
consumes are the server's to decide. The line an NPC speaks for a step is
|
||||
content on the objective. Recorded as Abweichung 4 in the NPC specification.
|
||||
|
||||
### D5 — The demo character starts with no loot bag
|
||||
|
||||
The vertical-slice seed handed over a free Basic Hide Bag as a stopgap between
|
||||
Slices 0.8.5 and 0.9, with a note to remove it once a quest could grant one.
|
||||
That is done: the bagless HIDE capacity of 1 is the whole premise of §4. The
|
||||
migration also clears that one row for the demo character on databases seeded
|
||||
before this change.
|
||||
|
||||
### Addition — a hint for the second pelt hunt
|
||||
|
||||
Not in the specification, but the same courtesy §4 asks for. The Hide Bag holds
|
||||
five of *any* hide, so Tough Hides can crowd out the fifth Ashen Pelt. That step
|
||||
must not give up the way the first one does — it is meant to be finished (§7) —
|
||||
so it carries a hint instead:
|
||||
|
||||
```text
|
||||
Your hide bag is full of other goods. Trade some to Borin to make room for pelts.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 16. Out of Scope
|
||||
|
||||
Do not implement:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user