Non Battle Encounter Framework - UQcsse3200/2026-studio-3 GitHub Wiki
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.
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]
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.
Each map node is represented by a MapNode.
A node currently contains:
- an
Integernode 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.
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
This design preserves the same generated map while moving between Map and Encounter screens.
Encounter completion is represented by two values:
nodeId
success
-
nodeIdidentifies the map node whose encounter has finished. -
successindicates 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
If an unknown node ID reaches MapGraph, the graph returns without modifying progression.
EncounterCallback defines the shared completion contract between encounters and map progression.
public interface EncounterCallback {
void onEncounterComplete(Integer nodeId, boolean success);
}| 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 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]
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
After the first valid callback clears the active encounter, a duplicate callback no longer matches an active encounter and is ignored.
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
EVENTorSHOP; - 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 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
This replaces the old Sprint 1 hard-coded sequence in which Chance and Shop were chained together.
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
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.
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
This allows persistent player values changed during non-battle encounters to remain available after returning to the Map and when entering later screens.
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.
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]
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 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]
Normal Shop exit is treated as successful completion of that Shop encounter.
The Shop does not automatically launch a Chance Encounter after exit.
MapGraph stores the map nodes and implements EncounterCallback.
public class MapGraph implements EncounterCallbackThe 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.
Adds a MapNode to the graph.
public void addNode(MapNode node)Returns the node associated with the supplied node ID.
public MapNode getNode(Integer nodeId)Creates a connection between two existing nodes.
public void connectNodes(Integer firstId, Integer secondId)The current implementation creates the connection in both directions.
Processes the result of a completed encounter.
public void completeNode(Integer nodeId, boolean success)When success is true:
- the relevant node becomes
COMPLETED; - connected nodes still in the
LOCKEDstate becomeAVAILABLE.
Implementation of the shared EncounterCallback.
@Override
public void onEncounterComplete(Integer nodeId, boolean success) {
completeNode(nodeId, success);
}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
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.
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 established the initial framework:
- common map and encounter lifecycle;
-
EncounterCallback; - basic
MapGraph,MapNode,RoomTypeandNodeStatestructures; - 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
The main Sprint 2 changes include:
- using the production
Integernode ID contract; - launching Chance from real
EVENTmap nodes; - launching Shop from real
SHOPmap nodes; - removing the hard-coded production
Chance → Shopsequence; - 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.
New non-battle encounter implementations should continue to use the shared lifecycle instead of directly modifying map progression.
A new encounter should:
- receive or retain the relevant map node ID;
- implement its own encounter-specific behaviour;
- determine when the encounter has completed;
- report completion through the shared lifecycle;
- allow
RunStateandMapGraphto 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]
This keeps encounter implementations independent from the internal details of map progression.
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]
Each system therefore only needs to understand the shared integration boundary relevant to it.
The framework is tested around the shared lifecycle, map integration and persistence boundaries.
Important behaviours include:
- valid map nodes can launch encounters;
-
EVENTlaunches Chance; -
SHOPlaunches 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
LOCKEDnodesAVAILABLE; - 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.
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.
- Issue #24 — Common Encounter Framework and Integration Interface
- Issue #68 — Encounter Resolution and Cross-System Integration
- 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.