Skip to content

Disable air unit intel on death instead of on impact except for scout vision - #7190

Open
lL1l1 wants to merge 28 commits into
developfrom
feature/aircraft-death-vision-entities
Open

Disable air unit intel on death instead of on impact except for scout vision#7190
lL1l1 wants to merge 28 commits into
developfrom
feature/aircraft-death-vision-entities

Conversation

@lL1l1

@lL1l1 lL1l1 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Description of the proposed changes

Intel for air units is not disabled upon death, instead it goes away when the unit is Destroyed. This is especially relevant for air units since they fly fast and take a long time falling to the ground after they die and before they are Destroyed.

This behavior of air units is commonly used on purpose so removing it with a fix is not permissible in my opinion. Instead, I made air units keep their intel range rings and vision shading using a dummy intel unit. Adding this intel dummy makes the mechanic people have been using more understandable for all players.

Testing done on the proposed changes

Make sure the intel dummy gives identical intel to the air unit when impacting on both land and water (water causes sinking instead of destruction).

Here is how it looks:

air.vision.dummy.mp4

One issue is that the overlay appears after 1 tick. I tried a workaround by putting the intel dummy creation in a Kill() hook, but that didn't work. I guess the only way to do it would be to pre-allocate intel dummies and move them into place when necessary and disable them when not. It's probably doable in a maintainable way although it is a bit of complex work so I haven't gotten around to it.

A potential issue is that this is enabled for all units and could be unusual with things like the czar:

air.vision.dummy.czar.mp4

People could get used to it or we could disable it for non-scouts. I don't like disabling it for non-scouts because there are situations where you use fighters for immediate intel instead of waiting for scouts, so I'd rather people get used to it.

Additional context

Other units use DisableUnitIntel('Killed') in OnKilled, which disables everything except vision and water vision.

Checklist

Summary by CodeRabbit

  • New Features

    • Scout and spy aircraft now retain their vision while falling after being destroyed.
    • Other air units continue to lose intel during their fall, while Scouts retain vision only.
  • Bug Fixes

    • Improved vision-marker updates during aircraft movement and falling.
    • Fixed a potential blueprint reload failure when load-order information is unavailable.
  • Documentation

    • Clarified intel behavior, vision settings, unit categories, and blueprint properties.

@lL1l1
lL1l1 requested review from Garanas and speed2CZ July 24, 2026 05:22
@lL1l1 lL1l1 added area: sim Area that is affected by the Simulation of the Game area: ui Anything to do with the User Interface of the Game labels Jul 24, 2026
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Air units can retain vision while falling after death through VisionMarkerOpti. Scout defaults and selected blueprints enable the behavior. Intel and blueprint documentation are updated, and blueprint reload loops now guard missing load-order indices.

Changes

Falling Air-Unit Vision

Layer / File(s) Summary
Intel contracts and marker behavior
engine/Core/Blueprints/UnitBlueprint.lua, engine/Sim/Entity.lua, engine/Core/Categories.lua, lua/sim/VizMarker.lua
Documentation clarifies intel and blueprint fields. VisionMarkerOpti accepts optional lifetimes and updates cached Z coordinates without recalculating terrain height.
Scout blueprint settings
lua/system/blueprints-units.lua, units/*/*_unit.bp
Scout post-processing enables EnableVisionWhileFalling when unset. Selected air-unit blueprints set the field explicitly.
Air-unit death vision flow
lua/sim/units/AirUnit.lua, changelog/snippets/*.7190.md
Completed air-unit deaths create and attach VisionMarkerOpti when enabled. Killed intel is disabled, while configured vision remains active during falling.

Blueprint Reload Guard

Layer / File(s) Summary
Conditional dependency reload
lua/system/Blueprints.lua
Dependency reload loops run only when the current source has a load-order index.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • FAForever/fa#7139: Both changes modify VisionMarkerOpti vision-intel behavior, but use different code paths.

Suggested labels: area: unit-blueprint

Suggested reviewers: speed2cz

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The description covers the changes, testing considerations, additional context, and checklist requirements.
Title check ✅ Passed The title clearly summarizes the main change: disabling air-unit intel on death while preserving scout vision.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/aircraft-death-vision-entities

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@lL1l1
lL1l1 marked this pull request as ready for review July 24, 2026 05:25

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@engine/Sim/Entity.lua`:
- Around line 268-274: Update the radius annotations for Entity:InitIntel and
the corresponding SetIntelRadius API to match their callers: use number if
fractional radii are supported, including AirUnit’s 0.5-scaled vision values, or
consistently normalize or reject non-integer inputs before they reach these
methods. Keep the documented optional-radius behavior aligned across both APIs.

In `@lua/sim/units/AirUnit.lua`:
- Around line 222-225: Correct the misspelled type annotation on the
CreateProjectileAtBone result assigned to self.colliderProj, changing
ShiledCollider to ShieldCollider to match the referenced projectile type and
provide accurate Lua tooling information.

In `@lua/sim/VizMarker.lua`:
- Around line 136-140: Update the lifetime guard in the method containing
UpdateDuration so the optional lifetime is validated for presence before
comparing it to zero. Use the existing UpdateDuration path only when lifetime is
non-nil and non-negative, leaving duration unchanged when lifetime is omitted.

In `@lua/system/Blueprints.lua`:
- Around line 440-445: Guard the dependent-reload block around the load-order
length and dependent-loop start with the same orderNumber check used by the
precursor loop. Update the logic near TableGetn(loadOrderTable) so it only
calculates and reloads dependent entries when orderNumber exists, while safely
skipping that pass when no load-order index is available.

In `@units/ZXA0004/ZXA0004_script.lua`:
- Around line 13-16: Update the self annotation on ZXA0004.OnCreate from ZXA0003
to ZXA0004, leaving the method implementation unchanged.
- Around line 32-46: Update CopyAllIntelFrom and its local CopyIntel helper so
each intel type copied from unit is initialized and enabled on self before
applying the source radius. Preserve the existing IsIntelEnabled filtering and
radius propagation for all listed intel types.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8f01962b-5212-4cef-9dbf-7a3543761062

📥 Commits

Reviewing files that changed from the base of the PR and between 1488b65 and eb93ac8.

📒 Files selected for processing (9)
  • changelog/snippets/features.7190.md
  • engine/Core/Blueprints/UnitBlueprint.lua
  • engine/Core/Categories.lua
  • engine/Sim/Entity.lua
  • lua/sim/VizMarker.lua
  • lua/sim/units/AirUnit.lua
  • lua/system/Blueprints.lua
  • units/ZXA0004/ZXA0004_script.lua
  • units/ZXA0004/ZXA0004_unit.bp

Comment thread engine/Sim/Entity.lua
Comment on lines +268 to 274
--- Initializes the entity's intel to provide intelligence of a partiuclar type for an army.
--- This lets the other intel methods work with this entity for that type of intel.
---@param army Army
---@param type IntelType
---@param radius? number
---@param type IntelType # Logs a warning if using `"None"`, but still inits intel manager.
---@param radius? integer
function Entity:InitIntel(army, type, radius)
end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Find relevant files:"
fd -a 'Entity.lua|AirUnit.lua|VizMarker.lua' . | sed 's#^\./##'

echo
echo "Relevant sections:"
for f in engine/Sim/Entity.lua lua/sim/units/AirUnit.lua lua/sim/VizMarker.lua; do
  if [ -f "$f" ]; then
    echo "===== $f ====="
    wc -l "$f"
    sed -n '240,385p' "$f"
  fi
done

echo
echo "Search InitIntel/SetIntelRadius usages:"
rg -n "InitIntel|SetIntelRadius|IntelRadius" -S . --glob '!vendor/**' --glob '!build/**' | head -200

Repository: FAForever/fa

Length of output: 12605


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Entity.lua intel implementations:"
sed -n '120,380p' engine/Sim/Entity.lua

echo
echo "lua/sim/VizMarker.lua relevant sections:"
sed -n '1,180p' lua/sim/VizMarker.lua

echo
echo "lua/sim/units/AirUnit.lua relevant sections:"
sed -n '1,90p' lua/sim/units/AirUnit.lua

echo
echo "Search blueprint intel field definitions/usage:"
rg -n "VisionRadius|WaterVisionRadius|OmniRadius|JammerRadius|SonarRadius|Intel" -S engine lua --glob '!build/**' | head -250

Repository: FAForever/fa

Length of output: 42483


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Luau/FAF callbacks around entity intel implementations:"
git ls-files | rg '(^|/)SimCallbacks\.lua$|/moho.*Entity|engine/Sim/CollisionBeamEntity.lua' | head -50
sed -n '850,970p' lua/SimCallbacks.lua
rg -n "InitIntel|SetIntelRadius|GetIntelRadius|IntelRadius" -S lua/SimCallbacks.lua lua/AI lua/system lua/sim engine/Sim --glob '!**/generated/**' | head -80

echo
echo "AirUnit blueprint classes:"
rg -n "AirUnit.*Blueprint|Intel =|WaterVisionRadius|VisionRadius" lua/system lua/blueprints units --glob '!**/generated/**' | head -200

echo
echo "Read-only Lua static scanner for direct integer annotation vs non-integer arg patterns:"
python3 - <<'PY'
from pathlib import Path
for p in ["engine/Sim/Entity.lua","lua/sim/VizMarker.lua","lua/sim/units/AirUnit.lua"]:
    text=Path(p).read_text()
    print(f"===== {p} =====")
    for i,line in enumerate(text.splitlines(),1):
        if "Entity:InitIntel" in line or "Entity:SetIntelRadius" in line or "IntelRadius" in line or "`@field` Radius" in line or "`@param` radius" in line or ".Intel." in line:
            print(f"{i}: {line}")
PY

Repository: FAForever/fa

Length of output: 19286


Align intel-radius parameter types with the callers.

Entity.InitIntel/SetIntelRadius are annotated as integer, but lua/sim/VizMarker.lua forwards radius: number values and lua/sim/units/AirUnit.lua passes 0.5 * blueprintIntel.VisionRadius/WaterVisionRadius. Either document these APIs as number when fractional radii are valid, or normalize/reject non-integer values consistently.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@engine/Sim/Entity.lua` around lines 268 - 274, Update the radius annotations
for Entity:InitIntel and the corresponding SetIntelRadius API to match their
callers: use number if fractional radii are supported, including AirUnit’s
0.5-scaled vision values, or consistently normalize or reject non-integer inputs
before they reach these methods. Keep the documented optional-radius behavior
aligned across both APIs.

Comment thread lua/sim/units/AirUnit.lua
Comment thread lua/sim/VizMarker.lua
Comment thread lua/system/Blueprints.lua Outdated
Comment thread units/ZXA0004/ZXA0004_script.lua Outdated
Comment thread units/ZXA0004/ZXA0004_script.lua Outdated
@speed2CZ

Copy link
Copy Markdown
Member

I would entertain the idea of actually removing this feature, as it doesnt matter for most of the air units. And is kinda OP for scouts where they get intel on things after getting show down. That would maybe even require them to not be so fragile as they are right now, and could lead to a different balance.

And for this change, if the problem with that 1 tick delay is causing by unit dying - intel entity getting created. How about creating this entity when the unit is constructed and then just enabling the intel on death?

lL1l1 added 5 commits July 23, 2026 23:55
0.45ms sim time just with an acu
fully zoomed out on 20km map (since vfx cause sim lag)
test with 1500 ASF:
before change: 5.3-5.9ms idle, 9.5-10.5 on initial patrol order, 8.7-9.3 on second
after change: 7.0-7.7ms idle, 11-12.9ms on first patrol order, 9.9-10.7ms second patrol order, 10.9-11.5ms on third patrol order
@lL1l1

lL1l1 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

I did some testing and got quite a big performance hit from spawning 2x the units for all air units, so I don't like the simple answer of just giving every air unit an intel dummy. Written in the commit description:

0.45ms sim time just with an acu
fully zoomed out on 20km map (since vfx cause sim lag)
test with 1500 ASF:
before change: 5.3-5.9ms idle, 9.5-10.5 on initial patrol order, 8.7-9.3 on second
after change: 7.0-7.7ms idle, 11-12.9ms on first patrol order, 9.9-10.7ms second patrol order, 10.9-11.5ms on third patrol order

The average patrol is +17% sim time taken up. On idle its +31%. With how conservatively air units are played, they don't die for quite a long time and build up, so keeping the intel dummies with these performance hits is quite bad.

@Garanas

Garanas commented Jul 24, 2026

Copy link
Copy Markdown
Member

I see that you requested my review, but I'm moving on from FAForever. You don't have to request it in the future.

More on-topic: adding a dummy unit to every air unit is a mistake. You also came to this conclusion.

before change: 5.3-5.9ms idle, 9.5-10.5 on initial patrol order, 8.7-9.3 on second
after change: 7.0-7.7ms idle, 11-12.9ms on first patrol order, 9.9-10.7ms second patrol order, 10.9-11.5ms on third patrol order

Not only is that a ~2ms increase, which will be much higher for fewer units on weaker hardware. You also increase memory consumption (modded games run into this issue quite quickly). You increase complexity of the code base with this solution. And you increase the likely hood for bugs, like how existing scripts can accidentally get a handle to this dummy unit when trying to find units in an area.

This behavior of air units is commonly used on purpose so removing it with a fix is not permissible in my opinion.

Just because something is commonly used does not make it a good feature. The only correct fix in my humble opinion is to stop intel when a unit dies, for any type of unit. The case you describe also applies to ships that sink I would assume. Now the behavior is consistent and aligns with what the engine is trying. Then you adjust the balance so that scouts don't die instantly. This can be by giving them more health, or by increasing their altitude so that it takes longer (and is more difficult) for projectiles to hit.

edit: another solution would be an engine patch, where units even after being killed still render their intel rings until they are destroyed. Then you double down on the behavior, which is fine too.

@lL1l1

lL1l1 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

Ok I understand. good luck in your future endeavors. Wish you a less stressful environment.

Yeah the performance impact is quite large, I forgot to mention that I have a powerful 7800x3d so 1500 units wasn't even an unrealistic number of units relative to my capabilities.
Engine patch would indeed be the optimal solution, but it seems like we'd have to transform some on-frame IsDead into IsIntelEnabled(type), which could be worse. Besides the intel dummy pool we could just use a singular vision-only vizmarker entity. It would convey the mechanic partially through the fog of war being cleared, which people can connect with the radar and other intel being active.
Balance-wise I think there's some extra depth with having both DPS and distance matter for stopping scouts, but I'll gather some extra opinions on the feature, there are too few participants for a debate/conclusion atm.
A compromise between everything would be to disable everything except vision on death and use the vision entity. Then it's a still a similar balance, performant, there is no hidden radar intel, and the vision is shown without 1-tick visual issues.

@lL1l1
lL1l1 removed the request for review from Garanas July 24, 2026 07:55
@lL1l1

lL1l1 commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

I updated the PR to disable all intel for most air units on death instead of on impact, with the exception of vision intel for scouts.
The behavior is controlled by a field in the unit air blueprint and defaults to true if the unit has the scout category.

I think this is a good compromise for the balance, immersion, performance, and player-pov understandability.

@lL1l1 lL1l1 changed the title Keep showing aircraft intel range overlays while falling through air Disable air unit intel on death instead of on impact except for scout vision Aug 3, 2026
@lL1l1 lL1l1 added the area: balance related to units balance, but not as a suggestion label Aug 3, 2026
@lL1l1 lL1l1 moved this from To Discuss to To Implement in 2025 - Balance Iteration I Aug 3, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lua/sim/units/AirUnit.lua`:
- Around line 224-235: The falling-death vision setup in the AirUnit vision
block ignores the blueprint’s death-specific radius and duration. Update the
VisionMarkerOpti configuration around EnableVisionWhileFalling to use
self.Blueprint.Intel.VisionRadiusOnDeath and apply the configured
IntelDurationOnDeath via the marker’s duration API; if this vision is
intentionally permanent until sinking or destruction instead, remove or document
those stale death-specific blueprint fields.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2c0e2661-e7d2-40eb-8492-936c88e545aa

📥 Commits

Reviewing files that changed from the base of the PR and between 1a54c46 and a9f7b83.

📒 Files selected for processing (13)
  • changelog/snippets/balance.7190.md
  • changelog/snippets/features.7190.md
  • engine/Core/Blueprints/UnitBlueprint.lua
  • lua/sim/units/AirUnit.lua
  • lua/system/blueprints-units.lua
  • units/UAA0101/UAA0101_unit.bp
  • units/UAA0302/UAA0302_unit.bp
  • units/UEA0101/UEA0101_unit.bp
  • units/UEA0302/UEA0302_unit.bp
  • units/URA0101/URA0101_unit.bp
  • units/URA0302/URA0302_unit.bp
  • units/XSA0101/XSA0101_unit.bp
  • units/XSA0302/XSA0302_unit.bp
🚧 Files skipped from review as they are similar to previous changes (1)
  • changelog/snippets/features.7190.md

Comment thread lua/sim/units/AirUnit.lua
Comment on lines +224 to +235
if not self.Blueprint.Air.EnableVisionWhileFalling then
self:DisableIntel('Vision') -- Disable vision seperately, it's not handled in DisableUnitIntel
else
-- Create a vision entity so vision shading is visible

local x, y, z = self:GetPositionXYZ(0)
---@type VisionMarkerOpti
local vizEnt = self.Trash:Add(VisionMarkerOpti({ Army = army }))
vizEnt:UpdateIntel(army, self:GetIntelRadius("Vision"), "Vision", true)
vizEnt:UpdatePosition(x, z)
vizEnt:AttachTo(self, 0)
end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 5 \
  'IntelDurationOnDeath|VisionRadiusOnDeath|EnableVisionWhileFalling|UpdateDuration|GetIntelRadius' \
  engine lua units

Repository: FAForever/fa

Length of output: 22047


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## AirUnit.lua context"
sed -n '180,245p' lua/sim/units/AirUnit.lua

echo
echo "## VizMarker lifecycle context"
sed -n '1,220p' lua/sim/VizMarker.lua

echo
echo "## Blueprint parser/deadlife context"
sed -n '600,660p' lua/system/blueprints-units.lua
sed -n '1040,1095p' engine/Core/Blueprints/UnitBlueprint.lua

echo
echo "## Vision field occurrences and assignments in lua/engine data"
rg -n -C 3 'EnableVisionWhileFalling|VisionRadiusOnDeath|IntelDurationOnDeath|OnKilled|DisableUnitIntel|DisableUnitIntel|KillUnit|death|Killed|Die' lua/sim/units/XSB*.lua lua/sim/units/UA*.lua lua/sim/units/UE*.lua lua/sim/units/UR*.lua lua/sim/AirUnit.lua lua/system/blueprints-units.lua engine/Core/Blueprints/UnitBlueprint.lua | sed -n '1,260p'

Repository: FAForever/fa

Length of output: 22034


Honor the death-specific vision settings or remove their stale contract.

EnableVisionWhileFalling creates VisionMarkerOpti, but line 232 uses self:GetIntelRadius("Vision") instead of self.Blueprint.Intel.VisionRadiusOnDeath, and it leaves UpdateDuration unset while IntelDurationOnDeath = 20 exists for this lifecycle. If death vision is meant to remain until sink or destruction, document/remove those death fields; otherwise pass the death radius and duration to the marker.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lua/sim/units/AirUnit.lua` around lines 224 - 235, The falling-death vision
setup in the AirUnit vision block ignores the blueprint’s death-specific radius
and duration. Update the VisionMarkerOpti configuration around
EnableVisionWhileFalling to use self.Blueprint.Intel.VisionRadiusOnDeath and
apply the configured IntelDurationOnDeath via the marker’s duration API; if this
vision is intentionally permanent until sinking or destruction instead, remove
or document those stale death-specific blueprint fields.

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

Labels

area: balance related to units balance, but not as a suggestion area: sim Area that is affected by the Simulation of the Game area: ui Anything to do with the User Interface of the Game

Projects

Status: To Implement

Development

Successfully merging this pull request may close these issues.

4 participants