atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0

Commit ea51d9fda49d

tarball

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

4 files changed+59-10
game: report a rupture as Ruptured in X-EQ, and split the run counters from the body in the UI
Modified file: src/game/arousal.rs+12 additions−2 deletions
@@ -1,5 +1,5 @@
11 use super::{
2- Run,
2+ Cause, Ending, Run,
33 balance::{DANGER_EQ, EQ_BAND, HOT_EQ, PREMATURE_EQ, RING_RUPTURE_LINE, RUPTURE_LINE},
44 };
55
@@ -15,6 +15,7 @@ pub enum Arousal {
1515 Straining,
1616 Imminent,
1717 Priapic,
18+ Ruptured,
1819 }
1920
2021 impl Arousal {
@@ -29,6 +30,7 @@ impl Arousal {
2930 Self::Straining => "Straining",
3031 Self::Imminent => "Imminent",
3132 Self::Priapic => "Priapic",
33+ Self::Ruptured => "Ruptured",
3234 }
3335 }
3436 }
@@ -43,7 +45,15 @@ impl Run {
4345 }
4446
4547 pub fn arousal(&self) -> Arousal {
46- if self.body.conditions.priapism_turns > 0 {
48+ if matches!(
49+ self.ended,
50+ Some(Ending {
51+ cause: Cause::Ruptured { .. },
52+ ..
53+ })
54+ ) {
55+ Arousal::Ruptured
56+ } else if self.body.conditions.priapism_turns > 0 {
4757 Arousal::Priapic
4858 } else if self.hotter_than(PREMATURE_EQ) {
4959 Arousal::Imminent
Modified file: src/game/danger.rs+3 additions−1 deletions
@@ -23,7 +23,9 @@ impl Arousal {
2323 fn danger(self) -> Option<Danger> {
2424 match self {
2525 Self::Cold | Self::Swollen | Self::Throbbing => Some(Danger::Dangerous),
26- Self::Straining | Self::Imminent | Self::Priapic => Some(Danger::Critical),
26+ Self::Straining | Self::Imminent | Self::Priapic | Self::Ruptured => {
27+ Some(Danger::Critical)
28+ }
2729 Self::Flaccid | Self::Warm | Self::Ideal => None,
2830 }
2931 }
Modified file: src/tests/turns.rs+28 additions
@@ -495,3 +495,31 @@ async fn a_served_floor_says_so() {
495495 let view = send(&forum, "GET", "/runs/cafebabe", &[], "").await;
496496 assert!(view.headers().get("x-floor-over").is_none());
497497 }
498+
499+#[tokio::test]
500+async fn a_rupture_names_itself_in_x_eq() {
501+ for seed in 0..500u64 {
502+ let app = app_with(Floor::Bedroom, seed, |run| {
503+ run.body.eq = 92;
504+ run.body.conditions.friction_burn = true;
505+ });
506+ let turn = send(
507+ &app,
508+ "JELQ",
509+ "/runs/cafebabe?technique=stretch&intensity=10",
510+ &[],
511+ "",
512+ )
513+ .await;
514+ if turn
515+ .headers()
516+ .get("x-run-over")
517+ .map(|v| v.to_str().unwrap())
518+ == Some("ruptured")
519+ {
520+ assert_eq!(header(&turn, "x-eq"), "Ruptured");
521+ return;
522+ }
523+ }
524+ panic!("no seed ruptured");
525+}
Modified file: src/ui.html+16 additions−7 deletions
@@ -34,6 +34,8 @@ pre, li, p, td, label { overflow-wrap: anywhere; }
3434 <p><input type="submit" id="send" value="Send" style="font-size: 1.3em"></p>
3535
3636 <h3>Stats</h3>
37+<table id="progress" border="1" cellspacing="0" cellpadding="3"></table>
38+<br>
3739 <table id="stats" border="1" cellspacing="0" cellpadding="3"></table>
3840
3941 <h3>Resource</h3>
@@ -57,11 +59,13 @@ let stocked = null;
5759 let excuses = [];
5860 let nonce = null;
5961 let inventorySeq = 0;
60-const STATS = [
62+const PROGRESS = [
6163 { header: "x-turn", label: "Turn" },
6264 { header: "x-floor-turn", label: "Floor turn" },
6365 { header: "x-floor", label: "Floor", better: 1 },
6466 { header: "x-cash", label: "Cash", better: 1 },
67+];
68+const STATS = [
6569 { header: "x-self-reported-length", label: "Self-reported length", better: 1, places: 2, measured: { header: "x-length", label: "Length" } },
6670 { header: "x-girth", label: "Girth", better: 1, places: 2 },
6771 { header: "x-foreskin", label: "Foreskin", better: 1, places: 2 },
@@ -74,7 +78,7 @@ const STATS = [
7478 const AROUSAL = ["Flaccid", "Cold", "Warm", "Ideal", "Swollen", "Throbbing", "Straining", "Imminent"];
7579 const DANGER_COLOURS = { dangerous: "#b8860b", critical: "red" };
7680 let lastStats = {};
77-const QUIET = new Set(["link", "set-cookie", "server", "x-powered-by", "cache-control", "content-length", "content-type", "date"].concat(STATS.flatMap(s => [s.header, s.measured?.header]).filter(Boolean)));
81+const QUIET = new Set(["link", "set-cookie", "server", "x-powered-by", "cache-control", "content-length", "content-type", "date"].concat(PROGRESS.concat(STATS).flatMap(s => [s.header, s.measured?.header]).filter(Boolean)));
7882
7983 const FIELDS = {
8084 seed: { label: "seed (unranked)", kind: "text" },
@@ -212,10 +216,15 @@ function chooseTarget(t) {
212216 }
213217
214218 function renderStats(headers) {
215- const table = $("stats");
216- table.replaceChildren();
217219 const dangers = Object.fromEntries((headers && headers["x-danger"] || "").split(",").filter(Boolean).map(d => d.trim().split("=")));
218- const stats = STATS.map(s => s.measured && headers && s.measured.header in headers ? { ...s, ...s.measured, unmeasured: s.header } : s);
220+ renderTable($("progress"), PROGRESS, headers, dangers);
221+ renderTable($("stats"), STATS, headers, dangers);
222+ lastStats = headers || {};
223+}
224+
225+function renderTable(table, list, headers, dangers) {
226+ table.replaceChildren();
227+ const stats = list.map(s => s.measured && headers && s.measured.header in headers ? { ...s, ...s.measured, unmeasured: s.header } : s);
219228 for (let i = 0; i < stats.length; i += 2) {
220229 const row = el("tr");
221230 for (const stat of stats.slice(i, i + 2)) {
@@ -230,11 +239,11 @@ function renderStats(headers) {
230239 }
231240 table.append(row);
232241 }
233- lastStats = headers || {};
234242 }
235243
236244 function fromIdeal(arousal) {
237- return arousal === "Priapic" ? AROUSAL.length : Math.abs(AROUSAL.indexOf(arousal) - AROUSAL.indexOf("Ideal"));
245+ const rung = AROUSAL.indexOf(arousal);
246+ return rung < 0 ? AROUSAL.length : Math.abs(rung - AROUSAL.indexOf("Ideal"));
238247 }
239248
240249 function change(stat, value) {