Non Battle Encounter Framework - UQcsse3200/2026-studio-3 GitHub Wiki

Overview

The Non-Battle Encounter Framework provides the shared lifecycle and integration boundary between Team 2's non-battle encounters and the Map / Game Flow systems.

The framework supports Team 2's two production non-battle encounter types:

  • Chance Encounters (EVENT)
  • Shop Encounters (SHOP)

Sprint 1 established the common encounter callback and basic map integration contract. Sprint 2 extended this framework into the production game flow so real map nodes can launch Chance or Shop encounters, preserve the current run state, and return to the same map after completion.

The main goal is to keep encounter-specific behaviour separate from map progression.

An encounter determines its own outcome, while RunState and MapGraph remain responsible for tracking the active node and applying map progression.


Encounter Lifecycle

The current production encounter lifecycle is:

flowchart TD
    A[Player selects an available MapNode] --> B[RunState records active node]
    B --> C{RoomType}
    C -->|EVENT| D[Open Chance Encounter]
    C -->|SHOP| E[Open Shop Encounter]

    D --> F[EncounterFlowController]
    E --> F

    F --> G[Player resolves or leaves encounter]
    G --> H[Encounter reports nodeId and success]
    H --> I[EncounterScreen validates active node]
    I --> J[RunState.completeEncounter]
    J --> K[MapGraph updates node progression]
    K --> L[Return to the same Map]
Loading

The Sprint 1 temporary demonstration flow:

Map → Chance → Shop → Map

is no longer used as the production encounter sequence.

Chance and Shop encounters are now launched independently:

Map → EVENT → Chance → Map

Map → SHOP → Shop → Map

At the integration boundary, the Map system supplies the selected node through the shared RunState.

The Encounter system then uses the node ID and room type to launch the correct encounter.


Map and Encounter Integration

Each map node is represented by a MapNode.

A node currently contains:

  • an Integer node ID;
  • a RoomType;
  • a NodeState;
  • its map height;
  • a set of connected map nodes.

The current room types are:

public enum RoomType {
    COMBAT,
    SHOP,
    EVENT,
    ELITE,
    START,
    FINAL
}

For Team 2's non-battle encounters:

Room Type Encounter
EVENT Chance Encounter
SHOP Shop Encounter

Other room types are handled by other game systems.

The production map-to-encounter information is therefore:

RunState
   │
   ├── activeNodeId
   │
   └── MapGraph
          │
          ↓
       MapNode
       ├── Integer nodeId
       └── RoomType
          │
          ↓
    EncounterScreen

The encounter does not directly control map progression.

Instead, completion is forwarded through the shared lifecycle so RunState and MapGraph remain responsible for updating map progress.


Production Integration Flow

EncounterScreen is the production screen used to host Team 2 non-battle encounters.

It retrieves the active map node from RunState and verifies that its room type is either EVENT or SHOP.

The production integration flow is:

sequenceDiagram
    participant M as Map
    participant R as RunState
    participant S as EncounterScreen
    participant A as EncounterGameArea
    participant F as EncounterFlowController
    participant E as Chance / Shop Encounter

    M->>R: enterEncounter(nodeId)
    M->>S: Open EncounterScreen
    S->>R: getActiveNodeId()
    S->>R: get active MapNode
    S->>A: create nodeId, roomType, callback
    A->>F: initialise encounter flow

    alt RoomType is EVENT
        F->>E: startChance(nodeId, encounter)
    else RoomType is SHOP
        F->>E: startShop(nodeId, shopService)
    end

    E->>F: onEncounterComplete(nodeId, success)
    F->>S: completion callback
    S->>R: completeEncounter(success)
    R->>M: update same MapGraph
Loading

This design preserves the same generated map while moving between Map and Encounter screens.


Encounter Completion

Encounter completion is represented by two values:

nodeId
success
  • nodeId identifies the map node whose encounter has finished.
  • success indicates whether the encounter completed successfully.

The current completion interface is:

public interface EncounterCallback {
    void onEncounterComplete(Integer nodeId, boolean success);
}

Map node IDs are now represented using Integer.

The completion path is:

sequenceDiagram
    participant E as Encounter
    participant F as EncounterFlowController
    participant S as EncounterScreen
    participant R as RunState
    participant M as MapGraph
    participant N as MapNode

    E->>F: onEncounterComplete(nodeId, success)
    F->>F: verify active node
    F->>F: clear active encounter
    F->>S: forward completion

    S->>S: verify callback node matches active node
    S->>R: completeEncounter(success)
    R->>M: onEncounterComplete(activeNodeId, success)

    alt success is true
        M->>N: setState(COMPLETED)
        M->>N: unlock connected LOCKED nodes
    else success is false
        M-->>N: no successful progression update
    end
Loading

If an unknown node ID reaches MapGraph, the graph returns without modifying progression.


Core Interfaces

EncounterCallback

EncounterCallback defines the shared completion contract between encounters and map progression.

public interface EncounterCallback {
    void onEncounterComplete(Integer nodeId, boolean success);
}

Parameters

Parameter Type Description
nodeId Integer ID of the map node whose encounter has finished
success boolean Whether the encounter completed successfully

An encounter only needs to report:

callback.onEncounterComplete(nodeId, success);

The encounter does not need to know how the map updates node states or unlocks future nodes.


EncounterFlowController

EncounterFlowController is the common lifecycle controller used by Chance and Shop encounters.

Its main responsibilities are:

  • allowing only one active encounter at a time;
  • storing the active map node ID;
  • storing the active encounter type;
  • starting Chance Encounter sessions;
  • starting Shop Encounters;
  • applying the shared Player / Card / Deck integration gateways;
  • forwarding completion for the active encounter;
  • clearing the active encounter before forwarding completion.

The supported encounter categories are:

public enum EncounterType {
    CHANCE,
    SHOP
}

Conceptually:

flowchart LR
    A[EVENT MapNode] --> B[EncounterFlowController]
    C[SHOP MapNode] --> B

    B -->|startChance| D[Chance Encounter]
    B -->|startShop| E[Shop Encounter]

    D --> F[Completion]
    E --> F

    F --> B
    B --> G[Map completion callback]
Loading

Exactly-Once Completion

The framework prevents the same active encounter from updating map progression more than once.

EncounterFlowController stores the currently active node ID.

When completion is received:

1. Check that the callback node matches the active node.
2. Clear the active encounter.
3. Forward completion to the owning screen.

Conceptually:

flowchart TD
    A[Completion callback received] --> B{Matches active node?}

    B -->|No| C[Ignore callback]
    B -->|Yes| D[Clear active encounter]

    D --> E[Forward completion]
    E --> F[Map progression updates]

    G[Duplicate callback] --> B
Loading

After the first valid callback clears the active encounter, a duplicate callback no longer matches an active encounter and is ignored.


EncounterScreen

EncounterScreen connects the shared run state to the Team 2 encounter framework.

Its main responsibilities are:

  • retrieving the currently active map node;
  • validating that the node is an EVENT or SHOP;
  • registering services required by the encounter;
  • retrieving the shared player state;
  • retrieving the shared PlayerDeck;
  • creating EncounterGameArea;
  • receiving the final completion callback;
  • returning to the Map or Defeat screen.

The screen obtains persistent run data before creating the encounter:

RunState
 ├── active MapNode
 ├── PlayerRunState
 └── PlayerDeck
        │
        ↓
 EncounterScreen
        │
        ↓
 EncounterGameArea

Before forwarding completion to RunState, the screen confirms that the callback node still matches the currently active node.

If an encounter outcome defeats the player, the game transitions to the defeat flow instead of returning to the Map.


EncounterGameArea

EncounterGameArea hosts the actual Chance or Shop encounter content.

For production encounters it receives:

nodeId
RoomType
EncounterCallback
PlayerRunState
PlayerDeck

The room type determines which encounter is launched:

flowchart TD
    A[EncounterGameArea] --> B{RoomType}

    B -->|EVENT| C[ChanceEncounterSelector]
    C --> D[Chance Encounter]

    B -->|SHOP| E[ShopService]
    E --> F[Shop Encounter]

    D --> G[EncounterFlowController]
    F --> G
Loading

This replaces the old Sprint 1 hard-coded sequence in which Chance and Shop were chained together.


RunState

RunState stores information that must survive screen changes during a run.

This is necessary because individual screens are disposed when the game moves between Map, Battle and Encounter screens.

Relevant run-scoped information includes:

RunState
 ├── MapGraph
 ├── activeNodeId
 ├── PlayerRunState
 └── PlayerDeck

Active Encounter

When the player enters an encounter, RunState retains the active node ID so that completion can later be applied to the same node.

runState.enterEncounter(nodeId);

When the encounter completes:

runState.completeEncounter(success);

RunState forwards the result to MapGraph and then clears the active encounter ID.

This preserves the same map instead of generating a new map after every encounter.


Shared Player State

Sprint 2 connects encounters to shared run-scoped player state.

The general lifecycle is:

flowchart LR
    A[RunState PlayerRunState] --> B[EncounterScreen]
    B --> C[EncounterGameArea]
    C --> D[Player Entity]
    D --> E[Chance / Shop modifies state]
    E --> F[Capture updated state]
    F --> A
Loading

This allows persistent player values changed during non-battle encounters to remain available after returning to the Map and when entering later screens.


Shared PlayerDeck

The player's persistent deck is also owned by RunState.

EncounterScreen obtains the run-scoped deck and passes it into EncounterGameArea.

RunState
   │
   └── PlayerDeck
          │
          ↓
  EncounterGameArea
       /        \
      /          \
 Chance          Shop
 Reward         Purchase
      \          /
       \        /
      same PlayerDeck

Chance card rewards and Shop purchases therefore modify the same persistent deck rather than creating separate encounter-only decks.


Chance Encounter Integration

EVENT nodes launch the Chance Encounter system.

The production flow is:

flowchart TD
    A[EVENT MapNode] --> B[EncounterScreen]
    B --> C[EncounterGameArea]
    C --> D[ChanceEncounterSelector]
    D --> E[Chance Encounter]
    E --> F[Player chooses outcome]
    F --> G[Apply supported outcome]
    G --> H[Encounter completion]
    H --> I[Return to Map]
Loading

Chance outcomes can interact with the shared integration layer, including:

  • health changes;
  • currency changes;
  • card rewards;
  • no-effect outcomes.

Card rewards use the shared card catalogue and persistent PlayerDeck.


Shop Encounter Integration

SHOP nodes launch the Shop Encounter system independently.

The production flow is:

flowchart TD
    A[SHOP MapNode] --> B[EncounterScreen]
    B --> C[EncounterGameArea]
    C --> D[ShopService]
    D --> E[Shop Encounter]
    E --> F[Player purchases or exits]
    F --> G[Shared Player / Card / Deck integration]
    G --> H[Encounter completion]
    H --> I[Return directly to Map]
Loading

Normal Shop exit is treated as successful completion of that Shop encounter.

The Shop does not automatically launch a Chance Encounter after exit.


MapGraph

MapGraph stores the map nodes and implements EncounterCallback.

public class MapGraph implements EncounterCallback

The graph stores nodes using integer IDs:

private Map<Integer, MapNode> nodes;

Its relevant responsibilities include:

  • storing and retrieving map nodes;
  • connecting nodes;
  • maintaining the current map position;
  • handling encounter completion;
  • updating node progression;
  • unlocking connected nodes.

Important Methods

addNode(MapNode node)

Adds a MapNode to the graph.

public void addNode(MapNode node)

getNode(Integer nodeId)

Returns the node associated with the supplied node ID.

public MapNode getNode(Integer nodeId)

connectNodes(Integer firstId, Integer secondId)

Creates a connection between two existing nodes.

public void connectNodes(Integer firstId, Integer secondId)

The current implementation creates the connection in both directions.

completeNode(Integer nodeId, boolean success)

Processes the result of a completed encounter.

public void completeNode(Integer nodeId, boolean success)

When success is true:

  1. the relevant node becomes COMPLETED;
  2. connected nodes still in the LOCKED state become AVAILABLE.

onEncounterComplete(Integer nodeId, boolean success)

Implementation of the shared EncounterCallback.

@Override
public void onEncounterComplete(Integer nodeId, boolean success) {
    completeNode(nodeId, success);
}

MapNode

MapNode represents an individual location on the generated map.

The current structure is:

MapNode
 ├── Integer nodeId
 ├── Integer height
 ├── RoomType roomType
 ├── NodeState state
 └── Set<MapNode> connections

A newly created node begins in the LOCKED state.

For Team 2:

EVENT → Chance Encounter
SHOP  → Shop Encounter

Node States

The map currently defines four node progression states:

public enum NodeState {
    LOCKED,
    AVAILABLE,
    CURRENT,
    COMPLETED
}
State Meaning
LOCKED The node cannot currently be selected
AVAILABLE The node can be selected by the player
CURRENT The node represents the player's current position
COMPLETED The encounter associated with the node has completed successfully

These states allow the Map system to control progression independently from the internal implementation of Chance or Shop encounters.


Responsibility Boundaries

The framework intentionally separates encounter lifecycle responsibilities from feature-specific behaviour.

Responsibility Owner
Map structure and map progression Map System
Active map and run state RunState / Map System
Node ID and room type Map System
Common encounter lifecycle Non-Battle Encounter Framework
Completion forwarding and exactly-once handling EncounterFlowController
Chance behaviour and outcomes Chance Encounter System
Shop inventory and purchasing Shop Encounter System
Encounter UI Encounter UI System
Card definitions and lookup Card Library System
Persistent player deck Deck System / shared RunState
Player health and money Player System / shared player state

For example, the framework does not decide which Chance Encounter is selected or which cards a Shop sells.

Those systems determine their own internal behaviour.

The framework is responsible for connecting them to the run lifecycle and ensuring completion returns to the correct map node.


Sprint 1 to Sprint 2 Changes

Sprint 1 established the initial framework:

  • common map and encounter lifecycle;
  • EncounterCallback;
  • basic MapGraph, MapNode, RoomType and NodeState structures;
  • completion reporting;
  • exactly-once completion protection;
  • temporary integration and demonstration flow.

Sprint 2 extended this into the production implementation:

flowchart LR
    A[Sprint 1 Framework] --> B[Real EVENT Map Integration]
    A --> C[Real SHOP Map Integration]

    B --> D[EncounterScreen]
    C --> D

    D --> E[Shared RunState]
    E --> F[Shared Player State]
    E --> G[Shared PlayerDeck]

    F --> H[Production Encounter Flow]
    G --> H
Loading

The main Sprint 2 changes include:

  • using the production Integer node ID contract;
  • launching Chance from real EVENT map nodes;
  • launching Shop from real SHOP map nodes;
  • removing the hard-coded production Chance → Shop sequence;
  • introducing the production EncounterScreen;
  • preserving the same generated map across screen transitions;
  • integrating run-scoped player state;
  • integrating the shared persistent PlayerDeck;
  • retaining exactly-once completion handling;
  • returning directly to Map after Chance completion or Shop exit.

Extending the Framework

New non-battle encounter implementations should continue to use the shared lifecycle instead of directly modifying map progression.

A new encounter should:

  1. receive or retain the relevant map node ID;
  2. implement its own encounter-specific behaviour;
  3. determine when the encounter has completed;
  4. report completion through the shared lifecycle;
  5. allow RunState and MapGraph to handle progression.

Conceptually:

flowchart TD
    A[New Encounter Type] --> B[Encounter-specific behaviour]
    B --> C[Outcome determined]
    C --> D[onEncounterComplete nodeId success]
    D --> E[Shared Encounter Lifecycle]
    E --> F[RunState]
    F --> G[MapGraph]
    G --> H[Map progression]
Loading

This keeps encounter implementations independent from the internal details of map progression.


Design Rationale

The framework uses callback-based integration to reduce coupling between encounter systems and the Map system.

Without the shared lifecycle:

Chance Encounter ──┐
                   ├── directly modify MapGraph internals
Shop Encounter ────┘

This would make each encounter implementation dependent on the internal Map implementation.

Instead:

flowchart TD
    A[Chance Encounter] --> C[EncounterFlowController]
    B[Shop Encounter] --> C

    C --> D[EncounterCallback]
    D --> E[EncounterScreen]
    E --> F[RunState]
    F --> G[MapGraph]
Loading

Each system therefore only needs to understand the shared integration boundary relevant to it.


Testing

The framework is tested around the shared lifecycle, map integration and persistence boundaries.

Important behaviours include:

  • valid map nodes can launch encounters;
  • EVENT launches Chance;
  • SHOP launches Shop;
  • the selected integer node ID is preserved throughout the encounter;
  • only one encounter can be active at a time;
  • successful encounter completion marks the relevant node as COMPLETED;
  • successful completion makes connected LOCKED nodes AVAILABLE;
  • unsuccessful completion does not perform successful map progression;
  • unknown node IDs do not crash MapGraph;
  • duplicate completion callbacks do not progress the map twice;
  • Chance completion returns to the same generated map;
  • Shop exit returns to the same generated map;
  • subsequent valid map nodes remain selectable;
  • shared player state survives encounter transitions;
  • Chance rewards and Shop purchases use the shared PlayerDeck.

The production flows manually verified during Sprint 2 include:

Map → EVENT → Chance → Map

Map → SHOP → Shop → Map

Project-wide Gradle tests and formatting checks should also be run after integration changes.


Related Systems

The Non-Battle Encounter Framework integrates with several systems across the project:

  • Map System — supplies nodes, stores map progression and owns the generated map.
  • RunState — preserves the current map, active encounter, player state and player deck across screen transitions.
  • Chance Encounter System — implements weighted events, choices and outcomes.
  • Shop Encounter System — implements shop inventory and transactions.
  • Encounter UI System — presents Chance and Shop interactions.
  • Player System — provides player statistics required by encounter outcomes.
  • Card Library / Deck Systems — provide valid card data and the persistent player deck.
  • Game Flow / Screens — switches between Map, Encounter and other game screens.

The framework acts as the shared boundary between these systems rather than implementing their internal behaviour.


Related Issues and Pull Requests

Sprint 1

  • Issue #24 — Common Encounter Framework and Integration Interface
  • Issue #68 — Encounter Resolution and Cross-System Integration

Sprint 2

  • Issue #127 — Non-Battle Encounter Expansion & Cross-System Integration
  • Issue #164 — Encounter Lifecycle and Map/Game Flow Integration
  • PR #213 — EVENT / SHOP Map Integration
  • PR #232 — Team 2 Sprint 2 integration into main

The final Sprint 2 Team 2 feature has been integrated into main.

⚠️ **GitHub.com Fallback** ⚠️