Skip to content

Stop a co-op companion killing its own team mate - #260

Merged
saworbit merged 1 commit into
mainfrom
coop-friendly-fire
Sep 5, 2026
Merged

Stop a co-op companion killing its own team mate#260
saworbit merged 1 commit into
mainfrom
coop-friendly-fire

Conversation

@saworbit

@saworbit saworbit commented Sep 5, 2026

Copy link
Copy Markdown
Owner

Two human sessions on e1m2, two deaths, two different mechanisms. Both are ours, not stock friendly fire being unlucky.

It shoots back

The 2026-09-05 session ended eighteen seconds in, health going 100, 76, 64, 48, 24, 4 across five hits in five seconds.

The tape holds the cause. Both parties took exactly 24 damage within one second of each other:

Carmack  hp 100 -> 76 at t 13.3
player   hp 100 -> 76 at t 13.5

Shane shot Carmack, and Argus_Pain fights back against "any live player, human or bot" with no co-op test, so it made its own team mate the enemy. In co-op that branch can only ever fire on a team mate, because monsters do not carry classname player.

It shoots through you

The earlier 157 s session died at 425 -469 320 to "chewed on Carmack's boomstick".

That one never targeted the player at all: two engage events in the whole session, both engage monster. The bot was in pursue, firing at a monster's last known position during the three second sight loss hold, and the human crossed the line. It pushed GOAL_REVIVE_SUPPORT the moment he died and came to help.

The fix

Two guards, both gated on coop so deathmatch cannot reach them.

  • Argus_Pain does not retaliate against a team mate.
  • The fire gate traces the shot it is about to take and holds when a team mate is what it reaches.

Splash is deliberately not covered: a rocket landing beside a team mate still hurts them, and that wants its own look if a session shows it.

Already safe

Recorded because it reads like a hole and is not one. Argus_CoopFindThreat requires FL_MONSTER so it cannot return a player, and Argus_Perceive returns before its player scan whenever coop > 0, so a companion cannot acquire a team mate through perception. A guard was written there first and removed as dead code once the early return was found.

Verification, honestly

This cannot be shown headless. A co-op botmatch runs one bot, and adding more with impulse 101 does not help, because the perception early return means bots never target each other in co-op anyway. Two ally tests were built and both came back null for exactly that reason, on e1m2 and on a monster free dm4 co-op: zero bot versus bot engagements in either arm, every death world or a spawn telefrag. The retaliation path needs a player to damage a bot, and the netclient can walk and send impulses but cannot fire (#259).

So the ladder can only show absence of harm, and does:

cells stalls routefails
dm4, recent tapes 74, 75 4, 18 9, 10
dm4, this build 73 17 22
dm2, this build 129 50 0

On a single bot tape the guards are provably inert: Argus_IsAlly can only return FALSE with no second player, and the fire trace never runs without an engage event. The e1m2 tape in this ladder pinned in water at 6 cells, which is the pre-existing stochastic pin filed as #256, on a tape with zero engage events where none of this code executed.

The real verification is a human session where the companion is shot and does not shoot back.

Refs #258, #259

🤖 Generated with Claude Code

Two human sessions on e1m2, two deaths, two different mechanisms. Both
are ours, not stock friendly fire being unlucky.

IT SHOOTS BACK. The 2026-09-05 session ended eighteen seconds in, with
health going 100, 76, 64, 48, 24, 4 across five hits. The tape holds
the cause: both parties took exactly 24 damage within one second of
each other, Carmack at t 13.3 and the player at t 13.5. Shane shot
Carmack, and Argus_Pain fights back against "any live player, human or
bot" with no co-op test, so it made its own team mate the enemy. In
co-op that branch can ONLY ever fire on a team mate, because monsters
do not carry classname "player".

IT SHOOTS THROUGH YOU. The earlier 157 s session died at '425 -469
320' to "chewed on Carmack's boomstick". That one never targeted the
player at all: two engage events in the whole session, both engage
monster. The bot was in pursue, firing at a monster's last known
position during the three second sight loss hold, and the human
crossed the line. It pushed GOAL_REVIVE_SUPPORT the moment he died and
came to help.

Two guards, both gated on coop so deathmatch cannot reach them.
Argus_Pain does not retaliate against a team mate, and the fire gate
traces the shot it is about to take and holds when a team mate is what
it reaches. Splash is deliberately not covered: a rocket landing
beside a team mate still hurts them, and that wants its own look if a
session shows it.

ALREADY SAFE, recorded because it reads like a hole and is not one.
Argus_CoopFindThreat requires FL_MONSTER so it cannot return a player,
and Argus_Perceive returns before its player scan whenever coop is on,
so a companion cannot acquire a team mate through perception. A guard
was written there first and then removed as dead code once the early
return was found.

VERIFICATION, honestly. This cannot be shown headless. A co-op
botmatch runs one bot, and adding more with impulse 101 does not help
because the perception early return means bots never target each other
in co-op anyway. Two ally tests were built and both came back null for
exactly that reason, on e1m2 and on a monster free dm4 co-op: zero bot
versus bot engagements in either arm, every death world or a spawn
telefrag. The retaliation path needs a player to damage a bot, and the
netclient can walk and send impulses but cannot fire.

So the ladder can only show absence of harm, and does. dm4 at parity,
73 cells and 17 stalls against 74 to 75 and 4 to 18 on recent tapes.
dm2 at 129 cells. On a single bot tape the guards are provably inert:
Argus_IsAlly can only return FALSE with no second player, and the fire
trace never runs without an engage event. The e1m2 tape in this ladder
pinned in water at 6 cells, which is the pre-existing stochastic pin
filed separately, on a tape with zero engage events where none of this
code executed.

The real verification is a human session where the companion is shot
and does not shoot back.

Refs #258, #259

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 5, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-05T02:56:36.319685Z 87999cd PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@saworbit
saworbit merged commit f787ef2 into main Sep 5, 2026
3 checks passed
@saworbit
saworbit deleted the coop-friendly-fire branch September 5, 2026 02:55

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 87999cde35

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/argus.qc
// shot we are about to take, and if a team mate is what it
// reaches, wait. Splash is not covered here: a rocket landing
// beside a team mate still hurts them.
traceline (self.origin + '0 0 22', aimpt, FALSE, self);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Trace the direction the weapon will actually fire

Trace along self.v_angle rather than directly toward aimpt. The preceding code overwrites self.v_angle_y with self.angles_y, and the trigger permits that yaw to differ from the desired aim by up to 12 degrees, while bot weapons such as W_FireShotgun fire using self.v_angle. During normal aim convergence, a teammate can therefore be directly in the actual firing line while this trace follows a different line and allows the shot, leaving the co-op team-kill regression unfixed.

Useful? React with 👍 / 👎.

Comment thread src/argus.qc
Comment on lines +4176 to +4177
traceline (self.origin + '0 0 22', aimpt, FALSE, self);
if (Argus_IsAlly (trace_ent))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Check the full shotgun spread before firing

Account for the weapon's firing envelope instead of checking only one center ray. FireBullets sends six or fourteen randomized traces with nonzero horizontal and vertical spread, extending 2048 units, so a teammate who is within the shotgun or super-shotgun pellet cone but not intersecting this exact ray—including one behind the intended target—can still be hit repeatedly and killed. This is especially relevant because the reported co-op death was caused by the bot's boomstick.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant