atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0

Commit fcc6cf6d5edc

tarball

atagen <boss@atagen.co>2026-10-13 11:33 UTC

7 files changed+60-5
game: announce queued gains when they land

A 202 promised gains on a later turn, but realise() applied them silently, so the only trace was a header total that had moved for no visible reason. The turn they land on now carries X-Gains-Arrived with the amounts and a Warning for the page's alert.
Modified file: src/game/body.rs+9 additions
@@ -22,6 +22,7 @@ pub struct Body {
2222 pub(crate) put_applied: bool,
2323 pub(crate) conditions: Conditions,
2424 pub(crate) pending: Vec<Pending>,
25+ pub(crate) arrived: Option<Arrival>,
2526 }
2627
2728 impl Body {
@@ -40,6 +41,7 @@ impl Body {
4041 put_applied: false,
4142 conditions: Conditions::default(),
4243 pending: Vec::new(),
44+ arrived: None,
4345 }
4446 }
4547 }
@@ -60,6 +62,13 @@ pub struct Pending {
6062 pub(crate) foreskin: f64,
6163 }
6264
65+#[derive(Default)]
66+pub struct Arrival {
67+ pub(crate) length: f64,
68+ pub(crate) girth: f64,
69+ pub(crate) foreskin: Option<f64>,
70+}
71+
6372 #[derive(Clone, Copy, PartialEq, Eq, Debug)]
6473 pub enum Grip {
6574 Overhand,
Modified file: src/game/gauges.rs+7 additions−1 deletions
@@ -3,7 +3,7 @@ use std::time::Instant;
33 use serde_json::{Value, json};
44
55 use super::{
6- Elder, Excuse, Floor, Grip, Item, Observer, Run, Story, Technique,
6+ Arrival, Elder, Excuse, Floor, Grip, Item, Observer, Run, Story, Technique,
77 balance::DANGER_EQ,
88 techniques::{Training, Utility},
99 };
@@ -27,6 +27,7 @@ pub struct Gauges {
2727 pub(crate) observed: Option<Observer>,
2828 pub(crate) mum_at_door: bool,
2929 pub(crate) warnings: Vec<&'static str>,
30+ pub(crate) arrived: Option<Arrival>,
3031 pub(crate) ruler: bool,
3132 pub(crate) ring: bool,
3233 }
@@ -57,6 +58,10 @@ impl Run {
5758 warnings.push("friction burn");
5859 }
5960 warnings.extend(self.notices.drain(..).map(|notice| notice.text()));
61+ let arrived = self.body.arrived.take();
62+ if arrived.is_some() {
63+ warnings.push("the queued gains have arrived");
64+ }
6065 if self.body.eq > DANGER_EQ && self.ended.is_none() {
6166 warnings.push("it is turning purple");
6267 }
@@ -78,6 +83,7 @@ impl Run {
7883 observed: self.observed,
7984 mum_at_door: self.household.mum.is_some(),
8085 warnings,
86+ arrived,
8187 ruler: self.items.kit.ruler,
8288 ring: self.items.kit.ring,
8389 }
Modified file: src/game/mod.rs+1 additions−1 deletions
@@ -31,7 +31,7 @@ mod utility;
3131
3232 #[cfg(test)]
3333 pub use body::Pending;
34-pub use body::{Body, Grip};
34+pub use body::{Arrival, Body, Grip};
3535 pub use cause::{Cause, Ending, RunStatus};
3636 pub use clinic::Clinic;
3737 pub use floor::{Floor, FloorState, Observer};
Modified file: src/game/run.rs+7 additions−1 deletions
@@ -358,7 +358,13 @@ impl Run {
358358 .partition(|p| p.due <= self.turn);
359359 self.body.pending = waiting;
360360 for p in due {
361- self.gain(p.length, p.girth, p.foreskin);
361+ let foreskin = self.gain(p.length, p.girth, p.foreskin);
362+ let arrived = self.body.arrived.get_or_insert_default();
363+ arrived.length += p.length;
364+ arrived.girth += p.girth;
365+ if let Some(grown) = foreskin {
366+ *arrived.foreskin.get_or_insert(0.0) += grown;
367+ }
362368 }
363369 }
364370
Modified file: src/headers.rs+1 additions
@@ -9,6 +9,7 @@ pub const X_FLOOR: HeaderName = HeaderName::from_static("x-floor");
99 pub const X_FORESKIN: HeaderName = HeaderName::from_static("x-foreskin");
1010 pub const X_FRICTION: HeaderName = HeaderName::from_static("x-friction");
1111 pub const X_FORUM_NOTE: HeaderName = HeaderName::from_static("x-forum-note");
12+pub const X_GAINS_ARRIVED: HeaderName = HeaderName::from_static("x-gains-arrived");
1213 pub const X_GIRTH: HeaderName = HeaderName::from_static("x-girth");
1314 pub const X_LENGTH: HeaderName = HeaderName::from_static("x-length");
1415 pub const X_OBSERVED_BY: HeaderName = HeaderName::from_static("x-observed-by");
Modified file: src/http/run.rs+9 additions−2 deletions
@@ -16,8 +16,8 @@ use crate::{
1616 balance::{FRICTION_MAX, SUSPICION_LIMIT},
1717 },
1818 headers::{
19- X_CASH, X_EQ, X_FLOOR, X_FORESKIN, X_FRICTION, X_GIRTH, X_LENGTH, X_OBSERVED_BY,
20- X_SELF_REPORTED_LENGTH, X_SHAME, X_STAMINA, X_SUSPICION,
19+ X_CASH, X_EQ, X_FLOOR, X_FORESKIN, X_FRICTION, X_GAINS_ARRIVED, X_GIRTH, X_LENGTH,
20+ X_OBSERVED_BY, X_SELF_REPORTED_LENGTH, X_SHAME, X_STAMINA, X_SUSPICION,
2121 },
2222 locale::Locale,
2323 problem::{PROBLEM_BASE, Problem},
@@ -166,6 +166,13 @@ fn stats(gauges: &Gauges, shame: u32) -> Vec<(HeaderName, String)> {
166166 stats.push((header::VIA, "1.1 mum".into()));
167167 stats.push((header::VARY, "Mum".into()));
168168 }
169+ if let Some(arrived) = &gauges.arrived {
170+ let mut gains = format!("length={:.3}, girth={:.3}", arrived.length, arrived.girth);
171+ if let Some(foreskin) = arrived.foreskin {
172+ gains.push_str(&format!(", foreskin={foreskin:.3}"));
173+ }
174+ stats.push((X_GAINS_ARRIVED, gains));
175+ }
169176 // RFC 9111 obsoleted Warning, which is why nobody heeds it.
170177 for warning in &gauges.warnings {
171178 stats.push((header::WARNING, format!("199 - \"{warning}\"")));
Modified file: src/tests/turns.rs+26 additions
@@ -225,3 +225,29 @@ async fn energy_drinks_add_three_stamina() {
225225 let again = send(&app, "JELQ", "/runs/cafebabe?technique=energy", &[], "").await;
226226 assert_eq!(again.status(), StatusCode::FAILED_DEPENDENCY);
227227 }
228+
229+#[tokio::test]
230+async fn queued_gains_announce_their_arrival() {
231+ let app = seeded_with(Floor::Bedroom, |run| {
232+ run.body.eq = 75;
233+ run.body.foreskin = Some(1.0);
234+ run.body.pending.push(crate::game::Pending {
235+ due: run.turn + 1,
236+ length: 0.105,
237+ girth: 0.0,
238+ foreskin: 0.03,
239+ });
240+ });
241+ let held = send(&app, "HOLD", "/runs/cafebabe", &[], "").await;
242+ assert_eq!(
243+ header(&held, "x-gains-arrived"),
244+ "length=0.105, girth=0.000, foreskin=0.030"
245+ );
246+ assert!(
247+ warnings(&held)
248+ .iter()
249+ .any(|w| w.contains("the queued gains have arrived"))
250+ );
251+ let later = send(&app, "GET", "/runs/cafebabe", &[], "").await;
252+ assert!(later.headers().get("x-gains-arrived").is_none());
253+}