diff --git a/docs/Ashen_Realms_NPC_System_Specification_V1.md b/docs/Ashen_Realms_NPC_System_Specification_V1.md index a35caae..0a6cfc4 100644 --- a/docs/Ashen_Realms_NPC_System_Specification_V1.md +++ b/docs/Ashen_Realms_NPC_System_Specification_V1.md @@ -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. diff --git a/docs/playable-slices/0.9-First-Quest-and-Bag-Tutorial.md b/docs/playable-slices/0.9-First-Quest-and-Bag-Tutorial.md index 50bac49..96a73b5 100644 --- a/docs/playable-slices/0.9-First-Quest-and-Bag-Tutorial.md +++ b/docs/playable-slices/0.9-First-Quest-and-Bag-Tutorial.md @@ -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: