BranchSimulation.h header
#include <ew/app/BranchSimulation.h>
Namespace ew::app
SimulationResult struct
struct ew::app::SimulationResult
What a simulated walk found.
Members
std::vector<ew::core::foundation::BranchNodeId> ew::app::branching::SimulationResult::reachable
Every node a reader can arrive at along some path, given the variables.
std::vector<ew::core::foundation::BranchNodeId> ew::app::branching::SimulationResult::unreachableInPlay
Nodes the graph connects but no playthrough can arrive at, because every path to them is gated by a condition that is never true.
This is the finding the structural validator cannot make. J8-c asks whether the graph connects a node; this asks whether the STORY ever gets there. A scene can be perfectly well-formed and still contain a branch no reader will ever see.
bool ew::app::branching::SimulationResult::truncated = false
Whether the walk stopped at its own limit rather than because it had run out of story.
A truncated walk UNDER-reports reachability, so unreachableInPlay may then name a node a reader could actually get to. That makes this flag part of the result rather than a footnote: a caller showing "no playthrough reaches this node" while the walk gave up would be stating a finding it did not establish, and the author would go looking for a gate that is not there.
It can only happen once effects exist. Reachability became a property of the PATH rather than of the graph, so the walk explores (node, state) pairs – and a Number incremented around a loop produces a new state every time round, which no graph-shaped bound can catch.
Types
using ew::app::branching::VariableState = std::map<ew::core::foundation::VariableId, ew::core::branching::VariableValue>
The variables a playthrough is carrying, by id.
Functions
VariableState ew::app::branching::applyEffects(const ew::core::branching::BranchNode &node, VariableState state, const ew::core::project::Project &project)
state after node's effects are applied, in the order they are written.
Public because a live playthrough has to change the story's state EXACTLY as the analysis does. Two implementations of "what this node does to the world" would disagree the first time one learned about a new operation, and the symptom would be an author watching a variable in the player and being told something different by the validator about the same node.
An effect whose operation cannot apply to its variable's type is left alone rather than guessed at; BranchProblemKind::EffectTypeMismatch is what says so.
bool ew::app::branching::conditionHolds(const ew::core::branching::BranchNode &node, const VariableState &state, const ew::core::project::Project &project)
Whether node's condition holds in state.
A node that is not a Condition is true: it does not gate anything, and answering "false" would make every ordinary node stop the walk.
A condition on a variable the state does not carry is false, not an error. The validator already reports a missing variable as a defect; making the simulation refuse to run as well would tell the author the same thing twice while denying them the walk that shows what else is wrong. An expression that does not evaluate is false for exactly the same reason.
project supplies the story's declarations – an expression names a variable by NAME where the single-comparison form names it by id – and the world an expression may also name.
bool ew::app::branching::expressionHolds(const QString &expression, const VariableState &state, const ew::core::project::Project &project)
Whether expression is true in state. An empty expression is true – nothing gates it.
Public because a link's condition and a node's are the same language read through the same resolver, and two evaluators would disagree the first time one learned a new operator. The world travels with it, so gold >= @Horse.price is a condition an author can write.
An expression that cannot be evaluated is FALSE. The validator reports it as a defect; refusing to walk as well would tell the author the same thing twice while denying them the rest of the report.
VariableState ew::app::branching::initialState(const ew::core::project::Project &project)
The state a scene begins in: every variable at its declared initial value.
Both scopes start the same way; the difference between Global and Scene is what happens at the NEXT scene, which is the caller's business rather than one walk's.
SimulationResult ew::app::branching::simulateScene(const ew::core::branching::BranchScene &scene, const ew::core::project::Project &project)
Walks scene from its start, carrying the variables project declares, and reports what a reader can and cannot arrive at.
Every choice is explored, not one. A Choice is the reader's decision, and the author wants to know about the branch behind the option they did not take – so the walk follows all of them. A Condition is the story's decision, so only the branch its state permits is followed; that is exactly what makes a never-taken branch visible.
A node's EFFECTS are applied when the reader arrives, before its outgoing links are chosen. So a Condition that also carries an effect tests the state it arrived with, and the node after it sees the change – and a flag set by one branch is visible to a condition further down that branch and not to the others.
That makes reachability a property of the PATH, not of the graph, so the walk visits (node, state) pairs rather than nodes: the same node reached two ways can carry different state, and a condition below it can hold on one arrival and not the other. Visiting nodes alone would silently take whichever state arrived first and call the other branch unreachable.
The walk is bounded – a scene that loops (a hub the reader returns to) is normal and must terminate rather than spin – but the bound is now on pairs, because a Number incremented around a loop makes a new state each time. When it stops at the bound it says so in SimulationResult::truncated rather than presenting a partial walk as a complete one.