Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Reference: conducts

Audience       Engine developer adding a conduct, or anyone reading
               a run dump or the leaderboard.
Prerequisites  Rust, and a passing familiarity with bevy_ecs.
Status         As the source is. If this page and the source
               disagree, the source is right and this page is a bug.

A conduct is something a run can go without. A run that wins is paid 10% of its score for each conduct it kept, added on top (additive: fourteen kept is +140%). A run that dies is paid nothing, and its dump lists no conducts.

Everything here lives in models/src/conduct.rs and is re-exported as models::conduct, bar Conduct and Conducts, which are at the crate root.

The fourteen

Each conduct breaks on one act, at one place in the code. Every site below is player-only: monsters share the code and have no conducts.

ConductBreaks when the playerSite
Potionsdrinks a potionresolve_use, models/src/items.rs
Scrollsreads a scrollresolve_use
Wandszaps a wand, or throws oneresolve_use; resolve_wand_throw, throwing.rs
Runesinvokes a charged runeresolve_use (an inert rune does not count)
Spellspays for a spellpay_for_spell, items/spells.rs
Coinstakes a coinspend_pickup, items/pickups.rs
Rangedthrows or fires anythingresolve_throw, items/throwing.rs
Attackinglands a melee swing, lunge or reachland_swing, combat.rs
Charmingcharms a creaturecharm, companion.rs
Companionrecruits a boon companionrecruit, companion.rs
Damageloses HPtook_damage, helpers.rs; the angel’s test of faith
Gearputs gear ontoggle_equipped, equipment.rs
Armorhas armor take a killing blowarmor_takes_last_hit, helpers.rs
Lurkis not the lurknot tracked; read off the Lurk marker

Gear is deliberately not in equip_silently, so the starting kit does not break it, and taking gear off does not either. A charm scroll or wand breaks Charming and Scrolls or Wands together; a creature that becomes the Helper breaks Companion.

Known gaps

Damage is “HP never dropped”, and three paths lower the player’s HP without going through took_damage: system shock from a polymorph (system_shock, wands.rs), the devil’s max-HP price (pay, spirits.rs), and the vampire’s max-HP drain (vampiric_drain, abilities.rs). A run can keep Damage through them. The drain only matters when its hit did no damage. They are left unhooked on purpose.

The tally

pub enum Conduct { Potions, Scrolls, /* ... */ Lurk }
pub struct Conducts { pub broken: u16 }

pub fn broke(world: &mut World, conduct: Conduct)
pub fn player_broke(world: &mut World, who: Entity, conduct: Conduct)
pub fn kept_mask(world: &mut World) -> u16
pub fn bonus_percent(mask: u16) -> i64

Conducts is a resource, one bit per Conduct in Conduct::ALL order; a set bit is a broken conduct. broke does nothing in a world with no Conducts, so a bare test world needs no setup. player_broke is broke behind a Player check.

kept_mask is what a run has kept: every conduct but the broken ones, with Lurk kept only when the player carries the Lurk marker. It is the one mask the payout, the leaderboard, the dump and the win screen all read.

The order of Conduct::ALL is a file format, because the leaderboard stores the mask by bit index. A new conduct goes on the end. Nothing is ever reordered.

Paying out

pub fn pay_conducts(world: &mut World) -> u16

models/src/score.rs

Settles the turn’s kills, adds bonus_percent(kept_mask) of the score on top, and returns the mask it paid for. It is called once, by run_victory_screens in engine/src/main.rs, before the leaderboard is written, so the board, the dump and the win panel all see the bonused score. Calling it twice pays twice.

Where it is stored and shown

WhereWhat
SaveSaveGame::conducts, the broken mask. Added with a SAVE_VERSION bump.
LeaderboardEntry::conducts, the kept mask (zero for a loss). LEADERBOARD_VERSION 2.
Win screenOne conduct::MARK per kept conduct, from conduct::marks, under the score.
Run dumpA -- Conducts (+N%) -- section on a win, one name per kept conduct.
-scoresThe marks, uncoloured, after each line.

A version 1 leaderboard is still read, with every mask zero, and becomes version 2 on the next write. Any other version is refused and left alone, as before.

marks(mask, stripes) colours the marks by cycling stripes by the conduct’s place in Conduct::ALL, so a conduct keeps its colour whichever others are broken. The win screen passes the run’s pride flag (pride::stripes). Lurk is always LURK_COLOR, the colour of the lurk’s own @; a test fails if the two drift apart. The names come from strings::conduct_name, indexed by bit.

See also

constants.md the score constants the bonus sits on cli-and-env.md -scores, the run dump and the data directory rendering.md the win panel the marks are drawn on ../explanation/ecs-in-nihilurk.md resources, and why the tally is one