Files
Twungeon/Twungeon_MVP_Technical_Specification.md

36 KiB

Twungeon MVP Technical Specification

Status: Approved implementation baseline

Date: 2026-08-17

Approved: 2026-08-17

Product source of truth: Twungeon MVP PRD

Execution and verification: Twungeon MVP Acceptance-Test and Build Checklist

Intended implementation: TypeScript, RPGJS, Twurple, and a Twitch Extension

1. Purpose

This approved document translates the Twungeon MVP Product Requirements Document into an implementation-ready system design. It defines component responsibilities, authoritative state, command handling, game-state transitions, spatial rules, integration boundaries, failure behavior, and verification requirements.

The PRD remains authoritative for product scope and acceptance. If this technical specification conflicts with the PRD, the PRD wins and this document must be corrected.

This specification intentionally does not add post-MVP features. Decisions made here resolve behavior that the PRD requires but does not define precisely enough for deterministic implementation.

2. Design Goals

The implementation must optimize for the following goals, in order:

  1. Prove the complete Twitch viewer-to-gameplay loop with real viewers.
  2. Keep all game and identity decisions authoritative on the backend.
  3. Make command processing deterministic and testable.
  4. Keep Twungeon domain rules independent of RPGJS where practical.
  5. Make Twitch setup reproducible from a clean checkout.
  6. Prefer observable, recoverable failure over silent state corruption.
  7. Avoid systems outside the MVP, including long-term progression and production-grade persistence.

3. Scope and Constraints

3.1 Included

  • Twitch chat !spawn handling and follower validation
  • Stable Twitch user identity binding between chat and Extension
  • One character per Twitch user
  • Twitch Extension controls
  • Shared stream-facing game view and chronological action log
  • Dormant, Player Phase, Enemy Phase, floor transition, and administrative run reset states
  • AP, movement, combat, AutoGuard, self-heal, death, and resurrection
  • One Goblin Guard with guarding, pursuit, and return behavior
  • Varied, navigable two-room floors
  • Runtime reconnect and refresh handling
  • Clean-checkout Twitch integration documentation
  • Automated domain tests and live integration verification

3.2 Excluded

All non-goals in PRD section 22 remain excluded. In particular, this design does not introduce accounts beyond Twitch identity, inventory, progression, multiple classes or enemies, sophisticated AI, long-term persistence, or anti-exploit systems.

3.3 Runtime persistence boundary

The authoritative run exists in backend memory. Browser refreshes and temporary client disconnects do not remove or duplicate characters because clients reconnect to that backend state using the same Twitch user ID.

Surviving a backend process restart is not an MVP requirement. On an intentional or unintentional backend restart, Twungeon starts a new run on Floor 1 and logs the reset. Configuration and secrets persist outside the run state.

4. System Architecture

Twungeon uses one authoritative backend process for game decisions. Other components translate external input into authenticated commands or render backend state; they never modify game state directly.

Twitch chat ----> Twitch adapter -----------+
                                                |
Twitch events --> Twitch adapter -----------+--> Authoritative backend
                                                |      |          |
Extension ------> Extension API/WebSocket --+      |          +--> Action log
                                                       |
                                                       +--> RPGJS adapter
                                                       |
                                                       +--> State snapshots
                                                                  |
                                               +------------------+----------------+
                                               |                                   |
                                         Stream view                       Extension view

4.1 Authoritative backend

The backend owns:

  • the current run, floor, map, phase, and phase deadline;
  • all participating player records and character state;
  • Twitch identity bindings and follower eligibility results;
  • the Goblin state and AI decisions;
  • command ordering, validation, and idempotency;
  • random outcomes;
  • floor generation and transitions;
  • the canonical action log; and
  • state snapshots distributed to clients.

Only this component may accept or reject a command or perform a state transition.

4.2 Twitch adapter

The Twitch adapter uses Twurple to:

  • receive chat messages needed for !spawn;
  • extract the stable Twitch user ID and display data from the chat event;
  • verify follower eligibility using the configured broadcaster identity;
  • receive the configured Channel Points custom reward redemption event;
  • normalize Twitch events into internal messages; and
  • reconnect without submitting the same external event more than once.

Version-specific Twitch scopes, event types, and transport configuration must be confirmed against the official Twitch and Twurple documentation during the integration task and recorded in the setup guide. They must not be guessed or hard-coded into domain logic.

4.3 Extension gateway

The Extension gateway:

  • accepts Twitch-authenticated Extension sessions;
  • verifies the credential server-side;
  • resolves the credential to a stable Twitch user ID;
  • binds that ID to the same player created from chat;
  • accepts controller commands;
  • adds a server-issued connection and request identity;
  • forwards normalized commands to the game core; and
  • distributes personalized state and command results.

The Extension never supplies an authoritative player ID, AP value, position, target validity, damage result, or follower result.

4.4 Domain game core

The domain core is a framework-independent TypeScript module containing:

  • state types;
  • command validation and reducers;
  • phase and floor state machines;
  • movement and combat rules;
  • Goblin AI;
  • dungeon generation contracts;
  • deterministic random interfaces; and
  • structured domain events.

It must not import RPGJS, Twurple, browser APIs, or Twitch SDK types.

4.5 RPGJS adapter

The RPGJS adapter maps domain state and events to prototype rendering and any engine-specific map representation. It must not decide whether movement, attacks, spawning, resurrection, or floor completion are valid.

4.6 Views

The presentation is implemented as one shared UI system with two modes:

  • Broadcast mode: a read-only stream source that renders the three-section layout. Its upper-left area shows shared status and a visible controller legend, the upper-right area shows the dungeon, and the bottom area shows the running log.
  • Extension mode: an authenticated controller using the same visual language. It renders interactive controls and personalized player status, while consuming the same authoritative shared state.

This resolves the difference between a personalized controller and a shared broadcast: controls are visibly represented on the stream, but only an authenticated viewer's Extension controls are interactive.

5. Trust and Security Model

5.1 Stable identity

twitchUserId is the canonical player key. Display name, chat name, Extension connection ID, and browser storage are metadata only and cannot establish ownership.

5.2 Identity-binding sequence

  1. The Twitch adapter receives !spawn with a stable chat user ID.
  2. The backend verifies that user follows the configured broadcaster.
  3. The backend creates or rejects the character using twitchUserId.
  4. The Extension sends its Twitch-authenticated credential to the gateway.
  5. The gateway verifies the credential and extracts its stable user ID.
  6. Controls become enabled only when that ID equals the character owner ID.
  7. Every later control command uses the verified server session; client-sent ownership fields are ignored.

5.3 Secrets and configuration

Secrets must be supplied through environment variables or ignored local secret files. They must never be committed, logged, returned to clients, included in URLs, or embedded in the Extension bundle.

Configuration validation must fail startup with a specific error when required values are missing or inconsistent. Logs may name a missing variable but must not print its value.

5.4 Input safety

All external messages are schema-validated. Invalid, unauthorized, stale, or duplicate messages are rejected without changing game state. Display names and other viewer text are escaped before rendering or logging.

6. Authoritative Data Model

The following types are conceptual contracts. Exact TypeScript syntax may vary, but the fields and ownership boundaries must remain equivalent.

6.1 Run state

interface RunState {
  runId: string
  floorNumber: number
  floor: FloorState
  phase: PhaseState
  players: Map<TwitchUserId, PlayerState>
  goblin: GoblinState
  actionLog: ActionLogEntry[]
  nextEventSequence: number
}

6.2 Player state

interface PlayerState {
  twitchUserId: string
  displayName: string
  followerVerified: boolean
  characterCreated: boolean
  extensionBound: boolean
  partySlot: 1 | 2 | 3 | 4
  color: 'blue' | 'green' | 'red' | 'yellow'
  connectionState: 'connected' | 'disconnected'
  position: TilePosition
  hp: 0 | 1 | 2 | 3
  lifeState: 'alive' | 'dead'
  ap: 0 | 1 | 2
  guard: 0 | 1 | 2
  healAvailable: boolean
  participatingFloor: number
  diedOnFloor: number | null
  eligibleThisPhase: boolean
}

Connection state does not determine whether a living character participates in the turn. A disconnected living character remains in the dungeon, receives AP, and converts unused AP to AutoGuard.

6.3 Phase state

type PhaseState =
  | { kind: 'dormant' }
  | {
      kind: 'player'
      phaseId: string
      startedAt: number
      deadlineAt: number
      initialEligiblePlayerIds: string[]
      timerResetPlayerIds: string[]
    }
  | { kind: 'enemy'; phaseId: string }
  | { kind: 'transition'; reason: 'floor-advance' | 'admin-reset' }

The backend uses a monotonic clock for elapsed phase timing. Wall-clock values may be included for display but cannot determine acceptance ordering.

6.4 Floor state

interface FloorState {
  floorId: string
  width: number
  height: number
  tiles: TileKind[][]
  spawnTiles: TilePosition[]
  exitPosition: TilePosition
  goblinGuardPosition: TilePosition
  generationSeed: string
}

type TileKind = 'wall' | 'floor' | 'exit'

6.5 Goblin state

interface GoblinState {
  hp: 0 | 1 | 2
  position: TilePosition
  guardPosition: TilePosition
  mode: 'guarding' | 'pursuing' | 'returning' | 'dead'
  targetPlayerId: string | null
}

6.6 Structured action log

interface ActionLogEntry {
  sequence: number
  runId: string
  floorNumber: number
  phaseId: string | null
  type: string
  actorId: string | null
  targetId: string | null
  message: string
  occurredAt: string
}

The backend creates log messages from domain events. Clients do not submit log text. Sequence numbers establish canonical ordering.

7. Spatial and Interaction Rules

These rules make movement and combat deterministic while preserving the PRD's simple cooperative intent.

7.1 Grid

  • Movement is orthogonal: up, down, left, or right by one tile.
  • Diagonal movement and attacks are not permitted.
  • Walls are impassable.
  • The exit tile is traversable by living players.
  • A Goblin blocks player movement into its tile.
  • A player blocks Goblin movement into its tile.
  • Multiple players may share a tile. This prevents spawn-room crowding and cooperative path blocking from becoming an accidental MVP system.

7.2 Spawn placement

New characters spawn on the first spawn tile chosen by deterministic ordering. Players may share that tile. A late join never changes the current phase timer or the set of players required to finish the phase.

The authoritative player record is also the current-floor party roster. At most four records may exist. Spawn assigns the lowest open slot and its fixed color: Slot 1 Blue, Slot 2 Green, Slot 3 Red, and Slot 4 Yellow. Dead records remain in the roster and occupy their slots. A fifth spawn is rejected as PARTY_FULL without creating a character.

7.3 Attack range and targeting

  • Player and Goblin attacks target an orthogonally adjacent tile.
  • A player attack requires a selected living Goblin adjacent to the player.
  • A Goblin attack requires its living pursuit target to be adjacent.
  • Invalid or stale targets do not consume AP.
  • A valid attack consumes AP even when the hit roll misses.
  • A successful unblocked hit applies exactly 1 HP of damage.

7.4 Exit behavior

When a living player successfully moves onto the exit tile, the move consumes 1 AP and the floor transition starts immediately after that command resolves. No later queued command for the old floor is processed.

8. Command Model

8.1 Player commands

type PlayerCommand =
  | { type: 'move'; direction: 'up' | 'down' | 'left' | 'right' }
  | { type: 'attack'; targetId: string }
  | { type: 'heal-self' }
  | { type: 'pass' }

Each Extension request includes:

  • a client-generated requestId;
  • the current runId, floorId, and phaseId last observed by the client; and
  • exactly one command payload.

The gateway supplies the authenticated twitchUserId; it is not accepted from the payload.

8.2 Command ordering

The backend processes accepted messages serially in arrival order. Each message is fully validated against the state produced by all earlier messages before it is applied.

This rule resolves simultaneous actions:

  • the first accepted player to enter the exit ends the floor;
  • the first accepted attack may invalidate a later attack by killing the target;
  • duplicate request IDs return the original result without applying again; and
  • commands for an old run, floor, or phase are rejected as stale.

8.3 Player command validation

A command is accepted only when:

  • the session is authenticated and bound to the character owner;
  • the character exists, is alive, and is eligible to act;
  • the current phase is Player Phase;
  • the command reaches the authoritative queue before the deadline;
  • the player has at least 1 AP;
  • the supplied run, floor, and phase IDs match current state; and
  • command-specific spatial or resource rules pass.

Rejected commands consume no AP and return a machine-readable reason plus a safe display message.

8.4 Pass semantics

Pass costs exactly 1 AP. A player with 2 AP who wants to finish immediately must pass twice. There is no separate zero-cost end-turn command in the MVP.

9. Turn and State Machines

9.1 Dormant state

The game starts with a generated Floor 1 and no participating characters. While no active character exists:

  • phase is dormant;
  • no phase deadline exists;
  • no Enemy Phase can run;
  • the Goblin cannot act; and
  • the spawn banner is visible.

The first accepted !spawn creates a living character, removes the banner, and starts a fresh Player Phase.

Temporary disconnects do not create dormancy because disconnected living characters use AutoGuard. A total-party death does create dormancy while preserving the run, floor, Goblin, and dead character records.

9.2 Starting Player Phase

For every living participant present when the phase starts:

  1. set AP to 2;
  2. set Guard to 0;
  3. set eligibleThisPhase to true; and
  4. include the player in initialEligiblePlayerIds.

The initial deadline is five seconds after phase start. The phase begins with an empty timerResetPlayerIds list. After each player's first accepted command, the backend adds that Twitch user ID and moves deadlineAt to five seconds after the gateway arrival time. Later commands from that player do not move the deadline. Rejected, duplicate, disconnected, and AutoGuard events do not reset the timer.

Players who spawn or resurrect after the phase starts receive 0 AP, are not added to the phase completion set, and may act beginning with the next Player Phase.

9.3 Ending Player Phase

The phase ends when either:

  • every player in the initial eligible set has 0 AP or is dead; or
  • the authoritative deadline is reached.

At the transition, each living player's remaining AP becomes Guard one-for-one, then AP becomes 0. Dead players receive no Guard.

Commands already waiting in the serialized backend queue are accepted only if the gateway recorded their arrival before the deadline. A command arriving at or after the deadline is rejected even if the phase-transition task has not yet run.

9.4 Enemy Phase

The living Goblin receives 2 AP and repeatedly selects one deterministic action until it has no AP or no valid action.

After the Goblin finishes, Guard is discarded and a new Player Phase begins, unless the floor advanced or the party became dormant during the Enemy Phase.

9.5 AutoGuard

When a Goblin attack hits:

  1. if the target has Guard greater than 0, decrement Guard and apply no damage;
  2. otherwise, subtract 1 HP;
  3. if HP reaches 0, perform player death handling.

A miss never consumes Guard. Guard cannot carry into another round.

10. Player Lifecycle

10.1 !spawn

An accepted spawn requires:

  • a stable Twitch chat user ID;
  • verified follower status;
  • no existing character for that ID; and
  • no death marker for that ID on the current floor; and
  • one of four party slots to be open.

The backend creates one character at full HP with heal available. If the game is already active, the player has 0 AP until the next Player Phase.

Repeated !spawn attempts for an existing character return an informative chat response or log result and never create another character.

The roster is first come, first served for one floor. Twitch identity sessions remain outside the domain roster so clearing a party does not log viewers out.

10.2 Heal

A valid self-heal:

  • costs 1 AP;
  • requires the player to be alive;
  • requires healAvailable to be true;
  • restores HP to 3 even if already full; and
  • sets healAvailable to false.

10.3 Death

When HP reaches 0:

  • life state becomes dead;
  • AP and Guard become 0;
  • diedOnFloor becomes the current floor number;
  • controls are disabled; and
  • a death event is logged.

The dead character remains associated with the Twitch user ID and cannot be replaced with !spawn.

10.4 Channel Points resurrection

The Channel Points custom reward ID is required configuration and may change without changing domain code. Its cost is owned by Twitch. An accepted resurrection event must identify the configured reward, the same Twitch user ID as the dead character, and a unique redemption ID.

Resurrection:

  • restores the player to 3 HP;
  • places the player on the deterministic spawn tile;
  • clears the current-floor death marker;
  • preserves whether the floor heal was already used;
  • grants 0 AP during the current phase; and
  • enables action beginning with the next Player Phase.

Other-reward, duplicate, unknown-user, or non-dead-user events do not alter game state and must produce a diagnostic result without exposing secrets. Bits and Cheers are outside the MVP and are not normalized into domain messages.

10.5 Refresh and reconnect

On Extension refresh or reconnection, the gateway re-verifies identity and returns the existing character and current personalized snapshot. It never creates a character from browser state. Multiple connections for the same user may display state, but all share one player command budget and idempotency history.

11. Goblin Guard Behavior

11.1 Guarding

The Goblin begins at its guard position in guarding mode. It spends no AP and does not attack until it is attacked by a player.

11.2 Aggro and pursuit

The first valid player attack changes the Goblin to pursuing and sets that attacker as its target, whether the attack hits or misses. Later attacks do not retarget it while the target lives.

During each Enemy Phase, the Goblin:

  1. attacks its target if orthogonally adjacent;
  2. otherwise moves one tile along a shortest path to the target; and
  3. repeats until its 2 AP are spent or no path/action exists.

Shortest paths use breadth-first search. Equal choices use a fixed direction priority of up, left, right, then down so tests are deterministic.

11.3 Target death and return

If the pursued player dies, the Goblin enters returning, clears its target, and uses later Enemy Phases to take shortest-path movement toward its original guard position. It does not attack players while returning. On reaching the guard position, it becomes passive guarding again.

11.4 Goblin death

At 0 HP the Goblin becomes dead, is removed as a blocking entity, and takes no further actions. The exit remains usable whether the Goblin is alive or dead.

12. Floor Generation and Progression

12.1 Generator contract

For a supplied seed, the generator must return the same floor and guarantee:

  • exactly one rectangular spawn room;
  • exactly one rectangular exit room that does not overlap the spawn room;
  • one exit tile in the exit room;
  • one Goblin guard position adjacent to or near the exit;
  • an orthogonally navigable floor path between a spawn tile and the exit; and
  • enclosing impassable walls.

Variation must affect room sizes, room positions, or connecting corridor shape across seeds. Generation retries with a derived seed when validation fails and fails startup/transition visibly after a bounded retry count rather than loading an invalid map.

12.2 Floor advancement

When a living player enters the exit:

  1. enter transition state and reject remaining old-floor commands;
  2. increment the floor counter;
  3. generate and validate a new floor and Goblin;
  4. clear every current party record and release all four slots;
  5. discard all previous character HP, AP, Guard, heal, death, position, slot, and color state;
  6. log the new floor; and
  7. enter Dormant state for a new first-come, first-served party.

The previous Goblin and map are discarded. Existing authenticated Twitch sessions and sockets remain valid, but previous players have no slot reservation or priority.

12.3 Total-party defeat

After any death resolution, if participating characters exist and none is alive, the backend immediately:

  1. enters Dormant state with no deadline;
  2. preserves the current run ID, floor, and Goblin;
  3. preserves each dead character and current-floor death marker;
  4. stops Enemy Phase processing immediately; and
  5. logs the party defeat.

A valid Channel Points resurrection revives its owner and starts a fresh Player Phase from dormancy. If the defeated party claimed fewer than four slots, a newly eligible viewer may claim an open slot and resume play. A defeated full party requires resurrection. Initial dormancy has no participants, while defeated-party dormancy retains dead participant records.

13. Randomness

All random behavior goes through an injected pseudo-random interface. The backend is the sole random authority.

Randomness is used for:

  • player 65% attack hit rolls;
  • Goblin 50% attack hit rolls; and
  • procedural floor parameters.

Tests use fixed sequences or seeds. Production logs record the run/floor seed and outcome event, but clients never provide rolls or seeds used to resolve a command.

14. Backend Interfaces

Exact framework routing may change, but equivalent contracts are required.

14.1 Health

GET /health

Returns process readiness without credentials or game secrets. Readiness is false when required Twitch configuration or the game core failed to initialize.

14.2 Extension session

POST /api/extension/session

Accepts the Twitch Extension authentication credential, verifies it, and returns a short-lived Twungeon session plus personalized state. It does not create a character.

14.3 Commands

POST /api/commands

Accepts an authenticated player command envelope and returns:

{
  "requestId": "client-generated-id",
  "accepted": true,
  "eventSequence": 42,
  "reason": null
}

Rejected responses use a stable reason such as UNAUTHENTICATED, IDENTITY_NOT_BOUND, CHARACTER_DEAD, WRONG_PHASE, STALE_PHASE, NO_AP, INVALID_TARGET, BLOCKED_TILE, or DUPLICATE.

14.4 State stream

GET /ws upgrades to an authenticated WebSocket when personalized state is needed or a read-only broadcast connection otherwise. Messages include:

  • complete snapshot on connection;
  • ordered state/event updates;
  • current server time and phase deadline;
  • action-log entries; and
  • explicit resynchronization after sequence gaps.

Clients must replace local state with a complete snapshot after reconnect or a sequence gap. UI animation state is local; game state is not.

14.5 Internal Twitch messages

Twitch events normalize into idempotent internal messages:

type TwitchMessage =
  | {
      type: 'spawn-requested'
      externalEventId: string
      twitchUserId: string
      displayName: string
      broadcasterId: string
    }
  | {
      type: 'channel-point-resurrection-redeemed'
      externalEventId: string
      twitchUserId: string
      rewardId: string
    }

15. Presentation Requirements

15.1 Three-section layout

The default wide layout reserves:

  • a compact upper-left control/status area;
  • the largest upper-right area for the map; and
  • a full-width bottom area for the action log.

Temporary shapes, labels, and sprites are acceptable. Every required entity, wall, traversable path, and exit must remain distinguishable. Player markers, visible name labels, personalized status, and adapter render data use the current party-slot color consistently.

15.2 Player status

The personalized Extension status includes at least identity/binding status, party slot/color, HP, AP, Guard, heal availability, alive/dead status, current phase, and a clear disabled reason when controls are unavailable.

Desktop preserves the compact information-rich overlay. Twitch context platform data adds an extension-mobile presentation flag when available, and a narrow-viewport media query provides the fallback. Mobile keeps identity and color, HP, AP, and heal availability visible, reflows the controller instead of scaling the desktop layout, and uses touch targets of at least 44 pixels.

15.3 Action log

The view displays canonical entries by sequence, oldest to newest, with the latest entries visible. It must cover every event category required by PRD section 4.2. Reconnection restores recent history from the backend rather than starting an empty client-only log.

15.4 Dormant banner

When phase is dormant, both applicable views prominently display exactly:

Activate the Extension to spawn your character in the Twungeon!

Must be a follower to spawn.

16. Failure and Recovery Behavior

16.1 Twitch unavailable

  • Existing authenticated gameplay may continue if the backend remains healthy.
  • New spawn, new identity binding, and Channel Points redemptions fail closed when they cannot be verified.
  • The operator view and logs identify the unavailable integration.
  • Reconnect uses bounded exponential backoff and does not duplicate events.

16.2 Extension disconnected

The character remains participating. The current phase continues, unused AP becomes Guard, and reconnection restores authoritative state.

16.3 View disconnected

Game simulation continues. The view requests a full snapshot on return and does not replay commands.

16.4 Invalid floor

An invalid generated floor is never activated. Generation retries a bounded number of times. If all retries fail, phase progression stops with an operator- visible error rather than corrupting the active run.

16.5 Unexpected domain exception

The command is not partially applied. Command handling must compute and commit one atomic state transition. The error is correlated with request, run, floor, phase, and event sequence identifiers without logging secrets.

17. Observability

Structured operational logs must distinguish:

  • Twitch connection and subscription state;
  • authentication and identity-binding results;
  • follower checks;
  • accepted and rejected spawn attempts;
  • accepted and rejected controller commands;
  • phase transitions and timer expiry;
  • Channel Points redemption deduplication and resurrection results;
  • floor-generation seed and validation result;
  • reconnect and snapshot resynchronization; and
  • run reset or fatal errors.

Logs must use stable user IDs only where operationally required and must avoid credentials and full authentication payloads.

18. Proposed Source Layout

/
|-- apps/
|   |-- backend/              # Process composition, APIs, WebSocket, scheduler
|   |-- extension/            # Authenticated Twitch controller UI
|   `-- stream-view/          # Read-only broadcast layout
|-- packages/
|   |-- domain/               # Framework-independent game state and rules
|   |-- dungeon-generator/    # Seeded two-room generation and validation
|   |-- twitch-adapter/       # Twurple chat, follower, and Channel Points integration
|   |-- rpgjs-adapter/        # Mapping between domain state and RPGJS
|   |-- contracts/            # Shared validated API/event schemas
|   `-- ui/                   # Shared layout, status, controls, and log components
|-- tests/
|   |-- domain/               # Deterministic rule and state-machine tests
|   |-- integration/          # Backend, adapters, APIs, reconnect, deduplication
|   `-- e2e/                  # Multi-viewer and complete-loop scenarios
|-- docs/
|   |-- twitch-setup.md       # Clean-checkout Twitch setup and troubleshooting
|   `-- testing.md            # Local and live-channel verification procedure
|-- .env.example              # Names and descriptions, never secret values
|-- Twungeon_MVP_PRD_Current.md
`-- Twungeon_MVP_Technical_Specification.md

The exact monorepo tooling may be selected during project scaffolding. Component boundaries and dependency direction are mandatory even if folders are renamed.

19. Dependency Direction

Allowed dependency direction:

apps -> adapters/contracts -> domain
UI apps -> contracts/UI components
domain -> no framework or external-service package

The domain package exposes commands, state, and events. Adapters translate between those contracts and RPGJS, Twurple, HTTP, WebSocket, or browser APIs.

20. Verification Strategy

20.1 Domain unit tests

Deterministic tests must cover:

  • all accepted and rejected command conditions;
  • AP spending and early phase completion;
  • rolling five-second deadline and one reset per living player;
  • deadline boundary behavior;
  • AutoGuard conversion, hit blocking, misses, and reset;
  • player and Goblin hit probabilities through injected rolls;
  • heal availability and floor refresh;
  • death, spawn lockout, resurrection, advancement, and defeated-party dormancy;
  • Goblin guarding, aggro on hit or miss, pursuit, return, and death;
  • exit entry with a living Goblin;
  • initial versus defeated-party dormant conditions;
  • serial ordering and stale/duplicate command handling; and
  • deterministic floor generation and connectivity validation.

20.2 Integration tests

Integration tests use test doubles before live Twitch testing and cover:

  • chat event to follower check to spawn;
  • non-follower rejection;
  • chat-to-Extension identity match and mismatch;
  • two authenticated viewers controlling separate characters;
  • refresh/reconnect without duplication;
  • Twitch event and command deduplication;
  • Channel Points redemption mapped to the correct dead character;
  • backend snapshots, ordered events, and sequence-gap recovery; and
  • RPGJS rendering updates without domain authority leakage.

20.3 End-to-end scenarios

At minimum, the live test plan must demonstrate:

  1. clean setup and startup from repository documentation;
  2. dormant banner before any player joins;
  3. follower spawn and non-follower rejection;
  4. matching Extension control and mismatched identity rejection;
  5. two or more simultaneous viewer characters;
  6. movement, two attacks, heal, pass, timer expiry, and AutoGuard;
  7. Goblin aggro, pursuit, attack, target death, and return;
  8. Channel Points resurrection of the correct dead viewer;
  9. floor escape with the Goblin alive, party clearing, and next-floor reparty;
  10. total-party defeat, dormant transition, and resurrection recovery;
  11. refresh and temporary disconnect recovery; and
  12. repetition across multiple generated floors.

20.4 PRD success-criteria traceability

PRD criteria Primary verification
1-6 Clean-checkout, configuration, Twitch event, Extension, and layout E2E tests
7-13 Dormancy, spawn eligibility, duplicate prevention, and identity integration tests
14-19 Controller, phase, timer, AP, combat, heal, death, Guard, and log tests
20-22 Goblin AI, generator validation, and live-Goblin escape tests
23-29 Floor revival, death lockout, Channel Points, wipe, zero-player, late spawn, and reconnect tests
30-33 Multi-floor soak test, first-time-viewer test, setup troubleshooting review, and viewer-feedback session

The test implementation should reference individual PRD criterion numbers in test names or metadata so failures remain traceable to product acceptance.

21. Implementation Sequence

Implementation should proceed through vertical, independently verifiable slices:

  1. Scaffold the repository, shared contracts, configuration validation, and framework-independent domain package.
  2. Implement the deterministic floor generator and static stream rendering.
  3. Implement player/Goblin state, commands, turn phases, timer, and action log using local test drivers.
  4. Implement death, resurrection messages, floor advancement, defeat dormancy, dormancy, reconnect, and idempotency.
  5. Add the RPGJS adapter without moving rules out of the domain package.
  6. Add Twurple chat and follower verification for !spawn.
  7. Add Extension authentication, identity binding, controller commands, and personalized state.
  8. Add the Twitch Channel Points redemption adapter and configured reward rule.
  9. Complete clean-checkout Twitch setup and troubleshooting documentation.
  10. Run automated, live-channel, multi-viewer, and repeated-floor acceptance tests against all 33 PRD criteria.

22. Decisions Deferred to Integration Configuration

The following are intentionally configurable or version-dependent and do not change the domain design:

  • Channel Points resurrection reward ID and broadcaster-managed cost;
  • concrete Twitch developer application and Extension identifiers;
  • exact Twitch scopes, EventSub event names, and Twurple configuration required by the versions selected at implementation time;
  • callback, WebSocket, public endpoint, and HTTPS values for each environment;
  • UI art, typography, and final dimensions; and
  • retention limits for operational logs and the on-screen action-log window.

These values must be resolved and documented before live acceptance testing.

23. Definition of Ready for Implementation

Implementation may begin when:

  • the PRD and this specification are accepted as the product and technical baselines;
  • the eight resolved behavior choices below are acknowledged;
  • the initial repository/tooling scaffold is selected;
  • a Twitch development channel and required developer access are available; and
  • secrets can be supplied outside source control.

Resolved behavior choices:

  1. Players may share tiles; the Goblin and players may not share a tile.
  2. Movement and attacks are orthogonal, and attacks are adjacent-only.
  3. The backend serializes commands by authoritative arrival order.
  4. The arrival timestamp at the backend gateway decides timer-boundary commands.
  5. Run state survives client reconnects but not backend process restarts.
  6. Channel Points resurrection restores full HP at spawn and grants AP next Player Phase.
  7. Broadcast and Extension modes share presentation, but only the authenticated Extension is interactive.
  8. The backend domain core is the sole authority for every state transition.

Changes to these decisions must update this specification and any affected tests before or alongside implementation.