Actions, Events and Outcomes

2025-09-21

Terminology

Actions

Actions are inputs given by Actors.

Common fields includes

actor
An Actor entity that performs this Action
cost
the time cost of this Action
target
A location or an entity that is the victim of this Action

The Action system dispatch the Action into one or more Events.

Actor

Actors includes the player, mobs, and other "non-creatures" like traps with timers.

Each Actor has a timestamp. When picking the next Actor, the turn system pick the Actor with the earliest timestamp.

When an Action is taken, the time cost of this Action is added to the timestamp of the Actor.

Events

Events are things that happen in the world.

Each event has a source and a destination, and both of them are entities.

They are not final results however: When a change is guaranteed to happen, and the numbers in the change is fixed, an outcome is generated.

Outcomes

Outcomes are the results of Events. They are guaranteed observable modifications to the world, and they are for "publishing" only.

They are thus should be potentially known by the UI: each of them should be able to produce

  • a message log, describing what happens with details
  • an animation, demonstrating quickly what happens
  • a traversable log, as each outcome is guaranteed to be applicable and revertible

Phases

Pick an Actor

The turn system pick an Actor with the earliest timestamp.

  • If it's the player, the game waits for the player's input.
  • Otherwise, the mob's AI gives the next action to do.

Dispatch the Action

Based on the given action, the system generates Events that specifies a source entity, a destination entity and related effects.

Handle Events

Until there is no event left, the system iterates over all events, applies each of them, and possibly generates new events.

When an event is observable (i.e. it's UI-wise meaningful), it's not removed immediately, but changed to an Outcome.

Eventually, the system will enter a peaceful state where there is no event but outcomes.

Publish Outcomes

From the above process, we collects observable Events as Outcomes. The system can then publish them to the UI.

Example: Bump with firebrand sword

Pick Actor

Player as an Actor has the earliest timestamp, so the turn system picked the player to give the next Action.

The player inputs with "Bump" targeting a tile next to the player character, where there is a goblin on that tile.

Bump
Action(actor=player, target=(0, 1), cost=100)

Dispatch Action

First, since it's an action, we generate a event to update the timestamp:

Event(src=player, dst=player)
TimeEffect(cost=100)

Also, the actual Event for the movement:

Event(src=player, dst=player)
MoveEffect(delta=(0, 1))

Handle Events

iteration 1

The event with the time effect is handled. Since the player is not speed up or slow down, it is applied without change:

Event(src=player, dst=player)
TimeUpdate(delta=100)

The event with the movement effect is handled. The system figure out that there is a hostile mob (goblin) in the tile, so it is translated into an attack Event:

Event(src=player, dst=player)
AttackEffect(target=(0, 1))

iteration 2

Time update is something meaningful, thus it's saved as an Outcome:

Outcome(dst=player)
TimeUpdate(delta=100)

There is only one mob in the given tile, so the destination is determined to be the goblin.

Then, the system figures out that the player is holding a firebrand sword. It generates these raw damages:

Event(src=player, dst=goblin)
MeleeDamage(damage=10)
FireDamage(damage=6)

iteration 3

Now the system takes goblin's defense into account. The results are

Event(src=player, dst=goblin)
HpModify(delta=-7, type=melee)

Event(src=player, dst=goblin)
HpModify(delta=-5, type=fire)

iteration 4

The system applies these HpModify s. Clearly the goblin is dead:

Event(src=player, dst=goblin)
Kill

Also, HpModify is observable events, so they are saved as Outcomes:

Outcome(src=player, dst=goblin)
HpModify(delta=-7, type=melee)

Outcome(src=player, dst=goblin)
HpModify(delta=-5, type=fire)

iteration 5

Kill in an RPG world means gaining experience too:

Event(src=goblin, dst=player)
GainExperience(10)

And it's an outcome of course:

Outcome(src=player, dst=goblin)
Kill

iteration 6

Gaining experience is also observable, so it's becoming an Outcome:

Outcome(src=goblin, dst=player)
GainExperience(10)

Publish Outcomes

Now we have these outcomes:

Outcome(dst=player)
TimeUpdate(delta=100)

Outcome(src=player, dst=goblin)
HpModify(delta=-7, type=melee)

Outcome(src=player, dst=goblin)
HpModify(delta=-5, type=fire)

Outcome(src=player, dst=goblin)
Kill

Outcome(src=goblin, dst=player)
GainExperience(10)

The system finally publish these outcomes to the UI. As messages, for example, it can be

The player did 7 melee damage to the goblin.  The player did 5 fire damage to the goblin.  The goblin was killed by the player.  The player gained 10 exp.

As a bonus, if at this state the only Outcome is TimeUpdate, the system shall be able to revert it and cancel the Action. For example, if the player mistypes and hits a wall.

Reference