Skip to content

Scenario Stats & Completion ​

Record player choices as stats, show them on an end screen, and mark the scenario complete when the narrative finishes.

Overview ​

  1. Define labelable stats in src/stats.json
  2. Emit values from Ink with #stat:id:value when choices resolve
  3. End the scenario with #completeScenario

The player app queues stats offline, syncs them when signed in, and opens a completion screen that shows the player's recorded choices using your labels from stats.json.

stats.json ​

Place this file next to scenario.json in your scenario source (usually src/stats.json). It is optional — without it, the end screen still works but falls back to raw ids/values.

json
{
  "stats": [
    {
      "id": "who_you_told",
      "label": "You told the group",
      "buckets": [
        {
          "value": "found_phone",
          "label": "You found Sarah's phone"
        },
        {
          "value": "call_police",
          "label": "You wanted to call the police"
        },
        {
          "value": "ask_location",
          "label": "You asked where Sarah was last seen"
        }
      ]
    },
    {
      "id": "group_plan",
      "label": "The group decided to",
      "buckets": [
        {
          "value": "search_together",
          "label": "Meet up and search at Club Neon"
        },
        {
          "value": "discuss_concerns",
          "label": "Talk through Sarah's recent worries"
        },
        {
          "value": "contact_friends",
          "label": "Contact her work friends immediately"
        }
      ]
    }
  ]
}

Fields ​

FieldRequiredDescription
stats[].idYesStable id used in #stat:id:value
stats[].labelYesHuman-readable question / category on the end screen
stats[].bucketsNoAllowed values and display labels
buckets[].valueYes (if buckets)Value string passed to #stat:
buckets[].labelYes (if buckets)Text shown for that value

Keep ids stable

Once players have recorded a stat_id / value, changing those strings breaks label lookup for existing sessions. Prefer updating label text instead of renaming ids.

Ink tags ​

#stat:id:value ​

Records a play-through choice.

ink
== ending_search_together ==
{
  - who_you_told == "found_phone":
    # stat:who_you_told:found_phone
    -> emit_search_together
  - who_you_told == "ask_location":
    # stat:who_you_told:ask_location
    -> emit_search_together
}

== emit_search_together ==
# stat:group_plan:search_together
-> finish_scenario

Rules:

  • Format is exactly stat:<id>:<value> (two colons separating three parts)
  • id and value must be non-empty
  • Prefer values that match a bucket in stats.json
  • Emitting the same id again overwrites the previous value for that session (latest wins on the end screen)
  • Safe to put on its own line with no message text

#completeScenario ​

Marks the scenario finished: flushes queued reporting events, completes the remote play session when available, and opens the completion UI.

ink
== finish_scenario ==
Thanks for playing. #system #delay:1s
# completeScenario
-> END

When to use: Once, at the true ending knot(s). Call it after you have emitted any final #stat: tags so they appear on the end screen.

Don't skip END

Still divert to -> END (or otherwise stop the conversation) after #completeScenario. The tag handles reporting/UI; it does not replace Ink flow control.

Legacy aliases

Older packs may still use #completeThread or #completeStory. The player app still accepts those tags.

Suggested pattern ​

  1. Track decisions with Ink variables as the player chooses
  2. At each ending branch, emit one or more #stat: tags that summarize the path
  3. Divert to a shared finish_scenario knot that shows a short farewell and #completeScenario
ink
VAR who_you_told = ""

== player_found_phone_response ==
~ who_you_told = "found_phone"
// ... dialogue ...
-> group_follow_up_choices

== ending_call_police ==
# stat:who_you_told:call_police
-> finish_scenario

== finish_scenario ==
Thanks for playing the sample scenario. #system #delay:1s
# completeScenario
-> END

What players see ​

  • A scenario complete banner / screen
  • Labeled stats from stats.json for values recorded this session
  • Options to continue exploring the phone OS or leave the scenario (player app behavior)

Stats sync to the server when the player is signed in and play-session reporting is enabled. Offline play still records locally so the end screen works without a network.

Checklist ​

  • [ ] src/stats.json lists every id / value you emit
  • [ ] Every ending path emits the stats you care about
  • [ ] Exactly one #completeScenario per finished playthrough (shared finish knot is ideal)
  • [ ] Rebuild and republish after changing stats.json or Ink tags

Released under the MIT License.