Guide
Stories
Stories are the life events that land in your inbox: a cousin's wedding, a wrist that won't stop aching, a documentary crew, a letter from HMRC. Each one has a title, a body and up to four choices. A choice can change your stats, roll a random outcome, change the world (withdraw you from an event, start an injury, bring in a sponsor offer…) and schedule another story weeks later.
Stories live in data/events/narrative/*.json. Each file is a JSON array of stories. The core game splits them by category (family.json, health.json, travel.json, money.json, …), but any file name works. To change a core story, reuse its id or patch it ("target": "narrative:core.narrative.family-occasion"). The editor schema is the stories reference.
Every field on this page except id, trigger, title, body and choices is optional. A story written before these features existed works exactly as before.
A story#
{
"id": "acme.story.pub-quiz",
"trigger": "weekTick",
"condition": { "chance": 0.02, "tierMin": 4 },
"title": "Pub Quiz Night",
"body": "The Feathers need a sixth for the quiz team. The darts round is worth double.",
"choices": [
{
"id": "go",
"label": "Join the team",
"effects": { "morale": 4, "fatigue": 0.02 },
"outcome": "You get every darts question right and none of the geography."
},
{ "id": "no", "label": "Stay in and practise", "effects": { "form": 0.02 } }
],
"cooldownWeeks": 30
}| Field | Meaning |
|---|---|
trigger | When it's checked (below) |
condition | What must be true for it to fire, plus chance (below) |
title, body | The message. Both take text variables |
image | Optional picture above the story in the inbox, e.g. "assets/stories/derby.png" (about 2:1; see Images) |
choices | 1–4 choices (below). The last choice is taken for you if the story is left for two weeks, so make it the safe one |
weight | When several stories are ready at once, one is picked by weight. Default 1 |
once | Only ever fires once per career |
cooldownWeeks | Weeks before it can fire again. Default 26. Weekly stories never repeat within 8 weeks, whatever this says |
Triggers#
| Trigger | Checked | Extra facts for the condition |
|---|---|---|
weekTick | At the end of every week | |
matchEnd | After each of your matches | won, tv, major, againstRival; {opponent}, {event} |
eventEnd | When your event finishes | positionMax, prizeMin, won, tv, major; {event} |
exhibitionEnd | After an exhibition night | won, exhibitionKind; {exhibition}, {town} |
seasonStart | 1 January | |
seasonEnd | After the season review | |
followUp | Never on its own: only another story's followUp | See follow-ups |
At most one story fires per check. A follow-up that's due takes that week's weekTick slot. A mod's script can also fire a story at any check, whatever its trigger and condition.
Conditions#
All listed conditions must hold. Stat ranges use the same scales as the game: confidence and morale 0–100, form −1…1, fatigue 0…1, public image −100…100.
| Condition | True when |
|---|---|
chance | 0–1: the probability per check once everything else holds. No chance = always (milestones) |
tierMin, tierMax | Your tier is in range. Careful: tier 1 is the top, so tierMin: 4 means tier 4 or 5 (pub and county level) |
tourCard | You do (true) or don't (false) hold a Tour Card |
rankMax | Order of Merit rank at least this good |
followersMin, followersMax | Follower count |
confidenceMin/Max, moraleMin/Max | Confidence, morale |
formMin, formMax | Form |
fatigueMin, fatigueMax | Fatigue |
cashMin, cashMax | Bank balance. Use these to keep costs sensible for the player's means |
imageMin, imageMax | Public image |
ageMin, ageMax | Age |
nationality | Your nationality is one of these, e.g. ["ENG", "WAL"] |
livesIn, notLivesIn | Where you live (not your nationality) is, or is not, one of these countries, e.g. ["ENG"]. A story that only makes sense in one country says so here |
scene | You live in one of these darts scenes (scene record ids, e.g. ["core.scene.eng"]; a country with no scene of its own is in the fallback record's) |
gender | "m" or "f" |
months | Only in these months, e.g. [11, 12] |
dateFrom, dateTo | Only between these days of the year, "MM-DD", both included: a story that names a day ("10-30" to "11-05" for Fireworks Night) comes in the week up to it, as stories are checked once a week. A dateFrom later in the year than dateTo runs over the new year; either alone means from 1 January or to 31 December |
seasonPhase | "early" (Jan–Apr), "mid" (May–Aug) or "late" (Sep–Dec) |
hasEventWithinDays | You're entered in (or automatically in) an event starting within this many days. Use it for "the week before a big event" stories, and whenever the text mentions {nextEvent} |
nextTrip | (With hasEventWithinDays.) The journey to that event is one of these travel steps, by id (the travel settings' steps, for example ["country", "neighbour", "far"]): a long drive in the country, not a flight abroad or a pub round the corner |
trip | (matchEnd, eventEnd.) The journey to the match or event just played was one of these travel steps, by id: jet lag after a flight, not after the local |
seasonMonths | Months of the weather's year as a northern year has them ([12, 1, 2] for winter): where the player's scene has seasonOffset 6 (the southern hemisphere) the story comes in June to August. months stays the calendar's (Christmas, the tax return) |
noEventWithinDays | Nothing on your calendar for this many days (a quiet spell) |
hasCoach, hasWalkon | You have a darts coach / a walk-on track |
dayJob | true: only while you keep a day job; false: only once you have gone full-time |
hasStaff | You employ someone in this staff role: a role id ("core.staff.physio") or its short name ("physio"). A list means any of them |
staffQualityMin | A staff member rated at least this many stars (1–5): in the hasStaff role if given, otherwise anyone |
staffQualityMax | A staff member rated at most this many stars: with "hasStaff": "agent", a bad agent |
negotiated | Something happened in a sponsor negotiation in the last 4 weeks: walkedAway, biddingWar, agentDeal (your agent closed one) or signed (you did) |
hasSponsor | Any active sponsorship |
sponsorCategory | An active sponsor in this category (darts, apparel, drinks, boards, energy, travel, tech, local) |
hasRival | A rivalry of real intensity with someone ({rival} is then filled) |
rivalEdge | Who has the edge in your record against the story's rival: "yours" (you lead by rivalry.edgeLead wins, 1 by default), "theirs" or "level". The rival is the opponent just played in a matchEnd story against a rival, else your hottest rival |
rivalryMinMeetings | You and the story's rival have met at least this many times |
rivalLastResult | "won" or "lost": how your last meeting with the story's rival went (never met: neither holds) |
flag, notFlag | A story flag (or a list: all of them / none of them) is set / not set. Flags are set by a setFlag action; the name takes text variables, so "flag": "fuel:{rival}" is one flag per rival. See Story flags |
injured | You're carrying an injury |
minTitles, maxTitles | Career titles |
lastResultPositionMax | Your most recent event: finishing position at most this (0 = won, 1 = final, 2 = semi…) |
winningStreakMin, losingStreakMin | Consecutive match wins / losses |
won, tv, major, againstRival | matchEnd / eventEnd only: the result that just happened |
positionMax | eventEnd only: finishing position at most this (0 = the winner, 1 = the final). Never holds after a qualifier: coming through one is not a final |
prizeMin | eventEnd only: the prize money the event paid you is at least this |
exhibitionKind | exhibitionEnd only: the night was one of these kinds, e.g. ["holidayPark", "cruise"] (see exhibitions) |
hasBooking | You have (true) or don't have (false) an exhibition booked in the next 4 weeks |
loadMin, loadMax | Your workload (0–100): 60 tired, 75 overloaded, 90 burnt out. Use it for travel and burnout stories |
squad | Your standing in a squads league you play for (below). You must be in one of its squads |
Your standing in a squads league (squad)#
For stories about the county side or a youth side. tour picks the league: the squads events of that tour where you live (core.tour.county is the county league, core.tour.junior the Shires youth leagues; a tour that runs one league an age group gives the one that would take you). Every other field is optional and every listed check must hold.
| Field | Meaning |
|---|---|
capsMin, capsMax | League days that picked you (capsMax: 0: never picked yet) |
seasonsMin | League seasons in which you were picked |
rankMax | Your place on your squad's own average table this season (1 = its best); last season's until you have played this one |
averageMin | Your three-dart average on that table, a list with one bar a division, top division first; the last bar holds below |
firstSide | Your last pick was for your squad's first side (its A team) or not |
picked | The last day your squad played picked you (true) or left you out (false) |
dropped | That day left you out after the one before picked you |
"condition": {
"chance": 0.08,
"months": [9, 10, 11, 1, 2],
"squad": { "tour": "core.tour.county", "capsMin": 6, "seasonsMin": 2, "rankMax": 4, "picked": true, "averageMin": [80, 75, 70] }
}In a county league the scene word {scene:county} is the county you play for, so a story can name it.
Choices#
| Field | Meaning |
|---|---|
id | Unique within the story |
label | The button text (text variables allowed) |
effects | Always applied (below) |
actions | Always applied: world effects |
outcome | Shown after choosing |
outcomes | 1–6 random outcomes; one is rolled on top of effects |
followUp | A story to fire later (follow-ups) |
The inbox shows each choice's guaranteed effects as chips ("Costs £90", "Morale ↑", "Miss next event", "Sponsor interest"). A choice you can't pay for is greyed out, and a story where no choice is affordable never holds up time: it settles on the cheapest choice. Only the choice's own effects.money counts towards affordability, not money in its outcomes.
Effects#
Numbers are changes, applied once.
| Effect | Range | Notes |
|---|---|---|
money | −50,000…50,000 | Keep it in proportion to the tier the story can reach |
confidence, morale | −40…40 | Small is plenty: 2–8 |
form | −0.5…0.5 | 0.02–0.06 is noticeable |
fatigue | −0.5…0.5 | + is more tired |
followers | ±100,000 | |
reputation | −20…20 | |
image | −20…20 | Public image; sponsors notice it |
pressure | −0.3…0.3 | Media pressure carried into your next matches (it fades each week) |
load | −50…50 | Workload: + a draining trip, − a proper rest. 3–10 is a lot |
| any attribute | −5…5 | scoring, finishing, checkoutKnowledge, consistency, tempoControl, bullAccuracy, composure, resilience, crowdHandling, concentration, stamina, fitness, eyesight. Permanent, so use 1–2 |
Random outcomes#
Give a choice outcomes and one is rolled when it's picked, by weight, among the outcomes whose condition holds (their chance is ignored). The roll is seeded from the career, so the same save always rolls the same way. The choice's own effects, actions and outcome still apply; the rolled outcome's text is shown after them.
{
"id": "play-through",
"label": "Strap it and play through",
"effects": { "confidence": 2 },
"outcome": "Tape, gritted teeth and a stubborn streak.",
"outcomes": [
{ "weight": 3, "text": "It settles down by the weekend." },
{
"weight": 2,
"hint": "Could make it worse",
"text": "By Friday you can't hold a mug. You've made it worse.",
"actions": [
{ "type": "injury", "label": "Wrist strain", "weeks": [2, 3], "severity": "moderate" }
],
"followUp": { "story": "acme.story.wrist-setback", "afterWeeks": 1 }
}
]
}| Outcome field | Meaning |
|---|---|
weight | Relative likelihood. Default 1; 0 never happens |
condition | Only in the running if this holds when the choice is made, e.g. { "formMin": 0 } for a result that needs you in form |
effects, actions | As on a choice |
text | Shown after the choice's outcome |
followUp | As on a choice |
hint | Up to 40 characters, shown as a neutral chip on the choice before you pick it, e.g. "Might meet a sponsor" |
A choice with two or more outcomes gets a "Could go either way" chip.
Actions (world effects)#
actions is a list. Each action has a type:
| Type | Fields | What happens |
|---|---|---|
withdraw | refund (optional, false: a missed registration) | You pull out of the next event you're playing (entered or automatic). Entry fee refunded, unless refund is false: then you are out and the fee stays with the promoter (a missed registration; no fine). {event} in the rest of the text becomes that event. Pair it with hasEventWithinDays |
sitOut | weeks, reason? | You miss every event starting in the next weeks (entries refunded) |
injury | weeks, label? ("Injury"), severity? (minor), sitOut?, part?, injury? | An injury through the medical room: body part, treatment, healing week by week. sitOut defaults to true for serious only |
modifier | label, weeks, accuracy?, training?, fatigueFloor?, confidenceFloor?, composure? | A timed boost or setback (below) |
sponsorOffer | category?, sponsor? | A sponsorship offer arrives now, from that sponsor or category if one suits your tier. {sponsor} becomes its name for the rest of the text and any follow-up |
rivalry | with ("rival", "opponent" or a player id), by (−1…1) | Heats up (+) or cools (−) a rivalry. 0.1 is a flare-up; 0.3 makes a new rival |
news | headline, body | A news story about you (text variables allowed) |
setFlag | flag, value? (true) | Remembers a story flag on the career; value: false clears it. Later stories test it with the flag / notFlag conditions. Text variables in the name make it one flag per person ("fuel:{rival}") |
social | text, handle? | A post on your feed; without handle it's your own |
followerSurge | percent (0–1), min? | Followers grow by this share, at least min |
staffLeaves | role? (id or short name, e.g. "physio"); older stories may use kind (coach, mentalCoach, fitnessCoach) | That member of your team leaves, with no payoff. Without a role, whoever is least settled goes |
staffMorale | by (−50…50), role?, relationship? (−50…50) | Staff morale (and their relationship with you) changes: one role, or the whole team without role |
holiday | weeks, label? | Time off from today: you miss events for weeks (entries refunded, bookings called off) and your workload recovers as on a holiday |
charge | offence, chance? (1), afterWeeks? | The regulator charges you with an offence of its rule book, as a case (see the regulator). With chance it ever comes out; with afterWeeks it comes out that many weeks later, otherwise at once. The choice shows "Could bring a charge from the regulator" |
catchOther | chance? (1) | An investigation catches a computer player with a Tour Card selling places in events (the regulator's sellingOffence): news, and the card is taken, so a tour place opens |
weeks is a number, or [min, max] rolled when it happens.
Mod scripts use the same handlers: dtj.effects.apply(action) takes modifier, injury, sponsorOffer, rivalry, social, followerSurge and staffMorale (clamped to smaller limits than a story's), and a script's inbox message can give its choices effects and actions that show as the same chips.
Modifiers last for their weeks, then expire. While one runs:
| Modifier field | Effect |
|---|---|
accuracy | −0.1…0.1: + tightens your throwing by that fraction (0.02 = 2%), − loosens it |
training | −1…1: training gains × (1 + training). −1 stops training altogether |
fatigueFloor | Fatigue can't drop below this |
confidenceFloor | Confidence can't drop below this |
composure | −20…20: composure points on the oche while it lasts (− nerves, + calm) |
Injuries go through the medical room. part says where it hurts (shoulder, elbow, wrist, back, knee, foot, eyes; left out, it's worked out from the label, else the throwing shoulder), and each part hurts a different part of your game. injury names an injury type from medical.json instead (its name and part). weeks is how long it takes with plain rest; the treatment you choose (and your physio) makes it quicker or slower, and playing on can make it worse. sitOut: true starts you on rest (you miss events), false on playing through it. Keep story injuries short: one to three weeks.
{ "type": "injury", "label": "Wrist strain", "part": "wrist", "weeks": [1, 2] }League nights are never skipped, because their line-up is fixed for the series.
Follow-ups and arcs#
A followUp on a choice or an outcome schedules another story:
"followUp": { "story": "acme.story.part-two", "afterWeeks": [2, 3], "chance": 0.8 }| Field | Meaning |
|---|---|
story | The story to fire (any trigger; usually followUp) |
afterWeeks | Weeks from the decision: a number or [min, max] |
chance | 0–1: whether it happens at all. Default 1 |
The follow-up fires at the first week's end on or after that date, even if its own trigger, cooldown or once wouldn't normally allow it. Its condition is still checked (without chance), so a condition can cancel it: "condition": { "hasRival": true } drops a grudge match if the rivalry has cooled. Trigger facts like won aren't available to a follow-up.
The follow-up keeps the text variables of the story that scheduled it ({opponent}, {event}, {rival}, {sponsor} and the rest), except {money}, {nextEvent}, {venue} and {city}, which are worked out fresh. The inbox marks it "Part 2", "Part 3" and so on. Scheduled follow-ups are kept in the save.
Use "trigger": "followUp" for parts that should only ever come from their arc (or from your script). The loader warns about a followUp story nothing schedules, unless its mod has a script, and removes a follow-up pointing at a story that doesn't exist.
Story flags#
A flag is a name the career remembers. A choice (or a rolled outcome) sets one with a setFlag action, and any later story can ask for it with flag or refuse to fire while it stands with notFlag. Flags never expire on their own: clear one with "value": false when the thread is paid off. Text variables are filled in the name, so a flag can be about a person: "fuel:{rival}" and "called-out:{opponent}" are one flag per rival or opponent, and a story that asks for "flag": "fuel:{rival}" only fires while the flag for that rival stands.
The game sets a few flags itself: staff-clash while two of your staff are at odds and staff-clash-asked once you have answered (the core's "{clashA} or {clashB}?" story in events/narrative/team.json fires on the first and not the second, and its choices set staff-clash-choice:back-one, bang-heads or let-it-ride, which the game reads the next week), and poached-by:<playerId> when a rival takes, or loses, a coach of yours.
The core's rival stories are the worked example (events/narrative/rivals.json): "Beaten by {rival}" sets fuel:{rival}, feud:{rival} or respect:{rival} for the choice you made, and three matchEnd and weekTick stories pay each one off (the screenshot comes down, a columnist drags the feud up while the record is against you, the rival returns the compliment) and clear it; "The Handshake" sets called-out:{opponent} and schedules the opponent's reply as a follow-up; declining the grudge match sets bottled:{rival}, which a rival with the edge brings up until you answer it. The pattern:
{ "id": "fire", "label": "Use it as fuel",
"actions": [{ "type": "setFlag", "flag": "fuel:{rival}" }] }{ "id": "acme.story.fuel-paid", "trigger": "matchEnd",
"condition": { "againstRival": true, "won": true, "flag": "fuel:{rival}" },
"choices": [{ "id": "done", "label": "Take it down",
"actions": [{ "type": "setFlag", "flag": "fuel:{rival}", "value": false }] }] }The flags are listed in the save's story memory (flags), in the order set. Mod scripts do not read them yet.
Text variables#
Titles, bodies, labels, outcome texts, news and social posts can use:
| Variable | Is | When missing |
|---|---|---|
{name} | Your full name | |
{first} | Your first name | |
{nickname} | Your nickname | |
{money} | Your bank balance, in the player's currency (a fixed sum: {money:500}) | |
{event} | The event (matchEnd, eventEnd, or the one a withdraw pulled you out of) | "the event" |
{opponent} | Your opponent (matchEnd) | "your opponent" |
{rival} | Your fiercest rival | "your old rival" |
{nextEvent} | The next event you're playing | "your next event" |
{venue}, {city} | Where that event (or the current one) is | "the venue", "town" |
{sponsor} | Your biggest sponsor, or the one a sponsorOffer brought in | "your sponsor" |
{coach} | Your darts coach | "your coach" |
{staff:physio} | The member in that staff role (short name or full id) | "your physio" |
{agency} | Your agent's agency (or "Name's outfit" for an independent) | "your agency" |
{clashA}, {clashB} | The two members of your team at odds while a clash runs | (not filled) |
{dealSponsor} | The sponsor in the latest negotiation news (see negotiated) | "the sponsor" |
{otherBidder} | The other side of the latest bidding war | "their rivals" |
{exhibition} | The exhibition night just played (exhibitionEnd), or your next booking | "the exhibition" |
{town} | That night's town | "town" |
{scene:level} … | A word of the darts scene you live in (see scenes, words): {scene:level} is "county" in England and "area" in Scotland; {scene:name}, {scene:body}, and {scene:town} / {scene:venue} are a place of the scene's own events, the same one all week; {scene:pub} is one of the scene's venues of the kind "pub" (the fallback record's word where the scene has none) and {scene:league} the name of its local rung's tour; the core also gives every scene road and money words ({scene:motorway}, {scene:services}, {scene:bigRoad}, {scene:smallWin}, {scene:chipShop}, {scene:socialClub} and others: see scenes.json), British in the UK's scenes and neutral on the fallback record | the story is not told |
A story that uses a scene word takes its wording from where the player lives, and a story that uses a word the player's scene has not got does not fire there: a title written as {scene:Level} Call-Up reads "County Call-Up" in England and "Area Call-Up" in Scotland, and the story is never sent to a player living in a country whose scene has no word for the level. Write the words your scene has in its words; a key your scene leaves out is read from the fallback scene's. Nothing in the base game names an English place or an English level word outright any more: use the scene words for places and levels, and livesIn for a story that can only happen in one country.
Example: an arc#
A charity exhibition in three steps: the invite, the night itself (two to three weeks later), and a chance of a viral moment.
[
{
"id": "acme.story.charity-invite",
"trigger": "weekTick",
"condition": { "chance": 0.02, "months": [10, 11], "followersMin": 400 },
"title": "The Hospice Exhibition",
"body": "The hospice in {city} wants you to headline its exhibition night.",
"choices": [
{
"id": "headline",
"label": "Headline it",
"effects": { "image": 2 },
"outcome": "Posters go up with your face on them.",
"followUp": { "story": "acme.story.charity-night", "afterWeeks": [2, 3] }
},
{
"id": "donate",
"label": "Send a donation",
"effects": { "money": -100, "image": 1 },
"outcome": "They send a lovely card."
}
],
"cooldownWeeks": 52
},
{
"id": "acme.story.charity-night",
"trigger": "followUp",
"title": "Exhibition Night",
"body": "The function room in {city} is packed. How do you play it?",
"choices": [
{
"id": "show",
"label": "Put on a show",
"effects": { "fatigue": 0.05, "morale": 6 },
"outcome": "Big finishes and bigger cheers.",
"outcomes": [
{ "weight": 3, "text": "The night raises a fortune.", "effects": { "image": 4 } },
{
"weight": 1,
"hint": "Could go viral",
"text": "You hit a nine-darter against the local radio DJ. The clip goes everywhere.",
"actions": [
{ "type": "followerSurge", "percent": 0.1, "min": 500 },
{
"type": "news",
"headline": "Nine-darter at {city} charity night",
"body": "{name} produced a perfect leg for a good cause."
}
]
}
]
},
{ "id": "relaxed", "label": "Keep it relaxed", "effects": { "morale": 4 } }
]
}
]Example: the week before an event#
{
"id": "acme.story.food-poisoning",
"trigger": "weekTick",
"condition": { "chance": 0.01, "hasEventWithinDays": 5, "injured": false },
"title": "The Dodgy Prawn",
"body": "A late-night takeaway and a very long night. {nextEvent} is in a few days.",
"choices": [
{
"id": "play",
"label": "Dry toast and play",
"effects": { "fatigue": 0.1 },
"outcomes": [
{ "weight": 2, "text": "You're grey, but upright." },
{
"weight": 1,
"text": "It lingers.",
"actions": [
{ "type": "modifier", "label": "Under the weather", "weeks": 1, "accuracy": -0.02 }
]
}
]
},
{
"id": "withdraw",
"label": "Pull out of {nextEvent}",
"actions": [{ "type": "withdraw" }],
"outcome": "You text the organisers from the bathroom floor. Entry fee refunded."
}
]
}Checking your stories#
The Mods screen lists problems with each file, line and field. A story with an invalid field (an unknown action type, a severity that doesn't exist, a week range like [4, 2]) disables the mod, like any other schema error. Broken references (a follow-up to a missing story, a sponsorOffer from an unknown sponsor, a rivalry with an unknown player) are removed with a warning and the story still loads.