Something went wrong. Try again.
HEXWORK - A Lantern in the Deep: 3D ASCII terminal roguelike RPG in Rust.
Entirely designed and built by Muse Spark 1.3 to evaluate if I like the model.
Something went wrong. Try again.
6.2 kB · 175 lines
Rust
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176//! Identification-state rendering (docs/03-systems-design.md §3.4, §3.10).//!//! This module EXTENDS the tooltip honesty contract of//! `hexwork_ui::look` — it never duplicates it: `look::item_tooltip` decides//! WHAT the delver may know (hiding unidentified uses); the functions here//! render the small status fragments around that decision (state lines,//! tinker receipts, trial reports, shop tags) as pure `String`s.//!//! Honesty law (docs/03 §3.4): the GAME never lies — every string here is//! either earned knowledge or an explicit marker of ignorance ("you haven't//! learned this yet", "the room shows nothing"). NPC lies (the underpaid//! tinker's fibs from `hexwork_content::identify::TINKER_FIBS`) arrive as//! opaque `out` text and are quoted as speech, never asserted as fact.//!//! Pure string building only: no I/O, no terminal calls, no RNG, no width//! truncation — the caller boxes (see `look::render`).
/// Identification-state line fragment (docs/03 §3.4).////// `state` is `"unknown" | "tried" | "known"` (anything else renders as the/// unknown marker — ignorance is the safe default). `uses_seen` decorates/// the half-known line only.#[must_use]pub fn idf_line(state: &str, uses_seen: u8) -> String { // `uses_seen` is informational symmetry with `IdfRecord` (a Tried record // always shows 1 here — the second witnessed use flips it to Known); // the line itself states the earned marker, never a stat. let _ = uses_seen; match state { "known" => "[identified]".to_owned(), "tried" => "[half-known — seen once, trial rooms can confirm]".to_owned(), _ => "[unidentified — you haven't learned this yet]".to_owned(), }}
/// Tinker receipt: ledger-flavored payment record (docs/02 §2: every altar/// keeps a ledger; docs/03 §3.4: the tinker is paid, sometimes lies if/// underpaid — and the ledger remembers).////// `out` is the outcome word the caller computed (`"identified"`,/// `"lied"`, `"refused"`, or the quoted fib). Underpaid bands (`paid </// fair`) append the ledger-remembers note: the tinker pockets the/// difference AND the town ledger records the insult offer.#[must_use]pub fn tinker_receipt(paid: u32, fair: u32, out: &str) -> String { let mut s = format!("tinker receipt: paid {paid}cu of {fair}cu fair — {out}"); if paid < fair { s.push_str("; the tinker pockets the difference AND the ledger remembers"); } s}
/// Trial-room report (docs/03 §3.4: the testing chapel toybox).////// Lists the revealed uses verbatim; when the room matched nothing it says/// so plainly — honest silence, not ignorance, and never an invented use.#[must_use]pub fn trial_report(room: &str, revealed: &[String]) -> String { if revealed.is_empty() { return format!("{room}: the room shows nothing — honest silence, not ignorance"); } let mut s = format!("{room} reveals:"); for u in revealed { s.push_str(&format!("\n· {u}")); } s}
/// Shop price tag (docs/03 §3.4: unidentified stock honestly labeled).#[must_use]pub fn shop_tag(price: u32, identified: bool) -> String { if identified { format!("{price}cu, identified") } else { format!("{price}cu, unidentified — priced as mystery") }}
#[cfg(test)]mod tests { use super::*;
#[test] fn unknown_line_marks_ignorance() { assert_eq!( idf_line("unknown", 0), "[unidentified — you haven't learned this yet]" ); }
#[test] fn unknown_is_the_safe_default() { assert_eq!( idf_line("bogus-state", 0), "[unidentified — you haven't learned this yet]" ); }
#[test] fn tried_line_points_to_trial_rooms() { let s = idf_line("tried", 1); assert!(s.contains("half-known"), "must carry the marker: {s:?}"); assert!(s.contains("trial rooms"), "must say how to confirm: {s:?}"); }
#[test] fn known_line_is_short() { assert_eq!(idf_line("known", 9), "[identified]"); }
#[test] fn receipt_fair_pay_has_no_ledger_note() { let s = tinker_receipt(10, 10, "identified"); assert!(s.contains("10cu"), "must show paid: {s:?}"); assert!(s.contains("identified"), "must show outcome: {s:?}"); assert!(!s.contains("ledger remembers"), "fair pay is square: {s:?}"); }
#[test] fn receipt_overpay_has_no_ledger_note() { let s = tinker_receipt(99, 10, "identified"); assert!(!s.contains("ledger remembers"), "overpay is square: {s:?}"); }
#[test] fn receipt_underpay_notes_the_ledger() { let s = tinker_receipt(5, 10, "lied: \"hums true when oiled\""); assert!(s.contains("5cu of 10cu"), "must show paid/fair: {s:?}"); assert!( s.contains("the tinker pockets the difference AND the ledger remembers"), "underpay must warn: {s:?}" ); }
#[test] fn receipt_refusal_notes_the_ledger() { let s = tinker_receipt(1, 10, "refused"); assert!( s.contains("ledger remembers"), "refusal remembers too: {s:?}" ); }
#[test] fn trial_report_lists_revealed_uses() { let revealed = vec!["bait a beast snare".to_owned(), "trap-tripwire".to_owned()]; let s = trial_report("cage", &revealed); assert!(s.contains("cage reveals"), "must name the room: {s:?}"); for u in &revealed { assert!(s.contains(u.as_str()), "must list {u:?}: {s:?}"); } }
#[test] fn trial_report_empty_is_honest_silence() { let s = trial_report("water barrel", &[]); assert!( s.contains("the room shows nothing — honest silence, not ignorance"), "empty must be honest: {s:?}" ); }
#[test] fn shop_tag_identified() { assert_eq!(shop_tag(12, true), "12cu, identified"); }
#[test] fn shop_tag_mystery() { assert_eq!( shop_tag(12, false), "12cu, unidentified — priced as mystery" ); }}