atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0

Commit e76ab808e7ad

tarball

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

4 files changed+25-17
ui: explain partial bodies and exhaustion where they happen

A 206 body looked like the earlier dropped-reply bug, so the log now notes which bytes arrived and why. Recovery from the refractory period used to wait for the next JELQ, which left stamina reading zero after the player had rested, so any request to the run now ends it once it has run out.
Modified file: src/game/mod.rs+19 additions−15 deletions
@@ -261,25 +261,29 @@ impl Run {
261261 cause.problem().with("turn", self.turn)
262262 }
263263
264+ /// Ends the refractory period once it has run out, whatever the request.
265+ pub fn recover(&mut self, now: Instant) {
266+ if self.refractory_until.is_some_and(|until| now >= until) {
267+ self.refractory_until = None;
268+ self.stamina = self.max_stamina();
269+ self.eq = STARTING_EQ;
270+ self.warmed_up = false;
271+ self.touch();
272+ }
273+ }
274+
264275 fn check_refractory(&mut self, now: Instant) -> Result<(), Problem> {
276+ self.recover(now);
265277 let Some(until) = self.refractory_until else {
266278 return Ok(());
267279 };
268- if now < until {
269- let seconds = (until - now).as_secs() + 1;
270- return Err(Problem::new(
271- StatusCode::TOO_MANY_REQUESTS,
272- "overtrained",
273- "Stamina is exhausted. The refractory period is in effect.",
274- )
275- .header(header::RETRY_AFTER, seconds.to_string()));
276- }
277- self.refractory_until = None;
278- self.stamina = self.max_stamina();
279- self.eq = STARTING_EQ;
280- self.warmed_up = false;
281- self.touch();
282- Ok(())
280+ let seconds = until.saturating_duration_since(now).as_secs() + 1;
281+ Err(Problem::new(
282+ StatusCode::TOO_MANY_REQUESTS,
283+ "overtrained",
284+ "Stamina is exhausted. The refractory period is in effect.",
285+ )
286+ .header(header::RETRY_AFTER, seconds.to_string()))
283287 }
284288
285289 fn exhaust(&mut self, now: Instant) {
Modified file: src/game/turn.rs−2 deletions
@@ -246,14 +246,12 @@ impl Run {
246246 if blocked {
247247 return Vec::new();
248248 }
249- let rested = self.refractory_until.is_some();
250249 Technique::ALL
251250 .into_iter()
252251 .filter(|&technique| match technique {
253252 Technique::Utility(Utility::Warmup) => true,
254253 Technique::Utility(Utility::Ice) => self.items.ice_packs > 0,
255254 Technique::Utility(Utility::Supplement) => self.items.supplements > 0,
256- _ if rested => false,
257255 _ => self.access(technique).is_ok() && self.check_storage().is_ok(),
258256 })
259257 .collect()
Modified file: src/http/run.rs+1 additions
@@ -57,6 +57,7 @@ pub fn serve(state: &AppState, mut ctx: Ctx, target: &RunResource) -> Response {
5757 .into_response();
5858 }
5959 run.last_seen = ctx.now;
60+ run.recover(ctx.now);
6061 run.observed = None;
6162 let response =
6263 gate(run, &ctx, target.scope).unwrap_or_else(|| target.resource.dispatch(run, &ctx));
Modified file: src/ui.html+5 additions
@@ -209,6 +209,8 @@ function renderAlerts(list) {
209209 alerts.push(titled(text) + ".");
210210 }
211211 }
212+ const stamina = (list || []).find(([name]) => name === "x-stamina");
213+ if (stamina && stamina[1].startsWith("0/")) alerts.push("Exhausted. Wait out the refractory period, then GET the run.");
212214 for (const alert of alerts) box.append(el("h2", { textContent: alert }));
213215 }
214216
@@ -327,7 +329,10 @@ function describe(r) {
327329 for (const key of ["type", "title", "status", "detail"]) delete rest[key];
328330 if (Object.keys(rest).length) lines.push(short(JSON.stringify(rest, null, 2)).replace(/^/gm, " "));
329331 } else if (r.body) {
332+ const range = (r.headers.find(h => h[0] === "content-range") || [])[1] || "";
333+ const partial = r.status === 206 && range.match(/^bytes (\d+)-(\d+)\/(\d+)$/);
330334 lines.push(" " + r.body);
335+ if (partial) lines.push(" [bytes " + partial[1] + "-" + partial[2] + " of " + partial[3] + ". A 206 carries only part of the representation, as RFC 9110 requires.]");
331336 }
332337 return lines.join("\n");
333338 }