atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0

Commit 5cc6ab966f30

tarball

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

4 files changed+102-0
game: the melon only waits while you keep raiding

The offer used to stand for the rest of the floor, so a declined
watermelon stayed claimable through the direct POST after any amount
of jelqing, a free EQ dump for anyone who closed the fridge. The
offer now dies with the first non-kitchen turn, and a later raid has
to win its ratio roll again instead of re-serving it by default.
Modified file: src/game/forum.rs+1 additions
@@ -117,6 +117,7 @@ impl Run {
117117 self.touch();
118118 self.turn += 1;
119119 self.here.turns += 1;
120+ self.end_kitchen_visit();
120121 self.realise();
121122 if self.chance(self.rupture_odds()) {
122123 return Err(self.rupture());
Modified file: src/game/mod.rs+5 additions
@@ -207,6 +207,11 @@ impl Run {
207207 self.eq = eq.max(floor);
208208 }
209209
210+ /// A melon offer stands only while the raid continues. Any other turn ends it.
211+ pub(super) fn end_kitchen_visit(&mut self) {
212+ self.here.melon_offered = false;
213+ }
214+
210215 fn fresh_nonce(&mut self) -> String {
211216 format!("{:016x}", self.rng.random::<u64>())
212217 }
Modified file: src/game/turn.rs+2 additions
@@ -371,6 +371,7 @@ impl Run {
371371 self.touch();
372372 self.turn += 1;
373373 self.here.turns += 1;
374+ self.end_kitchen_visit();
374375 self.realise();
375376 if self.items.extender {
376377 self.length += EXTENDER_GAIN;
@@ -458,6 +459,7 @@ impl Run {
458459 self.touch();
459460 self.turn += 1;
460461 self.here.turns += 1;
462+ self.end_kitchen_visit();
461463 self.realise();
462464 let was_below = self.suspicion < SUSPICION_LIMIT;
463465 if self.chance(REFLECTION) {
Modified file: src/tests.rs+94 additions
@@ -1244,6 +1244,100 @@ async fn the_watermelon_offers_itself_above_ninety() {
12441244 }
12451245
12461246 #[tokio::test]
1247+async fn a_melon_offer_does_not_survive_a_non_kitchen_turn() {
1248+ for seed in 0..60u64 {
1249+ let jelq_app = app_with(Floor::Bedroom, seed, |run| {
1250+ run.warmed_up = true;
1251+ run.here.melon_offered = true;
1252+ });
1253+ let stroked = send(
1254+ &jelq_app,
1255+ "JELQ",
1256+ "/runs/cafebabe?technique=wet&intensity=2",
1257+ &[],
1258+ "",
1259+ )
1260+ .await;
1261+ if stroked.status() != StatusCode::OK && stroked.status() != StatusCode::PARTIAL_CONTENT {
1262+ continue;
1263+ }
1264+ let bite = send(
1265+ &jelq_app,
1266+ "POST",
1267+ "/runs/cafebabe/kitchen/watermelon",
1268+ &[],
1269+ "",
1270+ )
1271+ .await;
1272+ assert_eq!(
1273+ bite.status(),
1274+ StatusCode::CONFLICT,
1275+ "seed {seed} kept the melon through a jelq"
1276+ );
1277+
1278+ let hold_app = app_with(Floor::Bedroom, seed, |run| {
1279+ run.stamina = 2;
1280+ run.here.melon_offered = true;
1281+ });
1282+ let held = send(&hold_app, "HOLD", "/runs/cafebabe", &[], "").await;
1283+ if held.status() != StatusCode::NO_CONTENT {
1284+ continue;
1285+ }
1286+ let bite = send(
1287+ &hold_app,
1288+ "POST",
1289+ "/runs/cafebabe/kitchen/watermelon",
1290+ &[],
1291+ "",
1292+ )
1293+ .await;
1294+ assert_eq!(
1295+ bite.status(),
1296+ StatusCode::CONFLICT,
1297+ "seed {seed} kept the melon through a hold"
1298+ );
1299+
1300+ let read_app = app_with(Floor::Bedroom, seed, |run| {
1301+ run.story = Story::ClosedTab;
1302+ run.here.melon_offered = true;
1303+ });
1304+ let read = send(&read_app, "POST", "/runs/cafebabe/re-read", &[], "").await;
1305+ if read.status() == StatusCode::INTERNAL_SERVER_ERROR {
1306+ continue;
1307+ }
1308+ let bite = send(
1309+ &read_app,
1310+ "POST",
1311+ "/runs/cafebabe/kitchen/watermelon",
1312+ &[],
1313+ "",
1314+ )
1315+ .await;
1316+ assert_eq!(
1317+ bite.status(),
1318+ StatusCode::CONFLICT,
1319+ "seed {seed} kept the melon through a re-read"
1320+ );
1321+ }
1322+
1323+ let mut stood = 0;
1324+ for seed in 0..60u64 {
1325+ let mut run = Run::new("cafebabe".into(), "cafe".into(), seed);
1326+ run.eq = 95;
1327+ run.here.melon_offered = true;
1328+ if run.raid_kitchen().is_err() {
1329+ continue;
1330+ }
1331+ stood += 1;
1332+ assert!(
1333+ run.here.melon_offered,
1334+ "seed {seed} lost the melon to another raid"
1335+ );
1336+ }
1337+ assert!(stood > 40, "raids at eq 95 rarely rupture");
1338+}
1339+
1340+#[tokio::test]
12471341 async fn the_kitchen_closes_after_two_tellings() {
12481342 let mut closed = None;
12491343 for seed in 0..300u64 {