Feather’s story system covers the three things most narrative games need: branching dialogues,
quests with staged objectives, and cinematics. All of it is project data, authored in the Story
window or through story.* commands, and saved with the project.
Conditions and actions are short Rhai snippets that use the same functions as world scripts. A dialogue choice can check the player’s inventory, and a quest stage can complete when a script value reaches a goal.
What’s included
Section titled “What’s included”- Dialogues: a node graph of lines and choices, with speakers, conditions, actions, voice-over, speaker animations, auto-advance and per-line camera framing.
- Quests: ordered stages with objectives, progress toward a goal, completion and failure conditions, on-screen tracking, world markers and a journal.
- Cinematics: a camera path with timed events — subtitles, animations, moves, sounds, fades and more — with letterbox, skipping and a blend back to gameplay.
- Story progress — dialogue choices seen, quest status and stage — is kept in saves.
Write a dialogue
Section titled “Write a dialogue”- Open Window → Story and select the Dialogues tab.
- Create a dialogue. Add nodes to the graph and drag between them to link a line to the next one or to a choice’s target.
- For each node, set the speaker and the text. Add choices for the player, each with optional conditions and actions.
- Use the preview to step through the conversation in the editor.
- Start the dialogue from a script, for example when the player interacts with a character.
// A world script: talk to the blacksmith (object 18).fn on_interact(id) { if id == "18" { start_dialogue("blacksmith_intro", 18); }}Dialogue nodes
Section titled “Dialogue nodes”| Field | Description |
|---|---|
speaker |
npc (the character the dialogue was started with), player, an object name or any display name; empty is a narrator line |
text |
The line. {key} inserts script state and {npc} the partner’s name. A node with no text is a silent logic step that runs its action and follows the first choice whose condition passes |
condition |
Rhai condition; when false the node is skipped and next follows |
action |
Rhai code run when the node is entered |
next |
The node after this one when it has no choices; empty ends the dialogue |
choices |
Up to 12 choices |
voice |
A sound played with the line |
animation |
A clip the speaker plays |
camera |
Free, Speaker, OverShoulder or Wide; unset uses the dialogue’s camera |
auto_advance |
Seconds before advancing on its own; 0 waits for the player |
| Choice field | Description |
|---|---|
text |
What the player picks |
next |
Node to go to; empty ends the dialogue |
condition |
Rhai condition; when false the choice is hidden |
show_disabled, hint |
Show a failing choice greyed out with a hint, such as “Needs 50 gold” |
action |
Rhai code run when picked |
once |
Offer the choice only until it has been picked once |
The dialogue’s camera defaults to OverShoulder. Other options are Speaker (close on the
speaker’s face), Wide (both characters side by side) and Free (leave the camera where it is).
Create a quest
Section titled “Create a quest”- In Window → Story, select the Quests tab and create a quest.
- Give it a title, description and category, such as “Main” or “Side”, and a reward shown in the journal.
- Add stages in order. For each stage, write a short objective and set how it completes.
- Start the quest with
auto_start, from a dialogue action, or from a script.
| Quest field | Description |
|---|---|
category |
Journal group |
start |
The first stage; empty uses the first in the list |
auto_start |
A Rhai condition that starts the quest when it becomes true |
on_complete, on_fail |
Rhai code run when the quest ends |
reward |
Text shown in the journal |
hidden |
Internal quests never show in the journal or tracker |
| Stage field | Description |
|---|---|
objective |
Short text for the tracker; {key} inserts script state |
details |
Longer journal entry |
complete_when |
Rhai condition that completes the stage |
fail_when |
Rhai condition that fails the whole quest |
progress, goal |
A Rhai number shown as progress toward the goal |
on_enter, on_complete |
Rhai code run when the stage starts and ends |
next |
The next stage; empty completes the quest |
target |
Object name the quest marker points at |
Quest conditions are checked every quest_check_interval seconds.
Cinematics
Section titled “Cinematics”A cinematic is a timed sequence: camera keys plus events. Events can show a subtitle, play an
animation, move, place or show an object, play a sound, fade, shake the camera, set the time of
day, start a dialogue or run Rhai code. A cinematic can follow an anchor object, show a
letterbox, hide the HUD, be skippable and blend back to the gameplay camera.
Cinematics are edited on the Cinematics tab, and as tracks in the Sequencer. See Sequencer.
Story settings
Section titled “Story settings”| Field | Description | Range | Default |
|---|---|---|---|
text_speed |
Characters revealed per second; 0 shows lines at once | 0–1000 | 45 |
dialogue_camera |
Frame the speakers during dialogue | On/off | On |
pause_clock_in_dialogue |
Stop the game clock while a dialogue is open | On/off | On |
quest_tracker |
Show active objectives on screen | On/off | On |
quest_markers |
Draw a marker over the current objective’s target | On/off | On |
quest_check_interval |
Seconds between quest condition checks | 0.05–10 s | 0.5 s |
journal_key |
Opens the quest journal | Key name | J |
skip_key |
Skips a skippable cinematic | Key name | Space |
advance_keys |
Advance dialogue | Up to 8 keys | Space, Enter, E |
letterbox |
Letterbox bar height as a fraction of the screen | 0–0.3 | 0.11 |
subtitles |
Show subtitles for cinematic lines | On/off | On |
Extend it
Section titled “Extend it”| Function | Description |
|---|---|
start_dialogue(id[, npc]) |
Starts a dialogue, optionally with an object as the npc speaker |
end_dialogue(), dialogue_active() |
End the open dialogue, or check for one |
dialogue_seen(dialogue, node) |
Whether the player has reached a node |
start_quest(id), set_quest_stage(id, stage) |
Start a quest or jump to a stage |
complete_quest(id), fail_quest(id) |
End a quest |
quest_status(id) |
inactive, active, completed or failed |
quest_stage(id), quest_active(id) |
The current stage, or whether the quest is active |
play_cinematic(id), stop_cinematic(), cinematic_playing() |
Cinematics |
play_sequence(id) |
Plays a Sequencer sequence |
open_journal(), close_journal() |
The quest journal |
| Hook | Runs when |
|---|---|
on_dialogue_end(id) |
A dialogue finishes |
on_quest(id, status, stage) |
A quest changes status or stage; status is started, stage, completed or failed |
on_cinematic_end(id) |
A cinematic finishes |
// A world script: play the ending cinematic when the main quest completes.fn on_quest(id, status, stage) { if id == "main" && status == "completed" { play_cinematic("ending"); }}Limits
Section titled “Limits”- Up to 512 dialogues with 400 nodes each, and 12 choices per node.
- Up to 256 quests with 64 stages each.
- Up to 128 cinematics with 512 events each.
- Text up to 4,096 characters and Rhai snippets up to 8,192 characters.
Automate it
Section titled “Automate it”# A two-line dialogue with a choicefeather-cli --connect .feather-session.json command story.set --json '{ "kind":"dialogue", "value":{"id":"blacksmith_intro","title":"Blacksmith","nodes":[ {"id":"hello","speaker":"npc","text":"Need a blade sharpened?","choices":[ {"text":"Yes, please.","next":"thanks"}, {"text":"Not today.","next":""}]}, {"id":"thanks","speaker":"npc","text":"Come back at dusk."}]}}'
# Show lines instantly and hide quest markersfeather-cli --connect .feather-session.json command story.settings \ --json '{"text_speed":0,"quest_markers":false}'story.set replaces the whole item, so read it with story.get first when you change one part.
story.get with no arguments summarises every dialogue, quest and cinematic plus the settings.