R-224: a body at zero absorbed damage for free

Hit points clamp at zero, so further damage to somebody already there changed
nothing — and a STABILISED agent absorbed it forever, because the count was held
and there was nothing else left to move. You could empty a magazine into someone
bleeding out and the system would not notice.

Damage at zero now breaks the stabilisation and costs a round of the count, and
hitLocationRoll calls died() when that meets the limit.

Found by playing LOW VERGE until it killed somebody: two agents who were
medically stable died in the water on a failed tidal extraction.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
slaguru666
2026-08-10 22:24:55 +01:00
co-authored by Claude Opus 5
parent 89ce557355
commit 5c114601a9
32 changed files with 140 additions and 80 deletions
+37
View File
@@ -3860,3 +3860,40 @@ journal to answer "where are we and how long have we got".
Checked across 300 seeds for `[object Object]`, `undefined` and `NaN` in every
generated page: clean. One real leak found and fixed on the way — the group's taboo
and desire are objects with a `.text`, and the brief was printing the object.
## LOW VERGE — played until it killed somebody
Field Lead, Physician, Recording Officer, Translation Officer at a boarding school out
of term. The brief did its new job: Where, When, the job, what to expect, and the way
back, all before the prose. Four phases.
The rival desk never became a fight — the Field Lead rolled a natural 1 on Status 15%
and they stood down, which is the leverage the Opposition page names. The danger was
never them. The Pressure page had already declared the price: *"There is a body that
has to be carried, and it is one of ours."*
Two phases failed, the Containment phase failed, and the other side's weather came
through a gap at the true scale. Three of the four went to zero in a single exposure
and the Physician — 48% First Aid, on four hit points herself — stabilised all three,
one after another, and then did it again after the next wave.
Then the extraction failed on a tidal causeway with the window already past. Marek
Achterberg and Tomas Vance died in the water, both at Coherence 10, both **recorded
properly — a death in service, a form, a pension, a name that stays where it was put.**
Nkechi Quist came home stable at zero. Ambaro Vance walked off the causeway on four
hit points having kept three people alive for six rounds and lost two of them anyway.
**R-224 · a body at zero absorbed damage for free.** Found by this play-through and it
is what killed them. Hit points clamp at zero, so further damage to somebody already
there changed nothing — and a STABILISED agent absorbed it forever, because the count
was held and there was nothing else left to move. You could empty a magazine into
someone bleeding out on the floor and the system would not notice. Damage at zero now
breaks the stabilisation and costs a round of the count, and `hitLocationRoll` calls
`died()` directly when that meets the limit. That is precisely how the causeway killed
two people who were medically stable four minutes earlier.
**Worth noting for the Last Entry table:** both died at Coherence 10 and so got the
dullest of the four entries. Coherence is normally spent on the way home, and these
two never got there — an agent who dies mid-case is likely to be *well* filed. The
interesting entries belong to people who die after a bad crossing, which is the right
way round but means the table's bottom half will be rarer than its top.
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-22:10:05.167695 172fa3000 Recovering log #3
2026/08/10-22:10:05.167719 172fa3000 Level-0 table #5: started
2026/08/10-22:10:05.167831 172fa3000 Level-0 table #5: 1902 bytes OK
2026/08/10-22:10:05.168131 172fa3000 Delete type=0 #3
2026/08/10-22:10:05.168177 172fa3000 Delete type=3 #2
2026/08/10-22:21:57.701683 1776e7000 Recovering log #3
2026/08/10-22:21:57.701703 1776e7000 Level-0 table #5: started
2026/08/10-22:21:57.701794 1776e7000 Level-0 table #5: 1902 bytes OK
2026/08/10-22:21:57.701993 1776e7000 Delete type=0 #3
2026/08/10-22:21:57.702019 1776e7000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-22:09:56.857234 172fe3000 Delete type=3 #1
2026/08/10-22:21:49.417954 172017000 Delete type=3 #1
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-22:10:05.175210 172797000 Recovering log #3
2026/08/10-22:10:05.175229 172797000 Level-0 table #5: started
2026/08/10-22:10:05.175333 172797000 Level-0 table #5: 4129 bytes OK
2026/08/10-22:10:05.175537 172797000 Delete type=0 #3
2026/08/10-22:10:05.175564 172797000 Delete type=3 #2
2026/08/10-22:21:57.709136 1776e7000 Recovering log #3
2026/08/10-22:21:57.709155 1776e7000 Level-0 table #5: started
2026/08/10-22:21:57.709295 1776e7000 Level-0 table #5: 4129 bytes OK
2026/08/10-22:21:57.709506 1776e7000 Delete type=0 #3
2026/08/10-22:21:57.709532 1776e7000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-22:09:56.883542 172fe3000 Delete type=3 #1
2026/08/10-22:21:49.443274 17302f000 Delete type=3 #1
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-22:10:05.168741 172797000 Recovering log #3
2026/08/10-22:10:05.168838 172797000 Level-0 table #5: started
2026/08/10-22:10:05.169097 172797000 Level-0 table #5: 27954 bytes OK
2026/08/10-22:10:05.169298 172797000 Delete type=0 #3
2026/08/10-22:10:05.169329 172797000 Delete type=3 #2
2026/08/10-22:21:57.702603 176edb000 Recovering log #3
2026/08/10-22:21:57.702692 176edb000 Level-0 table #5: started
2026/08/10-22:21:57.702943 176edb000 Level-0 table #5: 27954 bytes OK
2026/08/10-22:21:57.703215 176edb000 Delete type=0 #3
2026/08/10-22:21:57.703261 176edb000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-22:09:56.859443 1727d7000 Delete type=3 #1
2026/08/10-22:21:49.419830 172017000 Delete type=3 #1
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-22:10:05.160943 173fbb000 Recovering log #3
2026/08/10-22:10:05.161166 173fbb000 Level-0 table #5: started
2026/08/10-22:10:05.161661 173fbb000 Level-0 table #5: 60660 bytes OK
2026/08/10-22:10:05.161946 173fbb000 Delete type=0 #3
2026/08/10-22:10:05.162005 173fbb000 Delete type=3 #2
2026/08/10-22:21:57.694343 177ef3000 Recovering log #3
2026/08/10-22:21:57.694579 177ef3000 Level-0 table #5: started
2026/08/10-22:21:57.695401 177ef3000 Level-0 table #5: 60660 bytes OK
2026/08/10-22:21:57.695734 177ef3000 Delete type=0 #3
2026/08/10-22:21:57.695824 177ef3000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-22:09:56.870862 172fe3000 Delete type=3 #1
2026/08/10-22:21:49.430202 172823000 Delete type=3 #1
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-22:10:05.165461 172fa3000 Recovering log #3
2026/08/10-22:10:05.165486 172fa3000 Level-0 table #5: started
2026/08/10-22:10:05.165592 172fa3000 Level-0 table #5: 3295 bytes OK
2026/08/10-22:10:05.165831 172fa3000 Delete type=0 #3
2026/08/10-22:10:05.165879 172fa3000 Delete type=3 #2
2026/08/10-22:21:57.699606 1776e7000 Recovering log #3
2026/08/10-22:21:57.699632 1776e7000 Level-0 table #5: started
2026/08/10-22:21:57.699749 1776e7000 Level-0 table #5: 3295 bytes OK
2026/08/10-22:21:57.699987 1776e7000 Delete type=0 #3
2026/08/10-22:21:57.700037 1776e7000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-22:09:56.877345 172fe3000 Delete type=3 #1
2026/08/10-22:21:49.437043 172017000 Delete type=3 #1
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-22:10:05.158567 173fbb000 Recovering log #3
2026/08/10-22:10:05.158795 173fbb000 Level-0 table #5: started
2026/08/10-22:10:05.159257 173fbb000 Level-0 table #5: 54921 bytes OK
2026/08/10-22:10:05.159544 173fbb000 Delete type=0 #3
2026/08/10-22:10:05.159580 173fbb000 Delete type=3 #2
2026/08/10-22:21:57.691875 177ef3000 Recovering log #3
2026/08/10-22:21:57.692083 177ef3000 Level-0 table #5: started
2026/08/10-22:21:57.692589 177ef3000 Level-0 table #5: 54921 bytes OK
2026/08/10-22:21:57.692893 177ef3000 Delete type=0 #3
2026/08/10-22:21:57.692928 177ef3000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-22:09:56.866210 171fcb000 Delete type=3 #1
2026/08/10-22:21:49.425591 172017000 Delete type=3 #1
Binary file not shown.
Binary file not shown.
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-22:10:05.171339 172797000 Recovering log #3
2026/08/10-22:10:05.171403 172797000 Level-0 table #5: started
2026/08/10-22:10:05.171720 172797000 Level-0 table #5: 24552 bytes OK
2026/08/10-22:10:05.172072 172797000 Delete type=0 #3
2026/08/10-22:10:05.172108 172797000 Delete type=3 #2
2026/08/10-22:21:57.705201 1776e7000 Recovering log #3
2026/08/10-22:21:57.705262 1776e7000 Level-0 table #5: started
2026/08/10-22:21:57.705576 1776e7000 Level-0 table #5: 24724 bytes OK
2026/08/10-22:21:57.705849 1776e7000 Delete type=0 #3
2026/08/10-22:21:57.705894 1776e7000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-22:09:56.881569 172fe3000 Delete type=3 #1
2026/08/10-22:21:49.441356 17302f000 Delete type=3 #1
Binary file not shown.
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-22:10:05.163199 173fbb000 Recovering log #3
2026/08/10-22:10:05.163244 173fbb000 Level-0 table #5: started
2026/08/10-22:10:05.163412 173fbb000 Level-0 table #5: 6679 bytes OK
2026/08/10-22:10:05.163630 173fbb000 Delete type=0 #3
2026/08/10-22:10:05.163662 173fbb000 Delete type=3 #2
2026/08/10-22:21:57.697182 177ef3000 Recovering log #3
2026/08/10-22:21:57.697227 177ef3000 Level-0 table #5: started
2026/08/10-22:21:57.697417 177ef3000 Level-0 table #5: 6679 bytes OK
2026/08/10-22:21:57.697764 177ef3000 Delete type=0 #3
2026/08/10-22:21:57.697816 177ef3000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-22:09:56.849645 171fcb000 Delete type=3 #1
2026/08/10-22:21:49.411033 172823000 Delete type=3 #1
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-22:10:05.172842 173fbb000 Recovering log #3
2026/08/10-22:10:05.172885 173fbb000 Level-0 table #5: started
2026/08/10-22:10:05.173125 173fbb000 Level-0 table #5: 16892 bytes OK
2026/08/10-22:10:05.173406 173fbb000 Delete type=0 #3
2026/08/10-22:10:05.173455 173fbb000 Delete type=3 #2
2026/08/10-22:21:57.706581 177ef3000 Recovering log #3
2026/08/10-22:21:57.706617 177ef3000 Level-0 table #5: started
2026/08/10-22:21:57.706867 177ef3000 Level-0 table #5: 16892 bytes OK
2026/08/10-22:21:57.707171 177ef3000 Delete type=0 #3
2026/08/10-22:21:57.707228 177ef3000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-22:09:56.875668 1727d7000 Delete type=3 #1
2026/08/10-22:21:49.435401 172017000 Delete type=3 #1
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-22:10:05.174148 172fa3000 Recovering log #3
2026/08/10-22:10:05.174171 172fa3000 Level-0 table #5: started
2026/08/10-22:10:05.174281 172fa3000 Level-0 table #5: 3739 bytes OK
2026/08/10-22:10:05.174533 172fa3000 Delete type=0 #3
2026/08/10-22:10:05.174562 172fa3000 Delete type=3 #2
2026/08/10-22:21:57.707994 1766cf000 Recovering log #3
2026/08/10-22:21:57.708022 1766cf000 Level-0 table #5: started
2026/08/10-22:21:57.708162 1766cf000 Level-0 table #5: 3739 bytes OK
2026/08/10-22:21:57.708436 1766cf000 Delete type=0 #3
2026/08/10-22:21:57.708469 1766cf000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-22:09:56.873729 171fcb000 Delete type=3 #1
2026/08/10-22:21:49.433240 172017000 Delete type=3 #1
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-22:10:05.164350 1737af000 Recovering log #3
2026/08/10-22:10:05.164377 1737af000 Level-0 table #5: started
2026/08/10-22:10:05.164513 1737af000 Level-0 table #5: 5105 bytes OK
2026/08/10-22:10:05.164819 1737af000 Delete type=0 #3
2026/08/10-22:10:05.164862 1737af000 Delete type=3 #2
2026/08/10-22:21:57.698491 1766cf000 Recovering log #3
2026/08/10-22:21:57.698520 1766cf000 Level-0 table #5: started
2026/08/10-22:21:57.698674 1766cf000 Level-0 table #5: 5105 bytes OK
2026/08/10-22:21:57.698906 1766cf000 Delete type=0 #3
2026/08/10-22:21:57.698937 1766cf000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-22:09:56.853599 172fe3000 Delete type=3 #1
2026/08/10-22:21:49.414115 172017000 Delete type=3 #1
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-22:10:05.170089 172fa3000 Recovering log #3
2026/08/10-22:10:05.170122 172fa3000 Level-0 table #5: started
2026/08/10-22:10:05.170277 172fa3000 Level-0 table #5: 5012 bytes OK
2026/08/10-22:10:05.170539 172fa3000 Delete type=0 #3
2026/08/10-22:10:05.170582 172fa3000 Delete type=3 #2
2026/08/10-22:21:57.704003 177ef3000 Recovering log #3
2026/08/10-22:21:57.704031 177ef3000 Level-0 table #5: started
2026/08/10-22:21:57.704190 177ef3000 Level-0 table #5: 5012 bytes OK
2026/08/10-22:21:57.704463 177ef3000 Delete type=0 #3
2026/08/10-22:21:57.704506 177ef3000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-22:09:56.861512 1737ef000 Delete type=3 #1
2026/08/10-22:21:49.421696 172823000 Delete type=3 #1
+5 -5
View File
@@ -1,5 +1,5 @@
2026/08/10-22:10:05.166515 172797000 Recovering log #3
2026/08/10-22:10:05.166560 172797000 Level-0 table #5: started
2026/08/10-22:10:05.166726 172797000 Level-0 table #5: 7240 bytes OK
2026/08/10-22:10:05.166989 172797000 Delete type=0 #3
2026/08/10-22:10:05.167039 172797000 Delete type=3 #2
2026/08/10-22:21:57.700674 176edb000 Recovering log #3
2026/08/10-22:21:57.700710 176edb000 Level-0 table #5: started
2026/08/10-22:21:57.700909 176edb000 Level-0 table #5: 7240 bytes OK
2026/08/10-22:21:57.701117 176edb000 Delete type=0 #3
2026/08/10-22:21:57.701147 176edb000 Delete type=3 #2
+1 -1
View File
@@ -1 +1 @@
2026/08/10-22:09:56.855434 1727d7000 Delete type=3 #1
2026/08/10-22:21:49.416039 172017000 Delete type=3 #1
+21 -2
View File
@@ -1053,10 +1053,29 @@ export async function hitLocationRoll({ target, damage, mode = "ranged",
const dmg = foundry.utils.duplicate(sys.locationDamage ?? {});
dmg[loc.id] = res.locationTaken;
await target.update({
const upd = {
"system.locationDamage": dmg,
"system.hp.value": Math.max(0, (sys.hp?.value ?? 0) - res.damage)
});
};
// R-224. Hit points clamp at zero, so a body already at zero absorbed further
// damage for free — and a STABILISED agent absorbed it forever, because the count
// was held and there was nothing else left to move. You could empty a magazine
// into someone bleeding out on the floor and the system would not notice. Damage
// to somebody at zero breaks the stabilisation and costs them a round of the
// count they have left.
const wasAtZero = (Number(sys.hp?.value) || 0) <= 0;
const hurtAgain = res.damage > 0;
if (wasAtZero && hurtAgain && !sys.dying?.dead) {
upd["system.dying.stabilised"] = false;
upd["system.dying.rounds"] = (Number(sys.dying?.rounds) || 0) + 1;
}
await target.update(upd);
if (upd["system.dying.rounds"] !== undefined
&& upd["system.dying.rounds"] >= dyingLimitFor(sys.majorWound?.value)) {
await died(target);
}
return { location: loc, ...res };
}
+4
View File
@@ -321,6 +321,10 @@ ${table(["Hit points", "You are"], [
own <strong>Major Wound</strong> threshold you are <strong>dead</strong> — so the same
number that decides how much damage you can take standing up also decides how long you
have on the floor. A minimum of ${DYING.minCount} rounds applies however slight you are.</p>
<p><strong>Being hurt again at zero</strong> breaks any stabilisation and costs a
round of the count. Hit points cannot go below zero, so this is what further damage to
a body on the floor actually does — and it is why leaving somebody stable in a place
that is still dangerous is not the same as getting them out.</p>
<p><strong>Stabilising</strong> takes ${DYING.stabiliseWith.join(" or ")} and stops the
count. It does not put you back on your feet: you are still at zero and still
unconscious until somebody heals you above it. Healing above zero clears the count.