atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0

Commit 56d34031c321

tarball

atagen <boss@atagen.co>2026-10-14 09:25 UTC

4 files changed+71-5
game: let the forum print a ruler for a player without one

The forum floor cannot be left until the Elder is beaten, and with the drawer at 1 in 4 and no shop after the hotel, a rulerless player with modest girth was nearly always banned guessing among 21 offsets. Each live thread read there now rolls 1 in 3 for a printable ruler.
Modified file: src/game/balance/floors.rs+2 additions
@@ -6,6 +6,8 @@ pub const OVERSTAY_SHAME: u32 = 10;
66 pub const BRANCH_TURN: u32 = 10;
77 pub const ZIP: u32 = 200;
88 pub const RULER_IN_DRAWER: u32 = 4;
9+pub const PRINTABLE_RULER: u32 = 3;
10+pub const RULER_SHAME: u32 = 10;
911 pub const KEY_IN_DRAWER: u32 = 3;
1012 pub const GREGS_LOCKER: u32 = 20;
1113 pub const MEDITATION_ODDS: u32 = 2;
Modified file: src/game/drawer.rs+4 additions−2 deletions
@@ -1,6 +1,8 @@
11 use super::{
22 Floor, Item, Notice, Outcome, Run,
3- balance::{BATHROOM_VASELINE, DRAWER_JAMS, GREGS_LOCKER, KEY_IN_DRAWER, RULER_IN_DRAWER},
3+ balance::{
4+ BATHROOM_VASELINE, DRAWER_JAMS, GREGS_LOCKER, KEY_IN_DRAWER, RULER_IN_DRAWER, RULER_SHAME,
5+ },
46 };
57 use crate::problem::Problem;
68 use axum::http::{StatusCode, header};
@@ -72,7 +74,7 @@ impl Run {
7274 return nothing();
7375 }
7476 self.items.grant(Item::Ruler);
75- self.standing.shame += 10;
77+ self.standing.shame += RULER_SHAME;
7678 Ok(self.found(
7779 Item::Ruler,
7880 "You now know. This is the first honest measurement of the run.",
Modified file: src/game/forum.rs+26 additions−3 deletions
@@ -4,8 +4,11 @@ use axum::http::{StatusCode, header};
44 use serde_json::{Value, json};
55
66 use super::{
7- Cause, Floor, Outcome, Run,
8- balance::{EXAGGERATION, MEDITATION_ODDS, PATIENCE, THREAD_READS, ULI_ODDS},
7+ Cause, Floor, Item, Outcome, Run,
8+ balance::{
9+ EXAGGERATION, MEDITATION_ODDS, PATIENCE, PRINTABLE_RULER, RULER_SHAME, THREAD_READS,
10+ ULI_ODDS,
11+ },
912 credentials::{Scheme, parse_credentials},
1013 measure::{exaggeration, honest},
1114 };
@@ -197,7 +200,8 @@ impl Run {
197200 return Ok(thread(None));
198201 }
199202 self.end_kitchen_visit();
200- if self.online.uli_taught {
203+ let rulerless = self.floor == Floor::Forum && !self.items.kit.ruler;
204+ if self.online.uli_taught && !rulerless {
201205 return Ok(thread(None));
202206 }
203207 if self.here.thread_rolls >= THREAD_READS {
@@ -210,6 +214,25 @@ impl Run {
210214 }
211215 self.here.thread_rolls += 1;
212216 self.touch();
217+ if rulerless
218+ && self.chance(
219+ self.decision(
220+ "printable-ruler",
221+ "Does this read surface a member's printable ruler?",
222+ ),
223+ PRINTABLE_RULER,
224+ )
225+ {
226+ self.items.grant(Item::Ruler);
227+ self.standing.shame += RULER_SHAME;
228+ return Ok(thread(Some(
229+ "A member has attached a printable ruler. Print at 100%. Do not scale to fit. You now know.",
230+ ))
231+ .header(header::LOCATION, self.path("/inventory/ruler")));
232+ }
233+ if self.online.uli_taught {
234+ return Ok(thread(None));
235+ }
213236 let detail = if !self.online.meditation_taught {
214237 if !self.learn_meditation() {
215238 return Ok(thread(None));
Modified file: src/tests/floors.rs+39 additions
@@ -228,6 +228,45 @@ async fn reading_the_thread_teaches_uli() {
228228 }
229229
230230 #[tokio::test]
231+async fn the_forum_may_print_you_a_ruler() {
232+ let mut printed = 0;
233+ for seed in 0..100u64 {
234+ let app = app_with(Floor::Forum, seed, |run| {
235+ run.online.story = Story::ReadForum;
236+ run.online.meditation_taught = true;
237+ run.online.uli_taught = true;
238+ });
239+ for _ in 0..2 {
240+ let read = send(&app, "GET", "/runs/cafebabe/thread", &[], "").await;
241+ if read
242+ .headers()
243+ .get("location")
244+ .is_some_and(|l| l.to_str().unwrap().ends_with("/inventory/ruler"))
245+ {
246+ printed += 1;
247+ let state = send(&app, "GET", "/runs/cafebabe", &[], "").await;
248+ assert!(!header(&state, "x-length").is_empty());
249+ break;
250+ }
251+ }
252+ }
253+ assert!(
254+ (40..=72).contains(&printed),
255+ "{printed}/100 printed a ruler in two reads"
256+ );
257+
258+ let measured = seeded_with(Floor::Forum, |run| {
259+ run.online.story = Story::ReadForum;
260+ run.online.uli_taught = true;
261+ run.items.kit.ruler = true;
262+ });
263+ for _ in 0..2 {
264+ let read = send(&measured, "GET", "/runs/cafebabe/thread", &[], "").await;
265+ assert!(read.headers().get("location").is_none());
266+ }
267+}
268+
269+#[tokio::test]
231270 async fn the_thread_cannot_be_read_before_anyone_mentions_it() {
232271 let app = seeded_with(Floor::Bedroom, |_| {});
233272 let read = send(&app, "GET", "/runs/cafebabe/thread", &[], "").await;