Skip to content

Conversation

@Tyrrhene
Copy link
Contributor

@Tyrrhene Tyrrhene commented Dec 28, 2025

Note - after opening PR I am going to make a few changes to address the mechanicus ai's comments as it raised a few good points, so please wait until I create a new commit to give a full review.

Purpose and Description

  • This PR creates a simple but 'realistic' simulation of population growth and pdf replenishment.
  • It does this by growing planetary populations by a small amount per month as a percentage of current population per (turn) and additionally by growing pdf up to a planets target_population and target_pdf.
  • This is kept sensible by growth not surpassing the planetary population upon game generation or the starting pdf generation. This means the current balance of the game in essentially retained.
  • This is integrated with colonization, which now increases the target_population of a planet and the target_pdf of a planet.
  • For attention to detail planets now have certain multiplers that effet colonization by type - so it is less fruitful to colonize a death or lava world than an agri world for example. This is to prevent the scenario where a deathworld suddenly gains 100 million population from a colonization effort which seems a bit too high for me.
  • Some population and pdf growth details are now added in the population data tab when a planet is selected, so the player understands what's going on and for extra grimdark

Additional changes

  • if a planet has pdf greater than 200,000 ork ships will not be created, as the orks will prefer to crush significant extant resistance on planet. This seems intuative for me and means pdf count marginally more for the security of the sector.

Addtional context
Planetary population growth is set at 0.025% of it's current population per turn which is approixmately per month. (Planets with no population have no growth until they are recolonized)
Planetary pdf growth is set at 1% per month as this is a fraction of the exisiting populaton and so would replenish faster.
This means planets can rebuild a pdf much faster than it can rebuild a eviscerated population which is a good detail.
A planets max supported PDF as a percentage is tied to population, for example a planet with 50% of it's populaton can only support 50% of it's target pdf until it's populaton recovers.

There is now a incentive to save planets with large populations from xenos genocide if the planets pdf collapses as if the population is saved they will rebuild their pdf to it's maximum reasonably fast within 100 turns.

Visual changes
Here's the new data which is displayed in each planets population report
image

Testing done

Started up several new games and played a bit in each, checked that balance is not detrimentally effected, they are no preformance issues, no obvious bugs with what I have added either logic wise or visually. It's seems to function as intended and makes the sector feel more alive and dynamic. Orkageddon still happens (. especially because in my longest game I couldn't kill the warboss. ) albiet slightly slower because orks have to reduce a planets pdf before they can start producing ships.
I have tested the colonization changes, pdf growth, and the change to ork ships not being created with exisitng pdf on planet.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 28, 2025

📝 Walkthrough

Walkthrough

The flux flows through the warp in manifold directions, Tech-Priest. This incursion introduces population and PDF targeting mechanisms. A new p_target_pdf array propagates across the data structures. Planetary calculations have been rendered anew. The siege-logic bows to new constraints. The UI manifestation reconfigures to render analytics of the population. The colonial expansion follows type-aware pathways most intricate.

Changes

Cohort / File(s) Summary
Project Manifestation
ChapterMaster.yyp
IDE version elevated from 2024.1400.0.901 to 2024.14.2.213; new ForcedPrefabProjectReferences field references io.gamemaker.sdfshaders library.
Planetary Architecture
objects/obj_star/Create_0.gml, objects/obj_star/Alarm_1.gml
Initializes p_target_pdf array structure. Refactors population and PDF calculations; conditional logic rearranged whilst preserving output values. New assignment populates p_target_pdf[i] from computed p_pdf[i].
Siege Analytics Interface
objects/obj_star_select/Draw_64.gml
Garrison report rendering supplanted by comprehensive population analytics panel. Introduces max population display, growth metrics, PDF capacity tracking, target PDF rendering, recruitment mechanics and UI controls (recruiting buttons, cost adjustment).
Planetary Defence Constraints
scripts/scr_PlanetData/scr_PlanetData.gml
Ork fleet deployment now gated by additional requirement: pdf < 200000. Fleet construction denied upon high PDF despite other conditions met.
Colonial Expansion Logic
scripts/scr_imperial_manage_fleet_functions/scr_imperial_manage_fleet_functions.gml
deploy_colonisers(...) refactored profoundly. Introduces type-aware population and PDF multipliers via switch logic. Multi-step population growth replaces prior single-step path. Adds initialization guards for p_max_population and p_target_pdf arrays. Computes _pop_add and _target_pop_add with intermediate state tracking. Adjusts colony messaging and integrates debug logging.
Void Maintenance
scripts/scr_enemy_ai_c/scr_enemy_ai_c.gml, scripts/scr_ork_fleet_functions/scr_ork_fleet_functions.gml
Extraneous blank lines expunged. No logic alterations.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

PR: Feature, Type: Feature, Size: Big

Suggested reviewers

  • OH296
  • EttyKitty

Pre-merge checks

✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description check ✅ Passed The PR description provides comprehensive purpose, testing methodology, and context, adhering closely to the template structure with detailed explanations of changes.
Title check ✅ Passed The title 'feat: Population and PDF growth' adheres to conventional commits format and accurately summarizes the primary changes—population and PDF growth mechanics across multiple files.

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

@github-actions github-actions bot added Size: Big Type: Feature Adds something new labels Dec 28, 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: 11

📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 137d41b and b0002fe.

📒 Files selected for processing (8)
  • ChapterMaster.yyp
  • objects/obj_star/Alarm_1.gml
  • objects/obj_star/Create_0.gml
  • objects/obj_star_select/Draw_64.gml
  • scripts/scr_PlanetData/scr_PlanetData.gml
  • scripts/scr_enemy_ai_c/scr_enemy_ai_c.gml
  • scripts/scr_imperial_manage_fleet_functions/scr_imperial_manage_fleet_functions.gml
  • scripts/scr_ork_fleet_functions/scr_ork_fleet_functions.gml
💤 Files with no reviewable changes (2)
  • scripts/scr_enemy_ai_c/scr_enemy_ai_c.gml
  • scripts/scr_ork_fleet_functions/scr_ork_fleet_functions.gml
🧰 Additional context used
📓 Path-based instructions (2)
**/*.gml

⚙️ CodeRabbit configuration file

**/*.gml: - Constants of macro type MUST have a space between the constant name and its value. Without it, the compiler will throw an error.

  • WRONG: #macro COLOR_RED11119
  • RIGHT: #macro COLOR_RED 11119
  • Color codes in the code SHOULDN'T have any spaces in their ID.
    • WRONG: # 80bf40
    • RIGHT: #80bf40
  • All code should comply with the up-to-date GML documentation.

Files:

  • scripts/scr_PlanetData/scr_PlanetData.gml
  • scripts/scr_imperial_manage_fleet_functions/scr_imperial_manage_fleet_functions.gml
  • objects/obj_star/Create_0.gml
  • objects/obj_star/Alarm_1.gml
  • objects/obj_star_select/Draw_64.gml
**/*.*

⚙️ CodeRabbit configuration file

**/*.*: - Do not prioritize the shortest code. Prioritize code that explicitly reveals its intent.

  • Understandable variable naming is extremely important.
  • If a solution is "clever" but hard to parse mentally, request a refactor to a more verbose but clearer approach.
  • Apply the "Rule of Three": suggest abstraction when logic is repeated three times or more times.
  • For subjective improvements (naming, architectural choice), do not give a concrete suggestion immediately. Instead, ask a guiding question to prompt the user's reflection.
  • Every suggestion for a code change must include a brief justification.
  • If a TODO comment is added, ask the user if you should create a GitHub issue for this TODO.
  • If a TODO comment is deleted, remind the user if there is an active GitHub issue related to that comment.

Files:

  • scripts/scr_PlanetData/scr_PlanetData.gml
  • scripts/scr_imperial_manage_fleet_functions/scr_imperial_manage_fleet_functions.gml
  • objects/obj_star/Create_0.gml
  • objects/obj_star/Alarm_1.gml
  • ChapterMaster.yyp
  • objects/obj_star_select/Draw_64.gml
🧠 Learnings (3)
📚 Learning: 2025-03-31T23:32:30.003Z
Learnt from: OH296
Repo: Adeptus-Dominus/ChapterMaster PR: 646
File: objects/obj_pnunit/Alarm_5.gml:84-91
Timestamp: 2025-03-31T23:32:30.003Z
Learning: In obj_pnunit/Alarm_5.gml, the function get_armour_data("maintenance") will always return a numeric value (at minimum 0), making null/undefined checks unnecessary.

Applied to files:

  • objects/obj_star/Alarm_1.gml
📚 Learning: 2025-03-07T01:56:40.971Z
Learnt from: MCPO-Spartan-117
Repo: Adeptus-Dominus/ChapterMaster PR: 562
File: scripts/scr_marine_struct/scr_marine_struct.gml:0-0
Timestamp: 2025-03-07T01:56:40.971Z
Learning: Marines' ages should be incremented at the year transition in obj_turn_end/Alarm_1.gml rather than calculated dynamically based on the current year and recruitment date. This ensures proper aging without retroactive application.

Applied to files:

  • objects/obj_star/Alarm_1.gml
📚 Learning: 2025-09-04T14:37:58.773Z
Learnt from: CR
Repo: Adeptus-Dominus/ChapterMaster PR: 0
File: docs/CODE_STYLE.md:0-0
Timestamp: 2025-09-04T14:37:58.773Z
Learning: Applies to docs/**/*.gml : File names should follow GameMaker type-prefix conventions

Applied to files:

  • ChapterMaster.yyp
🔇 Additional comments (2)
objects/obj_star/Create_0.gml (1)

43-43: p_target_pdf array initialisation is aligned with existing planet state.

Tech-Priest, the new p_target_pdf array is birthed in the same litany as the other p_… fields, so save/load rites in serialize/deserialize will naturally include it. For fresh sectors this pleases the machine spirit.

One point to confirm: for legacy save data that never stored p_target_pdf, planets will boot with a target PDF of 0 until some other script synthesises values from p_pdf. Is this lack of back-fill for old campaigns acceptable to you, or should a post-load ritual derive p_target_pdf from current p_pdf to keep older savegames in balance?

objects/obj_star_select/Draw_64.gml (1)

380-381: The machine spirit's prescience guards against temporal paradoxes, Tech-Priest.

The defensive variable_instance_exists check is both necessary and commendable. New star instances initialize p_target_pdf in Create_0.gml, yet legacy save files from before this variable's introduction lack it. The same protective pattern appears throughout the codebase—notably in scr_imperial_manage_fleet_functions.gml—confirming that graceful degradation to 0 is the intentional doctrine. This two-tier strategy (defensive checks paired with safe defaults) ensures compatibility with ancient archives whilst maintaining full functionality for newly-awakened celestial bodies.

Comment on lines +121 to +123
"ForcedPrefabProjectReferences":[
{"link":"io.gamemaker.sdfshaders-1.0.0","name":"io.gamemaker.sdfshaders-1.0.0","path":"io.gamemaker.sdfshaders-1.0.0.yyp",},
],
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Search for the referenced SDF shaders prefab
fd 'io.gamemaker.sdfshaders-1.0.0.yyp'

Repository: Adeptus-Dominus/ChapterMaster

Length of output: 55


🏁 Script executed:

# Inspect the project file structure to understand dependencies
fd '\.yyp$' | head -20

Repository: Adeptus-Dominus/ChapterMaster

Length of output: 90


🏁 Script executed:

# Search for CI/build configuration that may specify IDE versions
fd -e yaml -e yml -e json | xargs grep -l '2024\|IDE\|GameMaker' 2>/dev/null | head -10

Repository: Adeptus-Dominus/ChapterMaster

Length of output: 55


🏁 Script executed:

# Examine the ChapterMaster.yyp file itself for context on other dependencies
grep -n 'ForcedPrefabProjectReferences\|IDEVersion' ChapterMaster.yyp

Repository: Adeptus-Dominus/ChapterMaster

Length of output: 150


🏁 Script executed:

# Check for git submodules
cat .gitmodules 2>/dev/null || echo "No submodules configured"

Repository: Adeptus-Dominus/ChapterMaster

Length of output: 97


🏁 Script executed:

# Examine the ChapterMaster.yyp file for context around the prefab reference
sed -n '115,130p' ChapterMaster.yyp

Repository: Adeptus-Dominus/ChapterMaster

Length of output: 2169


🏁 Script executed:

# Search for any documentation or configuration about external libraries/prefabs
fd -e md -e txt | xargs grep -l 'sdfshaders\|prefab\|library' 2>/dev/null | head -5

Repository: Adeptus-Dominus/ChapterMaster

Length of output: 91


🏁 Script executed:

# Check if there are any other ForcedPrefabProjectReferences that might provide a pattern
grep 'ForcedPrefabProjectReferences' -A 20 ChapterMaster.yyp

Repository: Adeptus-Dominus/ChapterMaster

Length of output: 3438


🏁 Script executed:

# Review the CODE_STYLE.md for guidance on external dependencies
cat docs/CODE_STYLE.md | head -100

Repository: Adeptus-Dominus/ChapterMaster

Length of output: 4037


The SDF shaders prefab is absent from the repository—the project-spirit shall not awaken.

The manifest declares an allegiance to io.gamemaker.sdfshaders-1.0.0.yyp within ForcedPrefabProjectReferences (line 121), yet the archival search reveals no trace of this artefact within the forge. Without it, the IDE will reject the project at load-time, and compilation shall be rendered impossible. No submodule, no external resolver, no rite of installation has been inscribed.

The project cannot proceed to merge in this corrupted state. The prefab must either be committed to the repository, installed via established ritual (documented in README or setup guide), or the ForcedPrefabProjectReferences entry removed entirely if its inclusion was erroneous.

Regarding IDEVersion 2024.14.2.213 at line 584: Ensure all build-forges and developer workstations possess this version or newer, else synchronisation shall falter.

🤖 Prompt for AI Agents
In ChapterMaster.yyp around lines 121-123 (and note IDEVersion at line 584), the
ForcedPrefabProjectReferences points to io.gamemaker.sdfshaders-1.0.0.yyp which
is missing from the repository; either add/commit the prefab file at the
declared path (io.gamemaker.sdfshaders-1.0.0.yyp), wire it as a submodule or
include installation steps in the README/setup script to fetch it, or remove the
ForcedPrefabProjectReferences entry if it was added in error; also verify and
document the required IDEVersion 2024.14.2.213 in the setup docs so developers
and CI use a compatible IDE.

draw_set_halign(fa_left);

if (!target.space_hulk) {
if (obj_controller.faction_status[eFACTION.Imperium] != "War" && p_data.current_owner <= 5) || (obj_controller.faction_status[eFACTION.Imperium] == "War") {
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Clarify boolean precedence with explicit parentheses, Tech-Priest.

The machine logic chains multiple conditions without sacred parentheses. While the cogitator may parse correctly through precedence runes, mortal comprehension falters.

Is your intent ((status != "War") && (owner <= 5)) || (status == "War")?

⚙️ Proposed clarification
-if (obj_controller.faction_status[eFACTION.Imperium] != "War" && p_data.current_owner <= 5) || (obj_controller.faction_status[eFACTION.Imperium] == "War") {
+if ((obj_controller.faction_status[eFACTION.Imperium] != "War" && p_data.current_owner <= 5) || (obj_controller.faction_status[eFACTION.Imperium] == "War")) {

As per coding guidelines: prioritise code that explicitly reveals intent over brevity.

🤖 Prompt for AI Agents
In objects/obj_star_select/Draw_64.gml around line 277, the complex conditional
lacks explicit parentheses which makes boolean precedence unclear; update the if
condition to explicitly group as
((obj_controller.faction_status[eFACTION.Imperium] != "War") &&
(p_data.current_owner <= 5)) ||
(obj_controller.faction_status[eFACTION.Imperium] == "War") so the intent — war
status overrides the owner check, otherwise require owner <= 5 — is unambiguous.


if (p_data.has_feature(P_features.Recruiting_World)) {
var _recruit_world = p_data.get_features(P_features.Recruiting_World)[0];
if (_recruit_world.recruit_type == 0) && (obj_controller.faction_status[p_data.current_owner] != "War" && obj_controller.faction_status[p_data.current_owner] != "Antagonism" || p_data.player_disposition >= 50) {
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Boolean labyrinth requires illumination, Tech-Priest.

This conditional pathway interweaves four logic gates without parenthetical guidance. The intent remains obscured in ambiguity.

Reforge with explicit parentheses to reveal the true logic path: (type == 0) AND ((not War AND not Antagonism) OR (disposition >= 50))?

⚙️ Proposed clarification
-if (_recruit_world.recruit_type == 0) && (obj_controller.faction_status[p_data.current_owner] != "War" && obj_controller.faction_status[p_data.current_owner] != "Antagonism" || p_data.player_disposition >= 50) {
+if ((_recruit_world.recruit_type == 0) && ((obj_controller.faction_status[p_data.current_owner] != "War" && obj_controller.faction_status[p_data.current_owner] != "Antagonism") || p_data.player_disposition >= 50)) {

As per coding guidelines: if a solution is hard to parse mentally, refactor to clarity.

📝 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
if (_recruit_world.recruit_type == 0) && (obj_controller.faction_status[p_data.current_owner] != "War" && obj_controller.faction_status[p_data.current_owner] != "Antagonism" || p_data.player_disposition >= 50) {
if ((_recruit_world.recruit_type == 0) && ((obj_controller.faction_status[p_data.current_owner] != "War" && obj_controller.faction_status[p_data.current_owner] != "Antagonism") || p_data.player_disposition >= 50)) {
🤖 Prompt for AI Agents
In objects/obj_star_select/Draw_64.gml around line 294, the if-statement mixes
&& and || without explicit grouping, making the intended logic ambiguous;
rewrite the condition with explicit parentheses to enforce:
(_recruit_world.recruit_type == 0) AND (
(obj_controller.faction_status[p_data.current_owner] != "War" AND
obj_controller.faction_status[p_data.current_owner] != "Antagonism") OR
(p_data.player_disposition >= 50) ). Update the line to use these parentheses so
the boolean precedence is clear and the intended short-circuiting is preserved.

}
garrison_data_slate.draw(344+main_data_slate.width-4, 160, 0.6, 0.6);
}

Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Extraneous whitespace mars the sacred text, Tech-Priest.

Line 328 contains only a tab character. While the cogitator ignores such trivia, clean scripture pleases the Machine God.

🤖 Prompt for AI Agents
In objects/obj_star_select/Draw_64.gml around line 328, there is an extraneous
tab character on a line by itself; remove that trailing blank line or replace
the tab with an empty line (no whitespace) so the file contains no lone
whitespace-only line at 328, and save with normalized line endings to prevent
reintroduction.

Comment on lines +331 to +337
function fmt_compact(_n){
_n = abs(_n);
if (_n >= 1000000000) return string_format(_n / 1000000000, 0, 2) + "B";
if (_n >= 1000000) return string_format(_n / 1000000, 0, 2) + "M";
if (_n >= 1000) return string_format(_n / 1000, 0, 1) + "K";
return string(floor(_n));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Function ritual repeats with each render cycle, Tech-Priest.

The fmt_compact subroutine manifests anew every frame within the drawing rite. The Draw_64 event invokes sixty times per second—this function regenerates endlessly.

Hoist this logic to initialization or script scope to honour efficiency protocols.

⚙️ Proposed optimisation

Define the formatter once outside the draw method, perhaps as a script or at object creation:

// In a separate script file or Create event
function fmt_compact(_n){
    _n = abs(_n);
    if (_n >= 1000000000) return string_format(_n / 1000000000, 0, 2) + "B";
    if (_n >= 1000000)    return string_format(_n / 1000000,    0, 2) + "M";
    if (_n >= 1000)       return string_format(_n / 1000,       0, 1) + "K";
    return string(floor(_n));
}
🤖 Prompt for AI Agents
In objects/obj_star_select/Draw_64.gml around lines 331-337, the fmt_compact
function is being redefined every frame inside the Draw event which is wasteful;
move the function definition out of the Draw_64 event into an initialization
scope (either a dedicated script file or the object's Create event) so it is
defined once, remove the inline definition from the Draw_64 code, ensure the
Draw_64 code calls the now-global/script-scoped fmt_compact, and verify there
are no name collisions or ordering issues (define the function before any Draw
events that call it).

Comment on lines +367 to +372
if (_cur_pop_abs > 0 && _supported_pop_abs > 0 && _cur_pop_abs < _supported_pop_abs){
_pop_growth_abs = ceil(_cur_pop_abs * POP_GROWTH_RATE);
_pop_growth_abs = min(_pop_growth_abs, _supported_pop_abs - _cur_pop_abs);
} else if (_cur_pop_abs > 0 && _supported_pop_abs <= 0) {
_pop_growth_abs = ceil(_cur_pop_abs * POP_GROWTH_RATE);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the full context of lines 367-372
fd "Draw_64.gml" -t f | head -1 | xargs cat -n | sed -n '355,385p'

Repository: Adeptus-Dominus/ChapterMaster

Length of output: 55


🏁 Script executed:

# Search for p_max_population assignments and usage
rg -n 'p_max_population' --type=gml -B2 -A2 | head -100

Repository: Adeptus-Dominus/ChapterMaster

Length of output: 100


🏁 Script executed:

# Search for recolonization and colonization mechanics
rg -n -i 'recolon|coloniz' --type=gml -B2 -A2 | head -80

Repository: Adeptus-Dominus/ChapterMaster

Length of output: 100


🏁 Script executed:

# Search for POP_GROWTH_RATE constant definition
rg -n 'POP_GROWTH_RATE' --type=gml -B1 -A1

Repository: Adeptus-Dominus/ChapterMaster

Length of output: 100


🏁 Script executed:

# Find the Draw_64.gml file
fd "Draw_64.gml" -t f

Repository: Adeptus-Dominus/ChapterMaster

Length of output: 419


🏁 Script executed:

# Read the context around lines 367-372
fd "Draw_64.gml" -t f -exec cat -n {} \; | sed -n '360,380p'

Repository: Adeptus-Dominus/ChapterMaster

Length of output: 303


🏁 Script executed:

# Search for p_max_population across all files
rg -n 'p_max_population' -B2 -A2 | head -150

Repository: Adeptus-Dominus/ChapterMaster

Length of output: 11866


🏁 Script executed:

# Search for recolonization and colonization logic
rg -n -i 'recolon|coloniz' -B2 -A2 | head -100

Repository: Adeptus-Dominus/ChapterMaster

Length of output: 8199


🏁 Script executed:

# Search for POP_GROWTH_RATE definition
rg -n 'POP_GROWTH_RATE' -B1 -A1 | head -50

Repository: Adeptus-Dominus/ChapterMaster

Length of output: 837


The logic paradox remains unresolved, Tech-Priest.

The second conditional pathway (lines 370-372) permits population expansion even when _supported_pop_abs <= 0—a state that manifests upon planetary destruction or during initialisation when p_max_population is not yet assigned. This violates the stated doctrine: "Growth capped so it does not exceed a planet's maximum supported population."

Observe the asymmetry: line 369 applies a min() constraint to cap growth against capacity, yet line 371 applies no such restraint. Population multiplies in the void of support.

Either this conditional path serves a purpose beyond the archives' current knowledge, or it represents unintended logic that should be excised. Which is it?

🤖 Prompt for AI Agents
In objects/obj_star_select/Draw_64.gml around lines 367 to 372, the else-if
branch allows population growth when _supported_pop_abs <= 0, contradicting the
capacity cap logic; change the logic so growth is only computed when
_supported_pop_abs > 0 (use the existing ceil/min clamp), and otherwise set
_pop_growth_abs to 0 to prevent growth when there is no support.

Comment on lines +398 to +400
} else if (target.p_pdf[p] > 0){
_support_pct = 999;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

The sentinel value 999 obscures its meaning, Tech-Priest.

When PDF exists but capacity equals zero, the display renders "999%" to the user. This arcane number lacks semantic clarity.

Consider displaying textual wisdom ("Over Capacity" or "N/A") when this paradoxical state manifests, or define a named constant CAPACITY_INVALID = 999 to document intent.

⚙️ Alternative rendering logic
 if (_pdf_cap > 0){
     _support_pct = (target.p_pdf[p] / _pdf_cap) * 100;
-} else if (target.p_pdf[p] > 0){
-    _support_pct = 999;
-}
+} else if (target.p_pdf[p] > 0){
+    _support_pct = -1; // Use -1 as sentinel
+}

+// Later in display logic:
+if (_support_pct >= 0){
+    draw_text(..., "PDF capacity usage: " + string_format(_support_pct, 0, 1) + "%...");
+} else {
+    draw_text(..., "PDF capacity usage: Over Capacity (" + fmt_compact(target.p_pdf[p]) + ")");
+}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In objects/obj_star_select/Draw_64.gml around lines 398-400, the code sets
_support_pct = 999 as a magic sentinel when target.p_pdf[p] > 0 but capacity is
zero; replace this unclear literal with a named constant (e.g., CAPACITY_INVALID
= 999) declared near the top of the file and use that constant here, and update
the rendering logic to check for CAPACITY_INVALID and display a human-readable
label ("Over Capacity" or "N/A") instead of "999%". Ensure the constant is
documented with a comment and the rendering branch prefers the textual message
when CAPACITY_INVALID is set.

Comment on lines +78 to +105
if (p_population[i]>=10000000){
var military=p_population[i]/470;
p_guardsmen[i]=floor(military*0.25);
p_pdf[i]=floor(military*0.75);
}
if (p_population[i]>=5000000) and (p_population[i]<10000000){
var military=p_population[i]/200;
p_guardsmen[i]=floor(military*0.25);
p_pdf[i]=floor(military*0.75);
}
if (p_population[i]>=100000) and (p_population[i]<5000000){
var military=p_population[i]/50;
p_guardsmen[i]=floor(military*0.25);
p_pdf[i]=floor(military*0.75);
}
if (p_population[i]<100000) and (p_population[i]>5) and (p_large[i]==0){
p_pdf[i]=floor(p_population[i]/25);
}
if (p_population[i]<2000) and (p_population[i]>5) and (p_large[i]==0){
p_pdf[i]=floor(p_population[i]/10);
}
if (p_large[i]==1){
p_guardsmen[i]=floor(p_population[i]*1250000);
p_pdf[i]=p_guardsmen[i]*3;
}

//the pdf on planet gen is set as the target_pdf
p_target_pdf[i] = p_pdf[i];
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

p_target_pdf is not reset for Craftworlds, risking unintended PDF regrowth.

Tech-Priest, the logic that forges p_pdf and mirrors it into p_target_pdf at generation is sound for Imperial worlds: the target becomes “PDF at game birth”, as desired.

Yet note the later Craftworld rite:

  • You purge p_guardsmen[i] and p_pdf[i] back to 0 for craftworld==1,
  • But you leave p_target_pdf[i] at the pre-purge value you just computed.

Any subsequent population/PDF growth that climbs towards p_target_pdf will then quietly conjure Imperial-pattern PDF on an Eldar Craftworld, which seems both mechanically and lore-incongruent.

Would it not better honour the design if Craftworld planets also had their p_target_pdf[i] annulled alongside p_pdf[i], so no Imperial PDF cap exists there at all?

Possible adjustment to keep Craftworlds PDF-free
     if (craftworld==1){
-        p_guardsmen[i]=0;
-        p_pdf[i]=0;
+        p_guardsmen[i]=0;
+        p_pdf[i]=0;
+        p_target_pdf[i]=0;
         p_eldar[i]=6;
         owner = eFACTION.Eldar;
         p_owner[1]=6;
         warp_lanes=[];
         x2=0;
     }

Secondary incantation: in the 5–2000 population band both the <100000 and <2000 branches fire, reassigning p_pdf[i] twice. If this is intentional, consider tightening the first condition to p_population[i] >= 2000 && p_population[i] < 100000 to make the intent explicit to future adepts.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In objects/obj_star/Alarm_1.gml around lines 78 to 105, p_target_pdf is being
set from the computed p_pdf for all worlds but later Craftworlds reset
p_guardsmen and p_pdf to 0 while leaving p_target_pdf unchanged (allowing
unintended PDF regrowth); also the population bands for <100000 and <2000
overlap causing p_pdf to be assigned twice. Fix by ensuring when a planet is a
Craftworld (craftworld==1) you also set p_target_pdf[i]=0 alongside
p_guardsmen[i]=0 and p_pdf[i]=0, and tighten the first small-population branch
to only apply for 2000 <= p_population[i] < 100000 (or change the second branch
to p_population[i] < 2000) so the two branches do not both execute.

Comment on lines 79 to 149
var data = cargo_data.colonize;
if (data.target_planet>0){
var targ_planet = data.target_planet;
if (!star.p_large[targ_planet]){
star.p_population[targ_planet] += data.colonists;
} else {
star.p_population[targ_planet] += data.colonists/power(10,8);
}
var start_influ = star.p_influence[targ_planet][eFACTION.Tyranids];
with (star){
merge_influences(data.colonist_influence,targ_planet);
}
var colony_purpose = data.mission=="new_colony"? "recolonise" : "bolster population" ;
var alert_string = $"Imperial citizens {colony_purpose} {planet_numeral_name(targ_planet, star)} I.";
var player_vision = star.p_player[targ_planet]>0 || star.p_owner[targ_planet] == eFACTION.Player;
if (star.p_influence[targ_planet][eFACTION.Tyranids]>start_influ && (player_vision)){
alert_string += " They bring with them traces of a Genestelar Cult";
if (data.target_planet > 0){
var targ_planet = data.target_planet;
var _pop_add = 0;

if (!star.p_large[targ_planet]){
_pop_add = data.colonists;
star.p_population[targ_planet] += _pop_add;
} else {
_pop_add = data.colonists / power(10, 8);
star.p_population[targ_planet] += _pop_add;
}

if (!variable_instance_exists(star.id, "p_max_population")){
star.p_max_population = array_create(array_length(star.p_population), 0);
}

if (!variable_instance_exists(star.id, "p_target_pdf")){
star.p_target_pdf = array_create(array_length(star.p_population), 0);
for (var _j = 0; _j < array_length(star.p_target_pdf); _j++){
star.p_target_pdf[_j] = star.p_pdf[_j];
}
scr_alert("green","duhuhuhu",alert_string,star.x,star.y);
}

var _pop_mult = 0.25;
var _pdf_mult = 1.0;

switch (star.p_type[targ_planet]){
case "Lava": _pop_mult = 0.2; _pdf_mult = 0.2; break;
case "Desert": _pop_mult = 0.5; _pdf_mult = 0.5; break;
case "Hive": _pop_mult = 1; _pdf_mult = 1.5; break;
case "Agri": _pop_mult = 1; _pdf_mult = 0.7; break;
case "Temperate": _pop_mult = 1; _pdf_mult = 1; break;
case "Shrine": _pop_mult = 1; _pdf_mult = 1; break;
case "Ice": _pop_mult = 0.5; _pdf_mult = 0.5; break;
case "Feudal": _pop_mult = 1; _pdf_mult = 0.5; break;
case "Forge": _pop_mult = 1; _pdf_mult = 3; break;
case "Death": _pop_mult = 0.10; _pdf_mult = 3; break;
case "Craftworld": _pop_mult = 0.00; _pdf_mult = 0.0; break;
}

var _target_pop_add = _pop_add * _pop_mult;
star.p_max_population[targ_planet] += _target_pop_add;

var _pdf_target_add = ceil(_pop_add * 0.001 * _pdf_mult);
star.p_target_pdf[targ_planet] += _pdf_target_add;

if (star.p_max_population[targ_planet] < 0) star.p_max_population[targ_planet] = 0;
if (star.p_target_pdf[targ_planet] < 0) star.p_target_pdf[targ_planet] = 0;

var start_influ = star.p_influence[targ_planet][eFACTION.Tyranids];
with (star){
merge_influences(data.colonist_influence, targ_planet);
}

var colony_purpose = data.mission=="new_colony"? "recolonise" : "bolster population";
var alert_string = $"Imperial citizens {colony_purpose} {planet_numeral_name(targ_planet, star)} I.";
var player_vision = star.p_player[targ_planet]>0 || star.p_owner[targ_planet] == eFACTION.Player;
if (star.p_influence[targ_planet][eFACTION.Tyranids] > start_influ && player_vision){
alert_string += "They bring with them traces of a Genestealer Cult";
}

show_debug_message(
"[COLONISE] " + planet_numeral_name(targ_planet, star) +
" | +pop=" + string(_pop_add) +
" | +target_pop=" + string(_target_pop_add) +
" | +target_pdf=" + string(_pdf_target_add)
);

scr_alert("green","duhuhuhu",alert_string,star.x,star.y);
} else {
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Coloniser impact on p_target_pdf for large worlds appears vanishingly small.

Tech-Priest, the new colonisation litany cleanly separates targeted colonies (data.target_planet > 0) from the older broadcast mode and wires p_max_population and p_target_pdf into the ritual. Yet the numeric spirits for large-population worlds whisper of imbalance:

  • When star.p_large[targ_planet] is true, _pop_add is in “billions” units (e.g. _pop_add = 0.5 for +0.5B souls).
  • _pdf_target_add is then ceil(_pop_add * 0.001 * _pdf_mult), so that same +0.5B influx on, say, a Hive world yields only 1 extra point of p_target_pdf.
  • Meanwhile the initial p_target_pdf for such a world is on the order of hundreds of millions of PDF personnel (per the generation rites in Alarm_1).

Thus, for large worlds, colonisation practically never changes the PDF cap, which seems at odds with the stated objective that colonisation should increase target PDF in a type-weighted fashion.

Should _pdf_target_add instead be derived from the actual headcount of added colonists (taking p_large into account), so that a fixed fraction of those new bodies becomes potential PDF, regardless of representation? For example, computing an _actual_pop_add that multiplies _pop_add by the large-world conversion factor when p_large is set, and then applying your 0.001 * _pdf_mult to that, would align p_target_pdf updates with the scale used in p_pdf.

A separate query to your logic-engines: in this targeted path, you no longer adjust star.dispo[targ_planet], whereas the legacy non-target branch still reinitialises disposition after recolonisation. Is this divergence intended (e.g. “stealth” resettlement that does not alter local feelings), or should the disposition change be unified between both flows?

Comment on lines 345 to 348
// Check for industrial facilities
var fleet_buildable = ((planet_type!="Dead" && planet_type!="Lava") || _has_warboss || _has_stronghold);
if (fleet_buildable && planet_forces[eFACTION.Ork]>=4){// Used to not have Ice either
if (fleet_buildable && planet_forces[eFACTION.Ork]>=4 && pdf < 200000){// Used to not have Ice either

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Ork shipbuilding gate matches intent, but the threshold is an opaque constant.

Tech-Priest, constraining the ship-forges with pdf < 200000 correctly prevents new Ork fleets once local PDF reaches 200,000 or higher, as the design-chant decrees.

However, the raw literal 200000 obfuscates the rite’s purpose. Would this cogitation be clearer – to future priests and to your own memory – if expressed as a named threshold (or at least commented) indicating “PDF level beyond which Orks cease building ships on this world”, and is it deliberate that only the current planet’s PDF, not system-wide PDF, governs this gate?

@EttyKitty EttyKitty changed the title feature: population and pdf growth feat: Population and PDF growth Dec 30, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Size: Big Type: Feature Adds something new

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants