native: run the imp's attack from the tic that reaches it - #477
Merged
Conversation
The state cycle now enters a frame carrying A_TroopAttack or A_SargAttack rather than saying the tic could not be produced. The attack primitive answers with the angle A_FaceTarget leaves, the flags it clears, the damage a claw that reaches does and how many numbers the call drew, and the tic puts each of those where it belongs: the angle and the flags over what the state cycle and the chase left, the damage through P_DamageMobj with the attacker as both the inflictor and the source, and the draws behind every look and chase the tic ran. P_CheckMeleeRange ends in P_CheckSight, so the attackers ask alongside the lookers, the chasers and the hearers in the one sight call a tic makes. Three cases say the tic could not be produced. More than one thing reaching a routine would have the second draw from an index the first moves. The fireball wants a missile spawned. A claw that kills owes the kill count and whatever the corpse drops. demo3 reaches the routine at gametic 169 and the imp throws a fireball from 407 map units away, so the first divergence stays there, at the two numbers the spawn draws. What the tic does carry through is the imp's frame, its angle and its flags, which sim_parity_live now reads against the probe at that gametic. The claw is seeded instead: sim_troop_live stands one imp four units from another and reads the damage, the pointer P_DamageMobj turns onto the attacker, the threshold it sets and the ambush flag the face clears.
A_Chase's face-target comment claimed nothing on the map carries the flag. E1M7 has a spectre. What is true is that no target of a face-target carries it, because monsters face the player and the player has no blur sphere, and that is what makes the refusal beside it unreachable rather than the flag being absent.
MarcusKainth
force-pushed
the
native/troop-attack
branch
from
September 5, 2026 00:28
db22ad1 to
7109711
Compare
MarcusKainth
marked this pull request as ready for review
September 5, 2026 00:28
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes, and why
attacks::attackworks out whatA_TroopAttackandA_SargAttackleave.Nothing called it. This is the call: the state cycle now enters a frame
carrying one of them instead of saying the tic could not be produced, and
what the routine answers with goes where it belongs.
A_FaceTargetleaves, and the ambush flag it clears, over whatthe state cycle and the chase left;
P_DamageMobjwith theattacker as both the inflictor and the source, from
attacks::claw_ask;P_CheckMeleeRangeends inP_CheckSight, so the attackers ask alongsidethe lookers, the chasers and the hearers in the one sight call a tic makes.
Both the routine and
P_DamageMobjare folded over their ask lists ratherthan mapped.
attacks::attack_fold,attacks::no_attackandinter::damage_foldare new for that. A map runs every function in itsbody once even on an empty list and these two bodies are the largest the
statement carries, so a tic that reaches no attack would otherwise pay for
both in full.
mt_hurt_asksis named as the thinker stage's oneP_DamageMobjasklist, and its comment says so: the missile's impact, the barrel's blast
and a monster's hitscan join that list rather than each standing up a call
of its own. The hitscan inside the player fold stays where it is, because
a pellet's kill changes what the next pellet hits.
Three cases still say the tic could not be produced. More than one thing
reaching a routine would have the second draw from an index the first
moves. The fireball wants a missile spawned. A claw that kills owes the
kill count and whatever the corpse drops.
demo3 takes the fireball branch.
P_AproxDistancefrom its imp to theplayer is 407 map units when the routine runs at gametic 169, against a
melee bound of 60, so the claw cannot move the first divergence. That
stays at 169, on the two numbers the missile spawn draws, until the wiring
pull request above this one. What the tic does carry through at 169 is the
imp's frame, its angle and its flags, which
sim_parity_livenow readsagainst the probe there.
The claw is seeded instead.
sim_claw_livereads the routine itselfagainst a reader written from
p_enemy.c;sim_troop_livereads what atic does with it, standing one
MT_TROOPfour units from another.Limitations
The
inter::Hurtingworld reads the target as the thinker stage has leftit so far:
mk_m_x,mk_m_y,mk_m_z, the momentum the move and thefall wrote,
mk_m_state,mk_m_tics,mk_m_reactiontime,mk_m_threshold,mk_m_targetand the flags the chase left.m_healthand
m_heighthave no writer ahead of this call and stand as the ticstarted.
That is right for a target whose own thinker runs before the attacker's,
because the engine runs them in list order, and early for a target further
down the list. demo3's only claw target is the player, which is slot 1 and
always first, so nothing on this map measures the difference.
Evidence
The first divergence
Unchanged at gametic 169, which is what the fireball leaving the tic
unresolved means.
The probe's own prndindex moves 184 to 194 across that tic. This carries
eight of those ten; the two left are
P_SpawnMissile's.The imp through its attack
sim_parity_live'sATTACKfixture gains gametic 169 with the state,flags and angle the probe carries there, and two checks on which tic
refuses.
The seeded claw
The seeded arm fails when the wiring breaks
Four breaks, each applied on its own and reverted. Run against the mapped
shape; the assertions and the writebacks they reach are the same.
Cost
Measured on a throwaway ClickHouse 26.8.2.7 of this lane's own, started
from
docker-compose.yml's digest with the sameconfig.dandusers.d,against
d824fc9in the same session, three rounds interleaved, themachine lock taken and given back around each measurement.
ticis thestatement's wall clock less parsing, analysis, planning and pipeline
building, divided by the rows it wrote. The 60 tic window is idle; the 240
tic window runs the chase, the shot and the attack.
The resident statement goes from 1,073,633 to 1,255,576 bytes.
The idle tic rises 4.6 ms, against a gate of 0.5 ms. The active tic
rises 1.0 ms.
Folding the two bodies is what the active figure bought. The same
measurement against the mapped shape read +4.2 ms idle and +2.9 ms active,
and the mapped shape put 252 extra function executions on an idle tic
where the folded one puts 156.
What is left is not the bodies. With both fold bodies replaced by the
answer an empty list gives, and everything else in place:
So the two routines cost about 1 ms of the 3.7 that run measured, and the
other 2.8 ms is the work that has to be per slot: the
mt_attackersfilter, the fourth sight ask list, and the writebacks for the angle, the
flags and the eleven columns a claw moves.
Folding the writebacks the same way does not give that back. A shape that
put all thirteen of those
arrayMaps inside twoarrayFolds, one overthe stage's hurt asks with the eleven held columns as its accumulator and
one over the slot the routine turned, measured the same as this branch on
three interleaved rounds against
d824fc9: main 41.59 ms against troop46.76 ms by arm medians, +4.4 ms paired, where this branch reads +4.6 ms.
A fold body does not run on an empty list, so those thirteen maps are not
what the idle tic was paying for. That shape is not in this branch, and
where the 2.8 ms does sit is still open.
mt_attackersand the fourth sight ask list are built frommt_cyclesand
mt_next, which the tic already has, and from the attacker listitself, so neither adds a pass over every slot.
The demo gate
Relative to main, in the same session, two rounds each.
Suites
A second commit
A_Chase's face-target comment claimed nothing on this map carriesMF_SHADOW, and E1M7 has a spectre. The commit beside this one says whatis true instead: no target of a face-target carries it, because monsters
face the player and the player has no blur sphere.
Invariants
None.
Spec impact
Checks
make gates. Not run whole.make lint, the whole ofclickdoom-native's test set against a live server, andmake native-smokeran instead, all by exit codemake native-smoke, ifnative/ordriver/src/native/changedAnything else
The idle tic is the part worth attention. Two shapes have been tried
against it: folding the two routine bodies, which bought the active tic,
and folding the writebacks, which bought nothing and is not here.
Written mostly by Claude Opus 5.