Skip to content

Conversation

@NewYearNewPhil
Copy link
Contributor

@NewYearNewPhil NewYearNewPhil commented Nov 1, 2025

Description:

Implements SAM range extension for stacked SAMs to cover hydros as requested in #2347 and many times from users in discord.
This implementation is as simple as possible: At level 5 and higher, SAMs extend their range to the range of a hydrogen bomb + 10 for a small safety margin. Levels 2-4 are interpolated between.

Screenshot to show the sizes compared to a hydro:
image

Everything works together with the new range UI, although I might need to unify with / rebase on #2350. Not yet tested with #2348, but shouldn't be an issue.

Input needed:

  • Should I add tests for this?
  • This is in effect a massive buff to SAMs, might be too strong. Popular ideas / suggestions from Discord to balance things:
    • Cap the SAM upgrade level at the maximum range (easy to do)
    • Alternative, instead of capping the level, decrease the range when missiles are reloading
    • Increase the cost scaling for SAMs per stack, and scale way higher (e.g. 1.5M > 3M > 4.5M > 6M or something like that) (UI integration unclear, breaks with existing cost logic)
    • Decrease SAM hit probability for Hydros

I'm happy to implement any of these paths, or just roll with the simple way it's set up now, just let me know.

Please complete the following:

  • I have added screenshots for all UI updates
  • I process any text displayed to the user through translateText() and I've added it to the en.json file
  • I have added relevant tests to the test directory
  • I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced

Please put your Discord username so you can be contacted if a bug or regression is found:

newyearnewphil

@NewYearNewPhil NewYearNewPhil requested a review from a team as a code owner November 1, 2025 00:26
@CLAassistant
Copy link

CLAassistant commented Nov 1, 2025

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 1, 2025

Walkthrough

Config adds level-aware SAM range methods; SAM execution and radius rendering now use per-SAM-level ranges; ghost/build preview range visualization computes and updates ranges based on resolved SAM level.

Changes

Cohort / File(s) Summary
Configuration API
src/core/configuration/Config.ts, src/core/configuration/DefaultConfig.ts
Added samRange(level: number): number and maxSamRange(): number; DefaultConfig implements level-based samRange and maxSamRange.
SAM Execution
src/core/execution/SAMLauncherExecution.ts
Replaced defaultSamRange() with samRange(this.sam.level()) for in-range checks; detection range uses maxSamRange() * 2.
Radius Visualization
src/client/graphics/layers/SAMRadiusLayer.ts
Circle radii computed with samRange(sam.level()) instead of the fixed default range.
Range Drawing Utility
src/client/graphics/layers/StructureDrawingUtils.ts
SpriteFactory.createRange(...) gained an optional level?: number; SAM ranges use samRange(level ?? 1).
Ghost Range Tracking
src/client/graphics/layers/StructureIconsLayer.ts
Ghost units track rangeLevel; added resolveGhostRangeLevel(buildableUnit) and updateGhostRange(level) to compute/update ghost range visuals and skip redundant updates.
Test Config
tests/util/TestConfig.ts
Added samRange(level: number): number returning a test value (20).

Sequence Diagram(s)

sequenceDiagram
    participant UI as User / UI
    participant SIL as StructureIconsLayer
    participant Config as Config
    participant Draw as SpriteFactory/Renderer
    participant SAM as SAMLauncher

    rect rgb(243,248,255)
    UI->>SIL: show ghost / select buildable
    SIL->>SIL: resolveGhostRangeLevel(unit)
    SIL->>Config: samRange(level)
    Config-->>SIL: computed range
    SIL->>Draw: updateGhostRange(range)
    Draw-->>UI: ghost range rendered
    end

    rect rgb(255,248,240)
    UI->>SAM: simulation tick / engage
    SAM->>Config: samRange(sam.level())
    Config-->>SAM: range
    SAM->>SAM: isInRange(target) / getSingleTarget()
    SAM-->>UI: fire or no-fire
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Inspect DefaultConfig.samRange formula for numeric correctness and edge cases (level <= 0).
  • Verify SAMLauncherExecution.getSingleTarget detectionRange change to maxSamRange() * 2.
  • Review StructureIconsLayer.updateGhostRange lifecycle to ensure creation/destruction and sync with ghost position/upgrades.

Possibly related PRs

Suggested labels

Feature - Frontend, Feature - Simulation, Balance Tweak

Suggested reviewers

  • evanpelle
  • jrouillard

Poem

A level nudges how far SAMs can sweep,
Ghost rings bloom where silent builders peep.
Config counts ranges, tidy and small,
Render and sim now answer the same call. 🎯

Pre-merge checks

✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Extend SAM Range to cover Hydros when stacked' directly describes the main change: implementing SAM range extension for stacked SAMs to reach hydrogen bomb range, which is the core objective of this PR.
Description check ✅ Passed The description is directly related to the changeset, detailing the SAM range extension feature implementation, including the formula used, integration with range UI, testing status, and balancing considerations for the feature.

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 and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 380307e and 4152be6.

📒 Files selected for processing (6)
  • src/client/graphics/layers/SAMRadiusLayer.ts (1 hunks)
  • src/client/graphics/layers/StructureDrawingUtils.ts (1 hunks)
  • src/client/graphics/layers/StructureIconsLayer.ts (4 hunks)
  • src/core/configuration/Config.ts (1 hunks)
  • src/core/configuration/DefaultConfig.ts (1 hunks)
  • src/core/execution/SAMLauncherExecution.ts (2 hunks)
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: DevelopingTom
Repo: openfrontio/OpenFrontIO PR: 1900
File: src/core/execution/SAMLauncherExecution.ts:103-111
Timestamp: 2025-08-24T11:13:08.495Z
Learning: In SAMLauncherExecution.ts, the cached target bug can only occur if: 1) SAM is not on cooldown when nuke is in range, 2) SAM goes on cooldown right after computing trajectory, 3) SAM becomes available again before nuke explodes. This is not possible with current cooldown values but the fix is still valuable for robustness.
📚 Learning: 2025-08-24T11:13:08.495Z
Learnt from: DevelopingTom
Repo: openfrontio/OpenFrontIO PR: 1900
File: src/core/execution/SAMLauncherExecution.ts:103-111
Timestamp: 2025-08-24T11:13:08.495Z
Learning: In SAMLauncherExecution.ts, the cached target bug can only occur if: 1) SAM is not on cooldown when nuke is in range, 2) SAM goes on cooldown right after computing trajectory, 3) SAM becomes available again before nuke explodes. This is not possible with current cooldown values but the fix is still valuable for robustness.

Applied to files:

  • src/core/configuration/DefaultConfig.ts
  • src/core/configuration/Config.ts
  • src/client/graphics/layers/SAMRadiusLayer.ts
  • src/core/execution/SAMLauncherExecution.ts
📚 Learning: 2025-10-20T20:15:28.858Z
Learnt from: sambokai
Repo: openfrontio/OpenFrontIO PR: 2225
File: src/core/execution/FakeHumanExecution.ts:51-51
Timestamp: 2025-10-20T20:15:28.858Z
Learning: In src/core/execution/FakeHumanExecution.ts, game balance constants like MIRV_COOLDOWN_TICKS, MIRV_HESITATION_ODDS, VICTORY_DENIAL_TEAM_THRESHOLD, VICTORY_DENIAL_INDIVIDUAL_THRESHOLD, and STEAMROLL_CITY_GAP_MULTIPLIER are experimental tuning parameters subject to frequent change during balance testing. Do not flag changes to these values as issues or compare them against previous values.

Applied to files:

  • src/core/execution/SAMLauncherExecution.ts
📚 Learning: 2025-10-27T09:47:26.395Z
Learnt from: sambokai
Repo: openfrontio/OpenFrontIO PR: 2225
File: src/core/execution/FakeHumanExecution.ts:770-795
Timestamp: 2025-10-27T09:47:26.395Z
Learning: In src/core/execution/FakeHumanExecution.ts, the selectSteamrollStopTarget() method intentionally allows MIRV targeting when secondHighest city count is 0 (e.g., nuclear endgame scenarios where structures are destroyed). This is valid game design—"if you can afford it, you're good to go"—and should not be flagged as requiring a guard condition.

Applied to files:

  • src/core/execution/SAMLauncherExecution.ts
📚 Learning: 2025-06-22T21:51:14.990Z
Learnt from: devalnor
Repo: openfrontio/OpenFrontIO PR: 1248
File: src/client/graphics/layers/TerritoryInfoLayer.ts:20-20
Timestamp: 2025-06-22T21:51:14.990Z
Learning: In TerritoryInfoLayer.ts, the highlightedTerritory field uses both null and undefined intentionally: undefined represents initial state or inactive layer (Ctrl released), while null represents active layer with no territory being highlighted at cursor position. This distinction is important for proper state change detection.

Applied to files:

  • src/client/graphics/layers/StructureIconsLayer.ts
🧬 Code graph analysis (4)
src/core/configuration/DefaultConfig.ts (2)
src/core/game/UnitImpl.ts (1)
  • level (406-408)
src/core/game/GameView.ts (1)
  • level (167-169)
src/core/configuration/Config.ts (2)
src/core/game/UnitImpl.ts (1)
  • level (406-408)
src/core/game/GameView.ts (1)
  • level (167-169)
src/client/graphics/layers/StructureDrawingUtils.ts (2)
src/core/game/UnitImpl.ts (2)
  • level (406-408)
  • type (145-147)
src/core/game/GameView.ts (3)
  • level (167-169)
  • type (94-96)
  • type (340-342)
src/client/graphics/layers/StructureIconsLayer.ts (2)
src/core/game/GameView.ts (2)
  • unit (658-660)
  • level (167-169)
src/core/game/Game.ts (1)
  • BuildableUnit (758-764)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4152be6 and 3ec8bb1.

📒 Files selected for processing (1)
  • src/core/configuration/DefaultConfig.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-08-24T11:13:08.495Z
Learnt from: DevelopingTom
Repo: openfrontio/OpenFrontIO PR: 1900
File: src/core/execution/SAMLauncherExecution.ts:103-111
Timestamp: 2025-08-24T11:13:08.495Z
Learning: In SAMLauncherExecution.ts, the cached target bug can only occur if: 1) SAM is not on cooldown when nuke is in range, 2) SAM goes on cooldown right after computing trajectory, 3) SAM becomes available again before nuke explodes. This is not possible with current cooldown values but the fix is still valuable for robustness.

Applied to files:

  • src/core/configuration/DefaultConfig.ts
📚 Learning: 2025-10-20T20:15:28.858Z
Learnt from: sambokai
Repo: openfrontio/OpenFrontIO PR: 2225
File: src/core/execution/FakeHumanExecution.ts:51-51
Timestamp: 2025-10-20T20:15:28.858Z
Learning: In src/core/execution/FakeHumanExecution.ts, game balance constants like MIRV_COOLDOWN_TICKS, MIRV_HESITATION_ODDS, VICTORY_DENIAL_TEAM_THRESHOLD, VICTORY_DENIAL_INDIVIDUAL_THRESHOLD, and STEAMROLL_CITY_GAP_MULTIPLIER are experimental tuning parameters subject to frequent change during balance testing. Do not flag changes to these values as issues or compare them against previous values.

Applied to files:

  • src/core/configuration/DefaultConfig.ts
📚 Learning: 2025-10-27T09:47:26.395Z
Learnt from: sambokai
Repo: openfrontio/OpenFrontIO PR: 2225
File: src/core/execution/FakeHumanExecution.ts:770-795
Timestamp: 2025-10-27T09:47:26.395Z
Learning: In src/core/execution/FakeHumanExecution.ts, the selectSteamrollStopTarget() method intentionally allows MIRV targeting when secondHighest city count is 0 (e.g., nuclear endgame scenarios where structures are destroyed). This is valid game design—"if you can afford it, you're good to go"—and should not be flagged as requiring a guard condition.

Applied to files:

  • src/core/configuration/DefaultConfig.ts
🧬 Code graph analysis (1)
src/core/configuration/DefaultConfig.ts (2)
src/core/game/UnitImpl.ts (1)
  • level (406-408)
src/core/game/GameView.ts (1)
  • level (167-169)
🔇 Additional comments (2)
src/core/configuration/DefaultConfig.ts (2)

915-930: Level 1 now correctly stays at base range.

The implementation properly addresses the previous concern about level 1. The formula baseRange + step * (numericLevel - 1) ensures level 1 returns exactly 70 (base SAM range), and levels 2–5 interpolate smoothly up to 110 (hydrogen outer range + 10). The step divisor (maxRangeLevel - 1) correctly spreads the progression across five levels.


918-918: Level 0 behavior is already handled safely by the clamping logic.

The Math.max(1, level) clamp at line 918 is intentional and correct. SAM units in the codebase are always created at level 1 (confirmed in tests), and the clamp ensures that if level 0 were somehow passed, it would map to the same range as level 1. The samRange() calculation at line 915–926 uses this clamped value to compute: baseRange + step × (numericLevel − 1). For both level 0 and level 1, this produces baseRange (the minimum range). This is defensive programming—level 1 is the true minimum level, and the code safely prevents invalid states. No action needed.

@NewYearNewPhil NewYearNewPhil force-pushed the increate-stacked-sam-range branch from 3ec8bb1 to 2997b03 Compare November 1, 2025 01:36
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/client/graphics/layers/StructureIconsLayer.ts (1)

401-420: Clarify the fallback to level 2.

The level resolution logic is generally sound, but the fallback to level 2 at line 416 is unclear. If we're upgrading a unit but can't find it (neither via game.unit() nor potentialUpgrade), why default to level 2 instead of level 1?

This could happen in edge cases (e.g., unit just destroyed, network lag, race condition). Consider:

  • Returning undefined if the unit can't be found and letting the caller handle it
  • Falling back to level 1 for consistency with new builds
  • Adding a comment explaining why level 2 is the right choice
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3ec8bb1 and 2997b03.

📒 Files selected for processing (6)
  • src/client/graphics/layers/SAMRadiusLayer.ts (1 hunks)
  • src/client/graphics/layers/StructureDrawingUtils.ts (1 hunks)
  • src/client/graphics/layers/StructureIconsLayer.ts (4 hunks)
  • src/core/configuration/Config.ts (1 hunks)
  • src/core/configuration/DefaultConfig.ts (1 hunks)
  • src/core/execution/SAMLauncherExecution.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/client/graphics/layers/SAMRadiusLayer.ts
  • src/core/configuration/DefaultConfig.ts
  • src/core/configuration/Config.ts
  • src/client/graphics/layers/StructureDrawingUtils.ts
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-08-24T11:13:08.495Z
Learnt from: DevelopingTom
Repo: openfrontio/OpenFrontIO PR: 1900
File: src/core/execution/SAMLauncherExecution.ts:103-111
Timestamp: 2025-08-24T11:13:08.495Z
Learning: In SAMLauncherExecution.ts, the cached target bug can only occur if: 1) SAM is not on cooldown when nuke is in range, 2) SAM goes on cooldown right after computing trajectory, 3) SAM becomes available again before nuke explodes. This is not possible with current cooldown values but the fix is still valuable for robustness.

Applied to files:

  • src/core/execution/SAMLauncherExecution.ts
📚 Learning: 2025-10-20T20:15:28.858Z
Learnt from: sambokai
Repo: openfrontio/OpenFrontIO PR: 2225
File: src/core/execution/FakeHumanExecution.ts:51-51
Timestamp: 2025-10-20T20:15:28.858Z
Learning: In src/core/execution/FakeHumanExecution.ts, game balance constants like MIRV_COOLDOWN_TICKS, MIRV_HESITATION_ODDS, VICTORY_DENIAL_TEAM_THRESHOLD, VICTORY_DENIAL_INDIVIDUAL_THRESHOLD, and STEAMROLL_CITY_GAP_MULTIPLIER are experimental tuning parameters subject to frequent change during balance testing. Do not flag changes to these values as issues or compare them against previous values.

Applied to files:

  • src/core/execution/SAMLauncherExecution.ts
📚 Learning: 2025-10-27T09:47:26.395Z
Learnt from: sambokai
Repo: openfrontio/OpenFrontIO PR: 2225
File: src/core/execution/FakeHumanExecution.ts:770-795
Timestamp: 2025-10-27T09:47:26.395Z
Learning: In src/core/execution/FakeHumanExecution.ts, the selectSteamrollStopTarget() method intentionally allows MIRV targeting when secondHighest city count is 0 (e.g., nuclear endgame scenarios where structures are destroyed). This is valid game design—"if you can afford it, you're good to go"—and should not be flagged as requiring a guard condition.

Applied to files:

  • src/core/execution/SAMLauncherExecution.ts
🧬 Code graph analysis (1)
src/client/graphics/layers/StructureIconsLayer.ts (2)
src/core/game/GameView.ts (2)
  • unit (658-660)
  • level (167-169)
src/core/game/Game.ts (1)
  • BuildableUnit (758-764)
🔇 Additional comments (6)
src/core/execution/SAMLauncherExecution.ts (2)

53-53: LGTM! Level-aware range calculation implemented correctly.

The range now correctly uses the SAM's current level to determine its effective range. This aligns with the PR objective of extending SAM range for stacked (higher-level) SAMs.


85-85: LGTM! Pre-shooting detection uses maximum range appropriately.

Using "MAX" for detection range allows the SAM to detect and compute trajectories for nukes that will eventually enter range, enabling the pre-shot targeting system to work correctly at all SAM levels.

src/client/graphics/layers/StructureIconsLayer.ts (4)

61-61: LGTM! Tracking range level enables optimization.

The rangeLevel field allows updateGhostRange to skip unnecessary range recreation when the level hasn't changed.


376-380: LGTM! Ghost range initialization properly deferred.

Setting range to null initially and then computing the base level separately is a clean approach that allows the range to be created with the correct level from the start.


422-445: LGTM! Range update logic is efficient and correct.

The method properly:

  • Guards against missing ghost
  • Optimizes by skipping recreation when level unchanged
  • Cleans up old range before creating new one
  • Reuses container position and delegates to factory

281-282: LGTM! Ghost range updates based on buildable unit state.

Resolving and applying the range level during each render cycle ensures the ghost preview reflects whether the player is upgrading (showing next level's range) or building new (showing level 1 range).

Comment on lines 916 to 929
// i.e. when a SAM has level >= 5, it outranges hydros
const maxRangeLevel = 5;
const numericLevel = level === "MAX" ? maxRangeLevel : Math.max(1, level);

const safetyMargin = 10;

const baseRange = this.defaultSamRange();
const hydroRange = this.nukeMagnitudes(UnitType.HydrogenBomb).outer;

const step = Math.ceil(
(hydroRange + safetyMargin - baseRange) / Math.max(1, maxRangeLevel - 1),
);

return baseRange + step * (Math.min(numericLevel, maxRangeLevel) - 1);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think basing it on the nuke magnitudes is a bit too complex. i'm thinking we use a rational function instead, something like:

return 250 * level/(level + 5)

this will have it asymptote ate 250 pixel range.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

alright, changed it to something simpler now:
150 - 480 / (level + 5)
results in level 1 = 70 (defaultSamRange), level 5 = 102 (just above hydro), approaches 150
image
i think a higher cap would be extreme, 250 is 2.5 hydro ranges, basically unkillable at that point. new screenshot for visualization:
Pasted image

if (this.potentialUpgrade) {
return this.potentialUpgrade.unit.level() + 1;
}
return 2;
Copy link
Collaborator

Choose a reason for hiding this comment

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

why return 2 here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was coding a bit too defensive here I suppose, because just based on types, the unit could still be undefined at that point, so 2 was sort of a sane guess given this is in the canUpgrade branch.
I've now changed it to simply error if such a state occured, which should never happen.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/client/graphics/layers/StructureIconsLayer.ts (1)

401-417: Handle missing upgrade unit more explicitly.

When canUpgrade is set but the existing unit cannot be found (line 408-413), the code logs an error and falls through to return 1. This is misleading since we're upgrading an unknown-level unit, not building a new one.

Consider returning undefined to indicate the level cannot be determined, or explicitly return a fallback value:

   if (buildableUnit.canUpgrade !== false) {
     const existing = this.game.unit(buildableUnit.canUpgrade);
     if (existing) {
       return existing.level() + 1;
     } else {
       console.error("Failed to find existing SAMLauncher for upgrade");
+      return undefined;  // or return 2 as a reasonable fallback
     }
   }

   return 1;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9d9edb9 and 4198163.

📒 Files selected for processing (4)
  • src/client/graphics/layers/StructureIconsLayer.ts (4 hunks)
  • src/core/configuration/Config.ts (1 hunks)
  • src/core/configuration/DefaultConfig.ts (1 hunks)
  • src/core/execution/SAMLauncherExecution.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/core/execution/SAMLauncherExecution.ts
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-24T11:13:08.495Z
Learnt from: DevelopingTom
Repo: openfrontio/OpenFrontIO PR: 1900
File: src/core/execution/SAMLauncherExecution.ts:103-111
Timestamp: 2025-08-24T11:13:08.495Z
Learning: In SAMLauncherExecution.ts, the cached target bug can only occur if: 1) SAM is not on cooldown when nuke is in range, 2) SAM goes on cooldown right after computing trajectory, 3) SAM becomes available again before nuke explodes. This is not possible with current cooldown values but the fix is still valuable for robustness.

Applied to files:

  • src/core/configuration/Config.ts
  • src/core/configuration/DefaultConfig.ts
📚 Learning: 2025-10-20T20:15:28.858Z
Learnt from: sambokai
Repo: openfrontio/OpenFrontIO PR: 2225
File: src/core/execution/FakeHumanExecution.ts:51-51
Timestamp: 2025-10-20T20:15:28.858Z
Learning: In src/core/execution/FakeHumanExecution.ts, game balance constants like MIRV_COOLDOWN_TICKS, MIRV_HESITATION_ODDS, VICTORY_DENIAL_TEAM_THRESHOLD, VICTORY_DENIAL_INDIVIDUAL_THRESHOLD, and STEAMROLL_CITY_GAP_MULTIPLIER are experimental tuning parameters subject to frequent change during balance testing. Do not flag changes to these values as issues or compare them against previous values.

Applied to files:

  • src/core/configuration/DefaultConfig.ts
🧬 Code graph analysis (3)
src/core/configuration/Config.ts (2)
src/core/game/UnitImpl.ts (1)
  • level (406-408)
src/core/game/GameView.ts (1)
  • level (167-169)
src/client/graphics/layers/StructureIconsLayer.ts (3)
src/core/game/GameView.ts (2)
  • unit (658-660)
  • level (167-169)
src/core/game/Game.ts (1)
  • BuildableUnit (758-764)
src/core/game/UnitImpl.ts (1)
  • level (406-408)
src/core/configuration/DefaultConfig.ts (2)
src/core/game/UnitImpl.ts (1)
  • level (406-408)
src/core/game/GameView.ts (1)
  • level (167-169)
🔇 Additional comments (6)
src/core/configuration/DefaultConfig.ts (1)

916-919: Verify the formula choice with maintainer.

The implementation uses 150 - 480 / (level + 5), but the past review comment suggested 250 * level/(level + 5) with asymptote at 250. Your formula achieves the stated goals (level 1 = 70, level 5 just above hydro range), but differs from the maintainer's suggestion. Is this intentional, or should the formula match the suggestion more closely?

src/core/configuration/Config.ts (1)

173-173: Clean interface extension.

The parameterized samRange(level) signature is correctly added alongside the existing defaultSamRange() method, maintaining backward compatibility.

src/client/graphics/layers/StructureIconsLayer.ts (4)

61-61: Good optimization with level tracking.

The optional rangeLevel property enables efficient early returns in updateGhostRange by detecting when the level hasn't changed.


281-282: Range update properly integrated into render flow.

The level resolution and range update are correctly placed after the buildable unit is determined, ensuring the range visualization reflects the current upgrade state.


376-380: Deferred range creation improves clarity.

Initializing range to null and creating it after resolving the base level is cleaner than creating it immediately with potentially incorrect parameters.


419-442: Solid implementation with optimization.

The early return at line 424 when rangeLevel === level prevents unnecessary range destruction and recreation. The cleanup and state management flow is correct.

@NewYearNewPhil
Copy link
Contributor Author

@evanpelle the defaultSamRange config value is now only used in the structureSpawnTileValue logic for nations. Let me know if I should get rid of it entirely.

@ryanbarlow97
Copy link
Contributor

ryanbarlow97 commented Nov 2, 2025

I feel like I would never stack 50 SAM sites in one place (nor get enough money with the new gold trade income!)

Would it be possible to make a max upgrade of ~10 SAM sites? It would be better to do 5x10 stacks of SAMs than one 50 stack. - And it would be unfair to allow people to upgrade more than 10 levels IMO.

@NewYearNewPhil NewYearNewPhil changed the title Extend SAM Range to cover Hydros when stacked (Input needed) Extend SAM Range to cover Hydros when stacked Nov 3, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/core/configuration/DefaultConfig.ts (1)

916-919: Fix missing input validation to prevent edge case issues.

The method lacks validation for the level parameter, creating critical edge cases:

  • Level 0: returns 54 (below base range of 70)
  • Level -5: division by zero crash
  • Negative levels: unpredictable behavior

Apply this diff to clamp the level to safe values:

 samRange(level: number): number {
   // rational growth function (level 1 = 70, level 5 just above hydro range, asymptotically approaches 150)
-  return this.maxSamRange() - 480 / (level + 5);
+  const clampedLevel = Math.max(1, level);
+  return this.maxSamRange() - 480 / (clampedLevel + 5);
 }
🧹 Nitpick comments (2)
src/core/configuration/DefaultConfig.ts (1)

916-923: Consider adding unit tests for this critical range calculation.

Since you requested input on testing in the PR description, and this method affects SAM targeting, rendering, and ghost previews across the codebase, unit tests would help verify:

  • Level 1 returns base range (70)
  • Levels 2-4 interpolate correctly
  • Level 5+ returns expected values
  • Edge cases (level 0, negative values) are handled

Do you want me to generate a test suite for the samRange and maxSamRange methods?

src/core/configuration/Config.ts (1)

172-174: Remove or consolidate defaultSamRange() for API consistency.

Analysis shows that samRange(1) returns exactly 70—the same value as defaultSamRange(). Since defaultSamRange() is called only once (in `structureSpawnTileValue.ts:115), you have two options:

  1. Migrate to samRange(1): Replace the single call with samRange(1) and remove defaultSamRange() from the interface and implementation. This eliminates redundancy.

  2. Keep defaultSamRange() documented: If you prefer the convenience API for clarity, add a comment explaining that it's an alias for samRange(1) (level 1 SAM range).

The current design has samRange(level) used actively (3 call sites) while defaultSamRange() sits unused except once. Consolidating simplifies the API surface.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4198163 and 8e45ad5.

📒 Files selected for processing (3)
  • src/core/configuration/Config.ts (1 hunks)
  • src/core/configuration/DefaultConfig.ts (1 hunks)
  • src/core/execution/SAMLauncherExecution.ts (2 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-08-24T11:13:08.495Z
Learnt from: DevelopingTom
Repo: openfrontio/OpenFrontIO PR: 1900
File: src/core/execution/SAMLauncherExecution.ts:103-111
Timestamp: 2025-08-24T11:13:08.495Z
Learning: In SAMLauncherExecution.ts, the cached target bug can only occur if: 1) SAM is not on cooldown when nuke is in range, 2) SAM goes on cooldown right after computing trajectory, 3) SAM becomes available again before nuke explodes. This is not possible with current cooldown values but the fix is still valuable for robustness.

Applied to files:

  • src/core/execution/SAMLauncherExecution.ts
  • src/core/configuration/DefaultConfig.ts
  • src/core/configuration/Config.ts
📚 Learning: 2025-10-20T20:15:28.858Z
Learnt from: sambokai
Repo: openfrontio/OpenFrontIO PR: 2225
File: src/core/execution/FakeHumanExecution.ts:51-51
Timestamp: 2025-10-20T20:15:28.858Z
Learning: In src/core/execution/FakeHumanExecution.ts, game balance constants like MIRV_COOLDOWN_TICKS, MIRV_HESITATION_ODDS, VICTORY_DENIAL_TEAM_THRESHOLD, VICTORY_DENIAL_INDIVIDUAL_THRESHOLD, and STEAMROLL_CITY_GAP_MULTIPLIER are experimental tuning parameters subject to frequent change during balance testing. Do not flag changes to these values as issues or compare them against previous values.

Applied to files:

  • src/core/execution/SAMLauncherExecution.ts
  • src/core/configuration/DefaultConfig.ts
  • src/core/configuration/Config.ts
📚 Learning: 2025-10-27T09:47:26.395Z
Learnt from: sambokai
Repo: openfrontio/OpenFrontIO PR: 2225
File: src/core/execution/FakeHumanExecution.ts:770-795
Timestamp: 2025-10-27T09:47:26.395Z
Learning: In src/core/execution/FakeHumanExecution.ts, the selectSteamrollStopTarget() method intentionally allows MIRV targeting when secondHighest city count is 0 (e.g., nuclear endgame scenarios where structures are destroyed). This is valid game design—"if you can afford it, you're good to go"—and should not be flagged as requiring a guard condition.

Applied to files:

  • src/core/execution/SAMLauncherExecution.ts
🧬 Code graph analysis (2)
src/core/configuration/DefaultConfig.ts (2)
src/core/game/UnitImpl.ts (1)
  • level (406-408)
src/core/game/GameView.ts (1)
  • level (167-169)
src/core/configuration/Config.ts (2)
src/core/game/UnitImpl.ts (1)
  • level (406-408)
src/core/game/GameView.ts (1)
  • level (167-169)
🪛 GitHub Actions: 🧪 CI
src/core/execution/SAMLauncherExecution.ts

[error] 228-228: Test assertion failed: expected 'sam1.isInCooldown()' to be falsy, but received true.


[warning] 108-108: Console warning: 'cannot build Nuke' during execution.

🔇 Additional comments (3)
src/core/execution/SAMLauncherExecution.ts (3)

53-53: LGTM!

Correctly uses the level-based SAM range for this specific SAM's range check.


85-85: LGTM!

Using maxSamRange() for detection range allows the SAM to preshot nukes beyond its current level's range, which aligns with the targeting system's design.


228-228: Confirm whether SAM range changes caused test failure and whether behavior change is intentional.

The test "SAMs should target only nukes aimed at nearby targets if not close to launch site" verifies that sam1 (at position 50,1) should not intercept a nuke targeted 149 tiles away at (199,1), while sam2 (at 199,1) should intercept it. The test currently fails because sam1.isInCooldown() is true when it should be false.

This indicates sam1 fired when it shouldn't have. Since the test directly validates range-based targeting logic, verify whether your SAM range changes extended the intercept range beyond 149 tiles. If so, either update the test expectations if this is intentional, or adjust the range configuration.

@NewYearNewPhil NewYearNewPhil force-pushed the increate-stacked-sam-range branch from 8e45ad5 to cd60ea3 Compare November 4, 2025 06:39
coderabbitai[bot]
coderabbitai bot previously approved these changes Nov 4, 2025
@evanpelle
Copy link
Collaborator

Looks like a SAM test is failing

evanpelle
evanpelle previously approved these changes Nov 5, 2025
Copy link
Collaborator

@evanpelle evanpelle left a comment

Choose a reason for hiding this comment

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

thanks!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 00fb22f and ec6328b.

📒 Files selected for processing (1)
  • tests/util/TestConfig.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
tests/util/TestConfig.ts (2)
src/core/game/UnitImpl.ts (1)
  • level (406-408)
src/core/game/GameView.ts (1)
  • level (167-169)

@NewYearNewPhil
Copy link
Contributor Author

Looks like a SAM test is failing

fixed in ec6328b

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.

4 participants