atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0

Commit 624fe9c09c8e

tarball

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

7 files changed+28-5
http: say when a timed floor has been served, and show it on the floor turn

FLOOR_TURNS only surfaced as a 425 after trying to leave, so a player could not tell the floor was done without spending a request. served_floor is shared with that 425 so the two cannot disagree.
Modified file: src/game/gauges.rs+2 additions
@@ -27,6 +27,7 @@ pub struct Gauges {
2727 pub(crate) turn: u32,
2828 pub(crate) floor_turns: u32,
2929 pub(crate) floor_limit: Option<u32>,
30+ pub(crate) floor_over: bool,
3031 pub(crate) dangers: Vec<(&'static str, Danger)>,
3132 pub(crate) observed: Option<Observer>,
3233 pub(crate) mum_challenge: Option<String>,
@@ -87,6 +88,7 @@ impl Run {
8788 turn: self.turn,
8889 floor_turns: self.here.turns,
8990 floor_limit: self.floor.timed().then_some(FLOOR_LIMIT),
91+ floor_over: self.floor.timed() && self.served_floor(),
9092 dangers: self.dangers(),
9193 observed: self.observed,
9294 mum_challenge: self.mum_challenge(),
Modified file: src/game/travel.rs+6 additions−1 deletions
@@ -20,6 +20,11 @@ pub(super) enum Exit {
2020 }
2121
2222 impl Run {
23+ /// Whether enough turns have passed for a timed floor to let the player go.
24+ pub(super) fn served_floor(&self) -> bool {
25+ self.here.turns >= FLOOR_TURNS
26+ }
27+
2328 pub(super) fn next_floor(&mut self, exit: Exit) {
2429 let Some(next) = self.floor.next() else {
2530 return;
@@ -93,7 +98,7 @@ impl Run {
9398 )
9499 .with("elder", self.path("/elder")));
95100 }
96- floor if floor.timed() && self.here.turns < FLOOR_TURNS => {
101+ floor if floor.timed() && !self.served_floor() => {
97102 return Err(Problem::new(
98103 StatusCode::TOO_EARLY,
99104 "floor-not-over",
Modified file: src/headers.rs+1 additions
@@ -6,6 +6,7 @@ pub const X_DANGER: HeaderName = HeaderName::from_static("x-danger");
66 pub const X_DIAGNOSIS: HeaderName = HeaderName::from_static("x-diagnosis");
77 pub const X_EQ: HeaderName = HeaderName::from_static("x-eq");
88 pub const X_FLOOR: HeaderName = HeaderName::from_static("x-floor");
9+pub const X_FLOOR_OVER: HeaderName = HeaderName::from_static("x-floor-over");
910 pub const X_FLOOR_TURN: HeaderName = HeaderName::from_static("x-floor-turn");
1011 pub const X_FORESKIN: HeaderName = HeaderName::from_static("x-foreskin");
1112 pub const X_FRICTION: HeaderName = HeaderName::from_static("x-friction");
Modified file: src/http/run.rs+6 additions−3 deletions
@@ -17,9 +17,9 @@ use crate::{
1717 balance::{FRICTION_MAX, SUSPICION_LIMIT},
1818 },
1919 headers::{
20- X_CASH, X_DANGER, X_EQ, X_FLOOR, X_FLOOR_TURN, X_FORESKIN, X_FRICTION, X_GAINS_ARRIVED,
21- X_GIRTH, X_LENGTH, X_OBSERVED_BY, X_RUN_OVER, X_SELF_REPORTED_LENGTH, X_SHAME, X_STAMINA,
22- X_SUSPICION, X_TURN,
20+ X_CASH, X_DANGER, X_EQ, X_FLOOR, X_FLOOR_OVER, X_FLOOR_TURN, X_FORESKIN, X_FRICTION,
21+ X_GAINS_ARRIVED, X_GIRTH, X_LENGTH, X_OBSERVED_BY, X_RUN_OVER, X_SELF_REPORTED_LENGTH,
22+ X_SHAME, X_STAMINA, X_SUSPICION, X_TURN,
2323 },
2424 link::Link,
2525 locale::Locale,
@@ -174,6 +174,9 @@ fn stats(gauges: &Gauges, shame: u32) -> Vec<(HeaderName, String)> {
174174 }),
175175 ),
176176 ];
177+ if gauges.floor_over {
178+ stats.push((X_FLOOR_OVER, "?1".into()));
179+ }
177180 if !gauges.dangers.is_empty() {
178181 let dangers: Vec<String> = gauges
179182 .dangers
Modified file: src/tests/methods.rs+1 additions
@@ -16,6 +16,7 @@ async fn bodies_do_not_repeat_the_headers() {
1616 assert!(response.headers().contains_key("x-eq"));
1717 assert_eq!(header(&response, "x-turn"), "0");
1818 assert_eq!(header(&response, "x-floor-turn"), "0/10");
19+ assert!(response.headers().get("x-floor-over").is_none());
1920 let body = json(response).await;
2021 for stat in [
2122 "eq",
Modified file: src/tests/turns.rs+10 additions
@@ -485,3 +485,13 @@ async fn gauges_near_the_edge_are_flagged() {
485485 "friction=dangerous, eq=critical, stamina=dangerous, suspicion=critical"
486486 );
487487 }
488+
489+#[tokio::test]
490+async fn a_served_floor_says_so() {
491+ let app = seeded_with(Floor::Bedroom, |run| run.here.turns = 6);
492+ let view = send(&app, "GET", "/runs/cafebabe", &[], "").await;
493+ assert_eq!(header(&view, "x-floor-over"), "?1");
494+ let forum = seeded_with(Floor::Forum, |run| run.here.turns = 6);
495+ let view = send(&forum, "GET", "/runs/cafebabe", &[], "").await;
496+ assert!(view.headers().get("x-floor-over").is_none());
497+}
Modified file: src/ui.html+2 additions−1 deletions
@@ -220,7 +220,8 @@ function renderStats(headers) {
220220 const row = el("tr");
221221 for (const stat of stats.slice(i, i + 2)) {
222222 const value = headers ? headers[stat.header] : undefined;
223- const cell = el("td", {}, value === undefined ? "—" : value);
223+ const done = stat.header === "x-floor-turn" && headers && "x-floor-over" in headers;
224+ const cell = el("td", { style: done ? "color: green; font-weight: bold" : "" }, value === undefined ? "—" : value);
224225 const changed = value === undefined ? null : change(stat, value);
225226 if (changed) cell.append(" ", changed);
226227 const measured = stat.unmeasured in lastStats;