Skip to content

[main/2026-10-08-1819] scr_company_order:143 #1633

Closed

Description

@EttyKitty

Game version

main/2026-10-08-1819

Operating System

Windows 10

Description

Error Details:

Pop :: Execution Error - Variable Index [-1] out of range [11] - -6.TTRPG(101722,-1)

Stacktrace:

  • scr_company_order:143 >> TTRPG[co] = _company_marines.units;
  • scr_promote:anonymous:68 >> scr_company_order(obj_popup.manag);
  • scr_buttons:draw:659 >> _method();
  • draw_popup_promotion:269 >> promote_button.draw(all_good && target_role > 0);
  • obj_popup_Draw_64:4 >> draw_popup_promotion();

User Message:

The error happens when you try to promote units on the "fleet units management" screen, that can be opened via Manage Units button with a fleet is selected.

The Promote button is only available on that screen if you save and load first. And the error only happens when promoting via that specific screen. Promoting via default management screen doesn't cause errors.

Also, during this save/load sequence, a lot of these errors pop out in last_messages (for different squads and roles):

01:36:27 | ERROR | obj_ini_Create_0:anonymous:326 >> Failed to load squad 043BD046-4E4E-4941-8F0B-3C2EAC768E2D
{ longMessage : "ERROR in action number 1
of Alarm Event for alarm 0 for object obj_saveload:
Push :: Execution Error - Variable Index [6] out of range [0] - -7._roles(100781,6)
 at gml_Script_role_hierarchy (line 187) -         _roles[eROLE.DREADNOUGHT],
", script : "gml_Script_role_hierarchy", line : 187, stacktrace : [ "gml_Script_role_hierarchy (line 187)","gml_Script_determine_leader@anon@23026@UnitSquad@scr_squads (line 575) -         var hierarchy = role_hierarchy();","gml_Script_load@anon@19382@UnitSquad@scr_squads (line 486) -         determine_leader();","gml_Script_anon@5360@gml_Object_obj_ini_Create_0 (line 324) -                 _squad.load(_data);","gml_Script_scr_load (line 48) -         obj_ini.deserialize(ini_save_data);","gml_Object_obj_saveload_Alarm_0 (line 80) -         scr_load(3, global.load);" ], message : "Push :: Execution Error - Variable Index [6] out of range [0] - -7._roles(100781,6)" }

Steps to reproduce

  1. Start a new game, save it and load.
  2. Go into manage fleet units screen, via Manage Units button with fleet is selected.
  3. Selected any units, press promote button, select company, role, confrim.
  4. The error appears.

Upload screenshots

No response

Activity

  1. added theissue type on Oct 8, 2026
  2. lyra-cactus commented on Oct 8, 2026

    @lyra-cactus
    Contributor

    Investigation Report - Issue #1633: scr_company_order:143 crash on fleet promote + squad load failures

    1. Summary

    Issue #1633 reports two linked symptoms on main/2026-10-08-1819 (Windows 10):

    1. Primary crash: promoting units from the "fleet units management" screen (Manage Units button with a fleet selected) raises:
      Pop :: Execution Error - Variable Index [-1] out of range [11] - -6.TTRPG(101722,-1) at scr_company_order:143 >> TTRPG[co] = _company_marines.units;
    2. Secondary load errors: after save/load, last_messages fills with Failed to load squad <uuid> caused by:
      Push :: Execution Error - Variable Index [6] out of range [0] - -7._roles(100781,6) at gml_Script_role_hierarchy (line 187) - _roles[eROLE.DREADNOUGHT],

    Both were reproduced by: new game -> save -> load -> fleet Manage Units -> select units -> Promote -> choose company/role -> confirm.

    Root causes found:

    • A. scr_company_order(-1): fleet/adhoc manage view uses obj_controller.managing == -1. The promotion popup copies that value into obj_popup.manag and then unconditionally calls scr_company_order(manag) with -1. obj_ini.TTRPG has length 11 (indices 0..10), so TTRPG[-1] throws.
    • B. Stale sel_promoting enablement: sel_promoting is only recalculated when managing > 0, is never reset by scr_ui_refresh() / group_selection(), and is persisted by controller serialization. After a save/load it stays 1, so the Promote button appears enabled in the fleet view where it was previously disabled. This explains "only available on that screen if you save and load first".
    • C. Deserialization order bug: obj_ini.deserialize() loads squad_structs (which calls UnitSquad.load() -> determine_leader() -> role_hierarchy() -> active_roles()) before it restores player_role_data. At that point player_role_data == [], so active_roles() returns [] and every indexed access _roles[eROLE.*] fails. First failure is _roles[6] (eROLE.DREADNOUGHT == 6).

    All three should be fixed together. Fixing only (A) hides the crash but leaves broken squad leaders and an unintended promotion path.

    2. Evidence and Code Walkthrough

    2.1 TTRPG layout - valid companies are 0..10

    TTRPG is created as 11 slots (HQ + 10 companies):

    https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/objects/obj_ini/Create_0.gml#L110

    TTRPG = array_create(11, []);

    collect_company() / collect_role_group() iterate 0..companies inclusive:

    https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_UnitGroup/scr_UnitGroup.gml#L640-L642

    https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_UnitGroup/scr_UnitGroup.gml#L660-L677

    So -1 is never a valid TTRPG index. The error text out of range [11] is the array length, -1 is the bad index.

    2.2 Crash site - scr_company_order()

    https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_company_order/scr_company_order.gml#L57-L68

    https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_company_order/scr_company_order.gml#L141-L147

    function scr_company_order(company) {
        try {
            var co = company;
            // ...
            var _company_marines = collect_company(company);
            // ... squad fulfilment, organise_by_template ...
            _company_marines.order_by_rank();
            TTRPG[co] = _company_marines.units; // L143 - crash when co == -1
            for (var i = 0; i < array_length(TTRPG[co]); i++) {
                TTRPG[co][i].marine_number = i;
            }
        } catch (_exception) {
            ERROR_HANDLER.handle_exception(_exception);
        }
    }

    Note collect_company(-1) itself does not crash (its loop for com 0..companies simply matches no company because _wanted_companies != com for all com), but the final writeback TTRPG[-1] does.

    2.3 Caller - promotion popup bind_method

    https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_promote/scr_promote.gml#L34-L70

    promote_button.bind_method = function() {
        units_to_move = new UnitGroup([]);
        // ...
        for (var i = 0; i < array_length(obj_controller.display_unit); i++) {
            if ((obj_controller.man[i] == "man") && (obj_controller.man_sel[i] == 1) && (obj_controller.ma_exp[i] >= min_exp)) {
                var _unit = obj_controller.display_unit[i];
                units_to_move.push(_unit);
            }
        }
        units_to_move.move_to_company(target_comp);
        // ... update_role + alter_equipment ...
        with (obj_controller) {
            scr_management(1);
        }
        with (obj_ini) {
            scr_company_order(obj_popup.manag);       // L68 - manag == -1 in fleet view
            scr_company_order(obj_popup.target_comp); // L69 - valid 0..10
        }
        // ... reset_manage_arrays + update_general_manage_view ...
    };

    Stacktrace in the issue matches exactly:

    2.4 Where does manag == -1 come from?

    Popup draw copies controller state, clamping only > 10:

    https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_promote/scr_promote.gml#L144-L150

    function draw_popup_promotion() {
        add_draw_return_values();
        manag = obj_controller.managing; // -1 in fleet view
        if (manag > 10) {
            manag = 0;
        }

    Popup default is manag = 0 (see https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/objects/obj_popup/Create_0.gml#L44 ), but draw overwrites it every frame.

    Fleet Manage Units explicitly enters adhoc mode with managing = -1:

    https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/objects/obj_fleet_select/Create_0.gml#L77-L83

    if (point_and_click(draw_unit_buttons([center_draw - math_string, yy + height - 50], "Manage Units", [1, 1], c_blue))) {
        var _fleet_array = fleet_full_ship_array(current_fleet);
        var _fleet_marines = collect_role_group("all", ["", 0, _fleet_array]);
        group_selection(_fleet_marines, {purpose: "Ship Management", purpose_code: "manage", number: 0, system: 0, ships: _fleet_array, feature: "none", planet: 0, selections: []});
    }

    https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_UnitGroup/scr_UnitGroup.gml#L969-L991

    function group_selection(group, selection_data = {}) {
        // ...
        with (obj_controller) {
            // ...
            scr_ui_refresh();
            managing = -1; // L990

    Squad-selection path does the same:

    https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_UnitGroup/scr_UnitGroup.gml#L1024-L1030

    And scr_ui_manage explicitly branches on it:

    https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_ui_manage/scr_ui_manage.gml#L88-L90

    if (managing == -1 && obj_controller.selection_data.purpose != "Ship Management") {
        update_adhoc_manage();
    }

    update_general_manage_view() also branches:

    https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_unit_quick_find_pane/scr_unit_quick_find_pane.gml#L617-L635

    function update_general_manage_view() {
        with (obj_controller) {
            if (managing > 0) {
                // ... scr_company_view ...
                alarm[6] = 30;
            } else if (managing == -1) {
                update_adhoc_manage();
            }
        }
    }

    So -1 means "adhoc / ship-manage selection, not a company". Passing it to a company-indexed function is a contract violation.

    Even if the -1 guard were added, the current reorder is still wrong for adhoc: fleet units can come from multiple source companies, but the code only reorders manag and target_comp. move_to_company() itself handles heterogeneous sources correctly per-unit:

    https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_marine_struct/scr_marine_struct.gml#L411-L424

    https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_UnitGroup/scr_UnitGroup.gml#L93-L121

    but marine_number normalisation / rank ordering for every touched source company is skipped. Contrast the transfer path, which correctly reorders all companies:

    https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_transfer_marines/scr_transfer_marines.gml#L75-L80

    with (obj_ini) {
        for (var co = 0; co <= obj_ini.companies; co++) {
            scr_company_order(co);
            scr_vehicle_order(co);
        }
    }

    and guards view refresh with managing > 0:

    https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_transfer_marines/scr_transfer_marines.gml#L84-L103

    The promote path should follow the same pattern.

    2.5 Why is Promote only enabled after save/load? Stale sel_promoting

    Button enablement:

    https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_ui_manage/scr_ui_manage.gml#L1283-L1294

    button.label = localize("Promote");
    var promote_possible = sel_promoting > 0 && !_non_control_loc && man_size > 0;

    sel_promoting initial value:

    https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/objects/obj_controller/Create_0.gml#L407

    sel_promoting = 0;

    Only writer is Alarm_6, gated on managing > 0:

    https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/objects/obj_controller/Alarm_6.gml#L1-L12

    if ((menu == eMENU.MANAGE) && (managing > 0)) {
        // ...
        sel_promoting = 1;

    Every refresh schedules alarm[6] = 7:

    https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_ui_refresh/scr_ui_refresh.gml#L1-L12

    https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_unit_quick_find_pane/scr_unit_quick_find_pane.gml#L630-L631

    but when managing == -1 the alarm body does nothing, leaving the old value intact.

    group_selection() -> scr_ui_refresh() clears display arrays but does not reset sel_promoting:

    https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_company_view/scr_company_view.gml#L14-L42 (reset_manage_arrays clears ma_promote but not sel_promoting)

    https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_UnitGroup/scr_UnitGroup.gml#L989-L991

    Meanwhile controller serialization copies all non-excluded instance vars:

    https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/objects/obj_controller/Create_0.gml#L1070-L1117

    sel_promoting, managing, man_sel, ma_promote, etc. are not in excluded_from_save, so copy_serializable_fields() persists them. Load restores them verbatim:

    https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_load/scr_load.gml#L56-L84

    Sequence explaining the report:

    1. Fresh game: sel_promoting == 0. Open fleet view directly -> managing == -1, alarm does nothing, button stays disabled. No crash possible.
    2. Visit a company view, select promotable units -> alarm[6] sets sel_promoting == 1. (Or save while in that state.)
    3. Save persists sel_promoting == 1. Load restores it.
    4. Open fleet view -> managing == -1, ma_promote[] is repopulated by other_manage_data() (see https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_company_view/scr_company_view.gml#L227-L366 ), man_size > 0 after selection, and stale sel_promoting == 1 makes promote_possible == true.
    5. setup_promotion_popup() (https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_promote/scr_promote.gml#L1-L11 ) succeeds, then confirm hits (A).

    other_manage_data() does compute ma_promote[] for adhoc units too (called from group_selection at https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_UnitGroup/scr_UnitGroup.gml#L1020 ), so per-row data is valid; only the sel_promoting flag logic is missing for managing == -1.

    2.6 Save/load squad failures - role_hierarchy() with empty _roles

    role_hierarchy():

    https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_company_order/scr_company_order.gml#L153-L197

    function role_hierarchy() {
        var _roles = active_roles();
        var hierarchy = [
            _roles[eROLE.CHAPTERMASTER], // eROLE.CHAPTERMASTER == 1
            // ...
            _roles[eROLE.DREADNOUGHT],   // L187, eROLE.DREADNOUGHT == 6

    Enum:

    https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/enums/enums.gml#L25-L61

    enum eROLE {
        NONE = 0,
        CHAPTERMASTER = 1,
        // ...
        DREADNOUGHT = 6,

    active_roles():

    https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/is_specialist/is_specialist.gml#L19-L26

    function active_roles() {
        var _obj = active_game_ini().player_role_data;
        var _roles = [];
        for (var i = 0; i < array_length(_obj); i++) {
            array_push(_roles, struct_exists(_obj[i], "role") ? _obj[i].role : "");
        }
        return _roles;
    }

    active_game_ini() returns obj_ini when obj_creation does not exist (normal loaded game):

    https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_controller_helpers/scr_controller_helpers.gml#L9-L11

    If player_role_data == [], result is [] (length 0). Then _roles[6] throws Variable Index [6] out of range [0], exactly the logged error.

    Why is it [] during load? obj_ini starts with:

    https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/objects/obj_ini/Create_0.gml#L19-L20

    player_role_data = [];

    deserialize() then handles squads before player_role_data:

    var _squad_structs = save_data[$ "squad_structs"];
    if (is_struct(_squad_structs)) {
        squads = {};
        var _squad_uids = struct_get_names(_squad_structs);
        for (var i = 0; i < array_length(_squad_uids); i++) {
            var _squad = new UnitSquad();
            try {
                _squad.load(_data); // L324
            } catch (e) {
                LOGGER.exception("Failed to load squad " + _squad_uid, e); // L326
            }
        }
    }
    if (struct_exists(save_data, "player_role_data")) {
        var _defaults = setup_default_gears();
        // ... merge saved fields ...
        player_role_data = _defaults;
    }

    UnitSquad.load() -> determine_leader():

    https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_squads/scr_squads.gml#L474-L487

    static load = function(data) {
        move_data_to_current_scope(data);
        obj_ini.squads[$ uid] = self;
        for (var s = 0; s < array_length(members); s++) {
            members[s] = fetch_unit_uid(members[s]);
        }
        for (var s = array_length(members) - 1; s >= 0; s--) {
            if (!is_struct(members[s])) {
                array_delete(members, s, 1);
            }
        }
        determine_leader(); // L486
    };

    https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_squads/scr_squads.gml#L573-L609

    static determine_leader = function() {
        var hierarchy = role_hierarchy(); // L575 - throws when _roles == []

    The deserialize() exclusion list also confirms player_role_data is deferred (https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/objects/obj_ini/Create_0.gml#L219-L241 ), but the explicit restore was placed after the squad block. Fresh games do not hit this because scr_initialize_custom() sets player_role_data before game_start_squads() creates squads.

    Impact: the catch in deserialize logs but keeps a half-loaded squad (squads[$uid] already inserted, squad_leader still undefined, determine_leader() never completed). Repeated for every squad, matching "a lot of these errors ... for different squads and roles" in the issue.

    3. Proposed Fixes

    Order by safety / minimality. All are ASCII-only, no new .yy files needed.

    Fix 1 (required) - Guard scr_company_order() against non-company indices

    File: scripts/scr_company_order/scr_company_order.gml

    At the top of scr_company_order(company), early-exit when company < 0 || company > obj_ini.companies (or > 10). Optionally log at debug level. This turns the hard crash into a no-op and protects every other caller (switch_view_company, sort_all_companies, future code) from the same contract violation.

    function scr_company_order(company) {
        if (company < 0 || company > obj_ini.companies) {
            return;
        }
        try {

    Rationale: matches existing scr_company_view() validation philosophy (see https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_company_view/scr_company_view.gml#L140-L144 which errors on bad company), but non-throwing is correct here because adhoc callers may legitimately not have a company. Keep the existing try/catch below it.

    Fix 2 (required) - Promotion confirm must reorder actually-affected companies, not manag

    File: scripts/scr_promote/scr_promote.gml, promote_button.bind_method (https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_promote/scr_promote.gml#L34-L70 )

    Current:

    with (obj_ini) {
        scr_company_order(obj_popup.manag);
        scr_company_order(obj_popup.target_comp);
    }

    Problems: manag can be -1 (crash), and even when valid it misses other source companies when the selection spans companies (fleet view always can).

    Recommended replacement (mirrors transfer logic at https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_transfer_marines/scr_transfer_marines.gml#L75-L80 ):

    var _source_companies = {};
    for (var i = 0; i < units_to_move.number(); i++) {
        // capture BEFORE move_to_company mutates unit.company
        // or snapshot before the move_to_company(target_comp) call above
    }

    Concretely, snapshot before units_to_move.move_to_company(target_comp):

    var _affected = {};
    for (var i = 0; i < units_to_move.number(); i++) {
        _affected[$ string(units_to_move.units[i].company)] = true;
    }
    _affected[$ string(target_comp)] = true;
    
    units_to_move.move_to_company(target_comp);
    // ... update_role / alter_equipment ...
    
    with (obj_ini) {
        var _keys = struct_get_names(_affected);
        for (var i = 0; i < array_length(_keys); i++) {
            scr_company_order(real(_keys[i]));
        }
    }

    Simplest acceptable variant if per-company tracking is unwanted: loop 0..obj_ini.companies like transfer_marines() does. Slightly heavier but consistent and obviously correct.

    Also fix the post-promote view refresh in the same function. Current:

    with (obj_controller) {
        scr_management(1);
    }
    // ...
    with (obj_controller) {
        reset_manage_arrays();
        alll = 0;
        update_general_manage_view();
    }

    For managing == -1, update_general_manage_view() delegates to update_adhoc_manage() -> group_selection() which is correct (re-collects by ship/system filter). Verify selection_data is still valid after the move (units changed company but not location, so the ship filter still matches). If update_general_manage_view() proves fragile here, call update_adhoc_manage() explicitly when managing == -1, mirroring jail_selection():

    https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_unit_quick_find_pane/scr_unit_quick_find_pane.gml#L693-L699

    Do not call scr_company_order(manag) when manag < 0.

    Fix 3 (required, choose A or B) - Decide whether Promote should exist in fleet/adhoc view

    Option 3A - Disable (restores pre-save/load behavior, smallest change):

    This is the conservative fix the issue reporter likely expects ("Promote button is only available ... if you save and load first" implies it should not be available there).

    Option 3B - Support (more work, better UX):

    Recommendation: ship 3A now (with Fixes 1+2 as safety net), track 3B as a feature. Fix 2 is still needed even under 3A because stale clients / edge paths could still invoke the popup.

    Also fix draw_popup_promotion() clamping to handle negative:

    manag = obj_controller.managing;
    if (manag < 0 || manag > 10) {
        manag = 0;
    }

    Currently only > 10 is clamped (https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_promote/scr_promote.gml#L147-L149 ). The popup title uses manag for the roman-numeral label; -1 falls through to empty string. Clamping to 0 (HQ) for display while not using it for reorder keeps display sane.

    Fix 4 (required) - Load player_role_data before squad_structs in obj_ini.deserialize()

    File: objects/obj_ini/Create_0.gml, function deserialize.

    Move the block at https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/objects/obj_ini/Create_0.gml#L354-L370 (if (struct_exists(save_data, "player_role_data")) ...) to before the marine/squad blocks at https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/objects/obj_ini/Create_0.gml#L290-L329 , i.e. immediately after the livery/squad_types restores and before load_marine_struct / squad load().

    Why this order: TTRPG_stats.load_json_data() and UnitSquad.load() -> determine_leader() -> role_hierarchy() -> active_roles() all depend on player_role_data. squad_types should also be restored before squads (it already is at https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/objects/obj_ini/Create_0.gml#L286-L288 - keep that ordering).

    After the move, the Variable Index [6] out of range [0] errors should disappear on the issue's repro (new game -> save -> load). Verify by loading the reporter's save and checking last_messages has no Failed to load squad entries and obj_ini.squads leaders are structs.

    Do not "fix" this by adding a fallback if (array_length(_roles) == 0) return [] inside role_hierarchy() alone: that would silence the crash but leave every squad leader undefined, preserving the corruption. Ordering is the real fix. A debug assertion on array_length(_roles) > eROLE.DREADNOUGHT is acceptable as documentation, but not a substitute.

    Fix 5 (optional hardening, low priority)

    4. Verification Plan

    1. Crash repro: new game -> save -> load -> select fleet -> Manage Units -> select marines -> confirm stale sel_promoting == 1 makes Promote clickable (before Fix 3A). Apply Fixes 1+2, repeat promotion from fleet view (if 3B) or confirm button is disabled (if 3A). No TTRPG[-1] error.
    2. Company promote regression: promote via normal company manage screen still works; marine_number sequence is contiguous in both source and target companies; tally_marines() counts unchanged except role changes.
    3. Squad load: new game -> save -> load, assert zero Failed to load squad in last_messages / obj_saveload alarm path (https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_load/scr_load.gml#L44-L49 ), and spot-check fetch_squad(id).squad_leader is a struct and determine_leader() returns without exception.
    4. Stale flag: save while sel_promoting == 1 in company view, load, enter fleet view, assert sel_promoting == 0 and Promote alpha is 0.5 / click does nothing.
    5. Static checks: 7-bit ASCII only (per docs/AGENTS.md), GoboCat formatting, no new .yy files.

    5. Files Referenced

    • scripts/scr_company_order/scr_company_order.gml - scr_company_order() L57-L151, role_hierarchy() L153-L197 (DREADNOUGHT at L187)
    • scripts/scr_promote/scr_promote.gml - setup_promotion_popup() L1-L111, bind_method L34-L89 (reorder L67-L70), draw_popup_promotion() L143-L271 (manag L146-L150, draw L269)
    • objects/obj_popup/Draw_64.gml - dispatch L1-L4
    • objects/obj_popup/Create_0.gml - defaults (manag = 0 L44, target_comp = -1 L65), get_unit_promotion_options L119-L259
    • objects/obj_fleet_select/Create_0.gml - Manage Units L77-L83
    • scripts/scr_UnitGroup/scr_UnitGroup.gml - group_selection L969-L1038 (managing = -1 L990/L1029), collect_company L640-L642, UnitGroup.move_to_company L93-L121
    • scripts/scr_marine_struct/scr_marine_struct.gml - move_to_company L411-L424
    • objects/obj_controller/Alarm_6.gml - managing > 0 gate L2, sel_promoting logic L12-L339
    • objects/obj_controller/Create_0.gml - sel_promoting = 0 L407, serialize L1070-L1117
    • scripts/scr_ui_manage/scr_ui_manage.gml - promote_possible L1288-L1294, adhoc branch L88-L90
    • scripts/scr_ui_refresh/scr_ui_refresh.gml - alarm[6] = 7 L11
    • scripts/scr_company_view/scr_company_view.gml - validation L140-L144, other_manage_data L227-L366, find_company_open_slot L44-L58
    • scripts/scr_unit_quick_find_pane/scr_unit_quick_find_pane.gml - update_general_manage_view L617-L635, update_adhoc_manage L587-L615, jail_selection pattern L693-L699
    • scripts/scr_transfer_marines/scr_transfer_marines.gml - correct all-company reorder L75-L80, managing > 0 guards L84-L103
    • objects/obj_ini/Create_0.gml - TTRPG init L110, player_role_data = [] L19, squad load L314-L329, role data restore L354-L370, exclusions L219-L241
    • scripts/scr_squads/scr_squads.gml - UnitSquad.load L474-L487, determine_leader L573-L609 (hierarchy L575)
    • scripts/is_specialist/is_specialist.gml - active_roles L19-L26
    • scripts/scr_controller_helpers/scr_controller_helpers.gml - active_game_ini L9-L11
    • scripts/enums/enums.gml - eROLE L25-L61 (DREADNOUGHT = 6)
    • scripts/scr_load/scr_load.gml - INI+controller load L44-L114
    • scripts/scr_popup_functions/scr_popup_functions.gml - calculate_equipment_needs L276-L379
  3. self-assigned this
    on Oct 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions