Skip to content

Trade3#3436

Draft
ryanbarlow97 wants to merge 13 commits intomainfrom
trade3
Draft

Trade3#3436
ryanbarlow97 wants to merge 13 commits intomainfrom
trade3

Conversation

@ryanbarlow97
Copy link
Copy Markdown
Contributor

If this PR fixes an issue, link it below. If not, delete these two lines.
Resolves #(issue number)

Description:

Describe the PR.

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:

DISCORD_USERNAME

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 15, 2026

Walkthrough

The pull request updates trade ship mechanics by renaming a configuration parameter from numTradeShips to numPlayerPorts across multiple files. It refactors trade ship spawn rate logic to use player port counts instead of global trade ship counts, simplifies the trade ship gold formula, changes MIRV cost to a static value, and removes a timeout wrapper from the Docker supervisor command.

Changes

Cohort / File(s) Summary
Configuration & Execution
src/core/configuration/Config.ts, src/core/configuration/DefaultConfig.ts, src/core/execution/PortExecution.ts
Parameter numTradeShips renamed to numPlayerPorts in spawn rate method. Trade ship spawn rate logic replaced from sigmoid-based calculation to hardcoded values for small port counts and fixed divisor of 50. Trade ship gold formula simplified from sigmoid+multiplier curve to direct power formula. MIRV cost changed from dynamic player-dependent calculation to static 35M gold. PortExecution now counts player ports instead of global trade ships when calculating spawn rate.
Infrastructure
Dockerfile
Removed timeout 25h wrapper from supervisord invocation in conditional branch, both branches now execute supervisord directly without timeout limit.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • use v22 trade meta #2935: Directly modifies the same configuration and execution entities—parameter renames, spawn rate logic, and trade ship mechanics are shared between both PRs.

Poem

⚓ Ports now guide the traders true,
Where gold flows in a simpler hue,
No global count, just player's own,
Each MIRV cost in static stone. 🚀

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Trade3' is vague and generic, using a non-descriptive term that does not convey meaningful information about the changeset. Replace with a descriptive title that summarizes the main changes, such as 'Refactor trade ship spawning to use player port count instead of global trade ship count'.
Description check ❓ Inconclusive The description is an unfilled template with placeholder text and no actual details about the changes made in this pull request. Complete the description with details about what was changed and why, including specifics about trade ship spawn logic updates and related configuration changes.
✅ Passed checks (1 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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
Copy Markdown
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: 2

🧹 Nitpick comments (1)
startup.sh (1)

88-92: Redundant conditional: both branches do the same thing.

After removing the timeout wrapper, the if and else branches now execute identical commands. This conditional can be simplified.

♻️ Proposed simplification
-if [ "$DOMAIN" = openfront.dev ] && [ "$SUBDOMAIN" != main ]; then
-    exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
-else
-    exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
-fi
+exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@startup.sh` around lines 88 - 92, The if/else conditional checking DOMAIN and
SUBDOMAIN is redundant because both branches call exec /usr/bin/supervisord -c
/etc/supervisor/conf.d/supervisord.conf; remove the entire if/else block and
replace it with a single exec /usr/bin/supervisord -c
/etc/supervisor/conf.d/supervisord.conf line (references: variables DOMAIN and
SUBDOMAIN, and the supervisord command path).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/core/configuration/DefaultConfig.ts`:
- Around line 387-390: In DefaultConfig (method returning the object with cost:
this.costWrapper(() => 35_000_000)), remove the unreachable `break` that follows
the `return` — locate the block in DefaultConfig where the function returns `{
cost: this.costWrapper(...) }` and delete the redundant `break` statement to
eliminate dead code.
- Around line 319-328: The current fallback for numPlayerPorts > 12 uses
Math.floor((100 * rejectionModifier) / 50) which produces an unexpected large
jump; replace this with a scaling formula that continues with numPlayerPorts
(e.g., return Math.max(2, Math.min(95, Math.floor((100 * rejectionModifier) /
Math.max(1, numPlayerPorts))))), referencing numPlayerPorts and
tradeShipSpawnRejections/rejectionModifier so spawn chance scales smoothly and
is clamped to reasonable bounds.

---

Nitpick comments:
In `@startup.sh`:
- Around line 88-92: The if/else conditional checking DOMAIN and SUBDOMAIN is
redundant because both branches call exec /usr/bin/supervisord -c
/etc/supervisor/conf.d/supervisord.conf; remove the entire if/else block and
replace it with a single exec /usr/bin/supervisord -c
/etc/supervisor/conf.d/supervisord.conf line (references: variables DOMAIN and
SUBDOMAIN, and the supervisord command path).
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0291d1b4-17a9-4904-af22-d80a04e33656

📥 Commits

Reviewing files that changed from the base of the PR and between 371c51f and c7c9ed9.

📒 Files selected for processing (4)
  • src/core/configuration/Config.ts
  • src/core/configuration/DefaultConfig.ts
  • src/core/execution/PortExecution.ts
  • startup.sh

Comment on lines +319 to +328
if (numPlayerPorts <= 3) return 18;
if (numPlayerPorts <= 5) return 25;
if (numPlayerPorts <= 8) return 35;
if (numPlayerPorts <= 10) return 40;
if (numPlayerPorts <= 12) return 45;

// Pity timer: increases spawn chance after consecutive rejections
const rejectionModifier = 1 / (tradeShipSpawnRejections + 1);

return Math.floor((100 * rejectionModifier) / baseSpawnRate);
return Math.floor((100 * rejectionModifier) / 50);
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.

⚠️ Potential issue | 🟠 Major

Possible logic issue: spawn rate drops sharply for >12 ports.

For numPlayerPorts <= 12, the function returns 45 (about 2.2% spawn chance). But for numPlayerPorts > 12, it returns Math.floor(100 / 50) = 2 when there are no rejections, meaning a 50% spawn chance.

This seems like a big jump. Is this intentional? If you meant to continue the scaling pattern, consider something like:

🐛 If this is unintentional, here is a possible fix
     if (numPlayerPorts <= 12) return 45;

     // Pity timer: increases spawn chance after consecutive rejections
     const rejectionModifier = 1 / (tradeShipSpawnRejections + 1);

-    return Math.floor((100 * rejectionModifier) / 50);
+    // Continue scaling for larger port counts
+    const baseRate = 45 + Math.floor((numPlayerPorts - 12) * 5);
+    return Math.floor(baseRate * rejectionModifier);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/configuration/DefaultConfig.ts` around lines 319 - 328, The current
fallback for numPlayerPorts > 12 uses Math.floor((100 * rejectionModifier) / 50)
which produces an unexpected large jump; replace this with a scaling formula
that continues with numPlayerPorts (e.g., return Math.max(2, Math.min(95,
Math.floor((100 * rejectionModifier) / Math.max(1, numPlayerPorts))))),
referencing numPlayerPorts and tradeShipSpawnRejections/rejectionModifier so
spawn chance scales smoothly and is clamped to reasonable bounds.

Comment on lines +387 to 390
return {
cost: this.costWrapper(() => 35_000_000),
};
break;
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.

⚠️ Potential issue | 🟡 Minor

Dead code: break after return is unreachable.

The return statement exits the function, so the break on Line 390 never executes.

🧹 Proposed fix
       case UnitType.MIRV:
         return {
           cost: this.costWrapper(() => 35_000_000),
         };
-        break;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return {
cost: this.costWrapper(() => 35_000_000),
};
break;
return {
cost: this.costWrapper(() => 35_000_000),
};
🧰 Tools
🪛 Biome (2.4.6)

[error] 390-390: This code is unreachable

(lint/correctness/noUnreachable)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/configuration/DefaultConfig.ts` around lines 387 - 390, In
DefaultConfig (method returning the object with cost: this.costWrapper(() =>
35_000_000)), remove the unreachable `break` that follows the `return` — locate
the block in DefaultConfig where the function returns `{ cost:
this.costWrapper(...) }` and delete the redundant `break` statement to eliminate
dead code.

@github-project-automation github-project-automation bot moved this from Triage to Development in OpenFront Release Management Mar 15, 2026
Copy link
Copy Markdown
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

🧹 Nitpick comments (2)
src/core/configuration/DefaultConfig.ts (1)

392-396: Keep MIRV on the shared cache path.

This early return skips this.unitInfoCache.set(type, info) at Line 468, so MIRV rebuilds a fresh UnitInfo object and cost closure on every lookup while every other unit type is memoized.

♻️ Reuse the normal cached path
       case UnitType.MIRV:
-        return {
+        info = {
           cost: this.costWrapper(() => 35_000_000),
         };
         break;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/configuration/DefaultConfig.ts` around lines 392 - 396, The MIRV
case in DefaultConfig.ts returns early and bypasses the shared cache, causing
MIRV to be rebuilt every lookup; change the UnitType.MIRV branch to assign the
info object (including cost: this.costWrapper(() => 35_000_000)) to the same
local variable used by the switch and do not return there so execution reaches
this.unitInfoCache.set(type, info) (preserving the cost closure via
this.costWrapper) instead of returning immediately.
Dockerfile (1)

79-83: Drop the dead branch, or restore the non-main behavior.

Both sides now exec the same supervisord command, so this DOMAIN/SUBDOMAIN check is dead. If this environment split is still needed, the branch needs different commands; otherwise simplify it away.

♻️ Simplify the script if the split is no longer needed
-if [ "$DOMAIN" = openfront.dev ] && [ "$SUBDOMAIN" != main ]; then
-    exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
-else
-    exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
-fi
+exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Dockerfile` around lines 79 - 83, The conditional block checking DOMAIN and
SUBDOMAIN is dead because both branches run the same supervisord command; either
remove the if/else and run exec /usr/bin/supervisord -c
/etc/supervisor/conf.d/supervisord.conf unconditionally, or restore the intended
non-main behavior by changing the alternative branch to the correct command (or
different supervisord config) for the case when DOMAIN=openfront.dev and
SUBDOMAIN!=main; edit the shell snippet that references DOMAIN and SUBDOMAIN and
update the branch logic accordingly so that only meaningful differences remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/core/configuration/DefaultConfig.ts`:
- Around line 321-334: The tail branch of
tradeShipSpawnRate(tradeShipSpawnRejections, numPlayerPorts) can return 0 for
tradeShipSpawnRejections >= 2; clamp the computed pity-timer result to a minimum
of 1 so callers never receive zero. Locate the final return (currently
Math.floor((100 * rejectionModifier) / 50)) and change it to return Math.max(1,
Math.floor((100 * rejectionModifier) / 50)) (or otherwise ensure the computed
value is at least 1) so the 1/tradeShipSpawnRate contract holds.

---

Nitpick comments:
In `@Dockerfile`:
- Around line 79-83: The conditional block checking DOMAIN and SUBDOMAIN is dead
because both branches run the same supervisord command; either remove the
if/else and run exec /usr/bin/supervisord -c
/etc/supervisor/conf.d/supervisord.conf unconditionally, or restore the intended
non-main behavior by changing the alternative branch to the correct command (or
different supervisord config) for the case when DOMAIN=openfront.dev and
SUBDOMAIN!=main; edit the shell snippet that references DOMAIN and SUBDOMAIN and
update the branch logic accordingly so that only meaningful differences remain.

In `@src/core/configuration/DefaultConfig.ts`:
- Around line 392-396: The MIRV case in DefaultConfig.ts returns early and
bypasses the shared cache, causing MIRV to be rebuilt every lookup; change the
UnitType.MIRV branch to assign the info object (including cost:
this.costWrapper(() => 35_000_000)) to the same local variable used by the
switch and do not return there so execution reaches this.unitInfoCache.set(type,
info) (preserving the cost closure via this.costWrapper) instead of returning
immediately.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d15f4214-f9d4-44be-98a3-93c6f430093b

📥 Commits

Reviewing files that changed from the base of the PR and between bb7e43a and 8d2301e.

📒 Files selected for processing (4)
  • Dockerfile
  • src/core/configuration/Config.ts
  • src/core/configuration/DefaultConfig.ts
  • src/core/execution/PortExecution.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/core/execution/PortExecution.ts

Comment on lines 321 to +334
tradeShipSpawnRate(
tradeShipSpawnRejections: number,
numTradeShips: number,
numPlayerPorts: number,
): number {
const decayRate = Math.LN2 / 50;

// Approaches 0 as numTradeShips increase
const baseSpawnRate = 1 - sigmoid(numTradeShips, decayRate, 200);
if (numPlayerPorts <= 3) return 18;
if (numPlayerPorts <= 5) return 25;
if (numPlayerPorts <= 8) return 35;
if (numPlayerPorts <= 10) return 40;
if (numPlayerPorts <= 12) return 45;

// Pity timer: increases spawn chance after consecutive rejections
const rejectionModifier = 1 / (tradeShipSpawnRejections + 1);

return Math.floor((100 * rejectionModifier) / baseSpawnRate);
return Math.floor((100 * rejectionModifier) / 50);
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.

⚠️ Potential issue | 🔴 Critical

Keep tradeShipSpawnRate() above zero.

Line 334 returns 0 for any tradeShipSpawnRejections >= 2, which breaks the 1 / tradeShipSpawnRate contract in the comment above and hands the caller an invalid probability. At minimum, clamp this branch to 1.

🐛 Minimal fix to preserve the contract
     // Pity timer: increases spawn chance after consecutive rejections
     const rejectionModifier = 1 / (tradeShipSpawnRejections + 1);

-    return Math.floor((100 * rejectionModifier) / 50);
+    return Math.max(1, Math.floor((100 * rejectionModifier) / 50));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/configuration/DefaultConfig.ts` around lines 321 - 334, The tail
branch of tradeShipSpawnRate(tradeShipSpawnRejections, numPlayerPorts) can
return 0 for tradeShipSpawnRejections >= 2; clamp the computed pity-timer result
to a minimum of 1 so callers never receive zero. Locate the final return
(currently Math.floor((100 * rejectionModifier) / 50)) and change it to return
Math.max(1, Math.floor((100 * rejectionModifier) / 50)) (or otherwise ensure the
computed value is at least 1) so the 1/tradeShipSpawnRate contract holds.

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

Labels

None yet

Projects

Status: Development

Development

Successfully merging this pull request may close these issues.

2 participants