You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Root cause: setup_complex_livery_shader() iterates over every key in the livery struct and passes the raw value straight to set_complex_shader_area() -> get_shader_colour_from_arrays(), which indexes col_r[colour] / col_g[colour] / col_b[colour] with no validation. Any undefined, -1 sentinel, bool metadata flag, non-shader key (is_changed, block_company_colours, company_marks_loc), out-of-range int, or icon entry with missing .colour triggers arr[undefined] / arr[-1] / arr[out_of_range] inside the try block. The try/catch then calls ERROR_HANDLER.assert_popup(_exception), which is what generated this exact issue (callstack captured via debug_get_callstack, not an uncaught crash).
if (_in_creation) {
_data_set = variable_clone(obj_creation.livery_picker.map_colour);
if (obj_creation.livery_selection_options.current_selection == 2) {
var _base = obj_creation.full_liveries[0];
// fill missing / -1 entries from base
}
}
Personal-livery overlay (empty {} for DummyMarine unless Deathwatch, but real marines can inject anything):
if (_is_unit) {
var _names = struct_get_names(unit.personal_livery);
for (var i = 0; i < array_length(_names); i++) {
var _area = _names[i];
_data_set[$ _area] = unit.personal_livery[$ _area];
}
}
for (var i = 0; i < _position_count; i++) {
var _colour_position = spot_names[i];
var _colour = variable_clone(_data_set[$ _colour_position]);
if (!is_array(_colour)) {
set_complex_shader_area(_colour_position, _colour);
ERROR_HANDLER.assert_popup captures debug_get_callstack() and files the report, which is why the issue stack starts inside get_shader_colour_from_arrays:
3. Root cause - unvalidated colour reaches col_r[colour]
col_r[colour] throws I32 argument is undefined exactly when colour itself is undefined. The code has at least four reachable ways to produce that (plus the -1/out-of-range near-miss that the same guard must fix):
3.1 Primary: no -1 / metadata / unknown-key filtering in main loop
Any other path that leaves a -1 in _data_set (stale map_colour after the Step init quirk above, company-livery clone, save/JSON with -1, hover-restore race) calls col_r[-1]. In GML an out-of-bounds read yields undefined, then undefined / 255 throws inside the same try, producing the same popup.
false (bool metadata) coerces to 0 today so it silently sets a non-existent uniform to white. It works by accident and still issues shader_get_uniform(..., "is_changed") == -1 calls every frame. Any future change that stores undefined instead of false crashes outright.
Reader does not verify _data is a struct or that .colour exists before dereferencing. An old save, hand-edited JSON, or truncated ["icon"] array makes _data.colour == undefined, and set_complex_shader_area(pos, undefined) -> col_r[undefined] -> exact reported error.
if (colour_return != false) {
if (hover_pos != colour_return[0]) {
map_colour[$ colour_return[0]] = colour_return[1];
If hover_pos ever names a key absent from map_colour (new image_location_maps entry, renamed area, company_marks_loc confusion), map_colour[$ hover_pos] reads undefined, the struct gains an undefined value, variable_clone preserves it, spot_names includes it, and the next ComplexSet.draw in the same frame crashes. This matches "hovering mouse over armor customization" perfectly: every hover forces reset_image() -> immediate draw_unit_image() -> setup_complex_livery_shader() on the mutated struct.
blindly copies every personal_livery value over the base. A modded/legacy personal_livery entry with undefined overwrites a valid colour. DummyMarine uses {} (safe), real marines use e.g. irandom(30) (safe today):
Why the game shows a popup instead of hard-crashing
The try/catch in get_shader_colour_from_arrays catches the indexing error and forwards the exception object to assert_popup, which builds the GitHub issue from debug_get_callstack(). So the report is the catch handler firing. The returned colour stays black [0,0,0], and shader_set_uniform_f_array(-1_or_valid, black) continues. Repeated hovers = repeated popups.
4. Proposed fixes (in priority order)
All line refs below are current main.
Fix 1 (required): validate in get_shader_colour_from_arrays + set_complex_shader_area
Never index col_r/col_g/col_b with undefined, -1, non-numeric, or out-of-range. Fail loud in debug, fall back to a safe colour at runtime.
function get_shader_colour_from_arrays(colour) {
var _colours_instance = active_game_controller();
var _fallback = [0, 0, 0];
if (!is_numeric(colour)) {
return _fallback;
}
var _idx = floor(colour);
if (_idx < 0 || _idx >= array_length(_colours_instance.col_r)) {
return _fallback;
}
try {
return [
_colours_instance.col_r[_idx] / 255,
_colours_instance.col_g[_idx] / 255,
_colours_instance.col_b[_idx] / 255,
];
} catch (_exception) {
ERROR_HANDLER.assert_popup(_exception);
}
return _fallback;
}
And in set_complex_shader_area, early-exit when the uniform does not exist (shader_get_uniform returns -1) so is_changed / block_company_colours / company_marks_loc stop issuing invalid uniform writes every frame:
function set_complex_shader_area(area, colour) {
if (is_array(area)) {
for (var i = 0; i < array_length(area); i++) {
var _handle = shader_get_uniform(full_livery_shader, area[i]);
if (_handle == -1) {
continue;
}
shader_set_uniform_f_array(_handle, get_shader_colour_from_arrays(colour));
}
} else {
var _handle = shader_get_uniform(full_livery_shader, area);
if (_handle == -1) {
return;
}
shader_set_uniform_f_array(_handle, get_shader_colour_from_arrays(colour));
}
}
Fix 2 (required): iterate only real shader areas, skip sentinels
In setup_complex_livery_shader, replace the blind struct_get_names(_data_set) loop with an allowlist derived from complex_colour_swaps (already static in the same function):
Also validate _colour[0] access itself: check array_length(_colour) > 0 before _colour[0] == "texture" / "icon" so a truncated [] entry cannot throw a different indexing error.
Fix 4 (recommended): stop mutating live map_colour on hover
Hover preview writes map_colour[$ hover_pos] = 0 then relies on a later-frame restore. Any missed restore (rapid mouse move, popup open, point_and_click creating colour_pick mid-hover) leaves 0 or undefined in the struct that the very next setup_complex_livery_shader clones.
Safer: keep map_colour immutable during hover and pass a preview override into the draw (e.g. clone once, apply highlight in the clone), or at minimum guard the save/restore:
if (!struct_exists(map_colour, hover_pos)) {
hover_pos = false;
} else {
// existing save/set logic, plus is_undefined(colour_return[1]) check
}
array_create(11, variable_clone(map_colour)) fills all slots with the same struct reference. Current code happens to break sharing via later variable_clone on write, but one direct mutation (e.g. _comp_livs[i].foo = x without clone) would corrupt all 11 companies. Either loop-clone or add a comment explaining why every write path must clone. Same for array_create(eROLE.MARINEEND, ...).
5. How to verify
Open Chapter Creation -> Livery slide (covers obj_creation_Draw_0 #L131 path).
Hover every armour hitbox (image_location_maps keys at ColourItem #L225-L380, including R Trim / L Trim / Lenses / Weapon Primary/Secondary / Company Marks buttons rewritten at #L716-L740). Before fix: I32 argument is undefined popup via assert_popup; after Fix 1+2: no popup, preview highlights normally.
Set a company livery slot to -1 (fresh scr_unit_draw_data(-1) state) and switch livery_selection_options between 0/1/2. Before fix: popup on next ComplexSet.draw; after: skipped cleanly.
Craft a livery with ["icon"] (no payload) and ["icon", {}] (no .colour) in full_liveries[0]. Before fix: same popup; after Fix 3: skipped, base colour kept.
High that the mechanism (unvalidated colour -> col_r[undefined]) is correct: it is the only expression in the reported frame that takes an I32 (array index), and the try/catch -> assert_popup explains the exact issue format. Medium on which of the four producers fired for this specific user (no save attached, Chapter Name: None, creation screen), with hover-mutated / -1-sentinel / stray-metadata keys as most likely given "hovering mouse over armor customization". Fixes 1-3 cover all of them regardless of which one fired.
Activity
lyra-cactus commentedon Oct 8, 2026
Investigation Report - Issue #1631:
get_shader_colour_from_arrays- "I32 argument is undefined"1. Summary
Crash report from build
main/2026-10-08-1819(commit0f17fbd34), reporterM60Wa:Error:
Stack (build line numbers, each is current
main+ 1):Root cause:
setup_complex_livery_shader()iterates over every key in the livery struct and passes the raw value straight toset_complex_shader_area()->get_shader_colour_from_arrays(), which indexescol_r[colour] / col_g[colour] / col_b[colour]with no validation. Anyundefined,-1sentinel,boolmetadata flag, non-shader key (is_changed,block_company_colours,company_marks_loc), out-of-range int, or icon entry with missing.colourtriggersarr[undefined]/arr[-1]/arr[out_of_range]inside thetryblock. Thetry/catchthen callsERROR_HANDLER.assert_popup(_exception), which is what generated this exact issue (callstack captured viadebug_get_callstack, not an uncaught crash).Files:
get_shader_colour_from_arrays)set_complex_shader_area)setup_complex_livery_shaderdata-set selection)2. Full call-chain walkthrough
2.1
obj_creation_Draw_0->scr_livery_setuphttps://github.com/Adeptus-Dominus/ChapterMaster/blob/main/objects/obj_creation/Draw_0.gml#L129-L132
https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_livery_setup/scr_livery_setup.gml#L24
2.2
ColourItem.draw_base->DataSlate.draw->inside_method->dummy_marine.draw_unit_image()https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/ColourItem/ColourItem.gml#L650-L779
draw_basesetsdata_slate.inside_methodthen calls:https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/ColourItem/ColourItem.gml#L778
https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_DataSlate/scr_DataSlate.gml#L128-L130
Inside
inside_method, the preview marine is (re)drawn:https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/ColourItem/ColourItem.gml#L742-L748
DummyMarineis defined here:https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_culture_visuals/scr_culture_visuals.gml#L2180-L2196
2.3
scr_draw_unit_image->ComplexSet.drawhttps://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_draw_unit_image/scr_draw_unit_image.gml#L404-L408
https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_ComplexSet/scr_ComplexSet.gml#L1535-L1542
draw_unithere is theDummyMarineinstance, sounit != "none"is true.2.4
setup_complex_livery_shader- crash siteCreation-screen branch:
https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_complex_colour_kit/scr_complex_colour_kit.gml#L51-L68
Personal-livery overlay (empty
{}forDummyMarineunless Deathwatch, but real marines can inject anything):https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_complex_colour_kit/scr_complex_colour_kit.gml#L96-L102
Main loop - no filtering, no
-1guard:https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_complex_colour_kit/scr_complex_colour_kit.gml#L292-L298
Icon branch - no
.colourexistence check:https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_complex_colour_kit/scr_complex_colour_kit.gml#L313-L342
2.5
set_complex_shader_area->get_shader_colour_from_arrayshttps://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_complex_colour_kit/scr_complex_colour_kit.gml#L369-L381
https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_complex_colour_kit/scr_complex_colour_kit.gml#L349-L367
active_game_controller()is:https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_controller_helpers/scr_controller_helpers.gml#L3-L5
col_r/col_g/col_bare built here (40 entries, indices0..39):https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_colors_initialize/scr_colors_initialize.gml#L44-L294
Enum:
https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_colors_initialize/scr_colors_initialize.gml#L1-L42
obj_creationarrays are initialised here:https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/objects/obj_creation/Create_0.gml#L568-L573
col = []; col_r = []; col_g = []; col_b = []; scr_colors_initialize();ERROR_HANDLER.assert_popupcapturesdebug_get_callstack()and files the report, which is why the issue stack starts insideget_shader_colour_from_arrays:https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/ErrorHandler/ErrorHandler.gml#L169-L178
3. Root cause - unvalidated
colourreachescol_r[colour]col_r[colour]throwsI32 argument is undefinedexactly whencolouritself isundefined. The code has at least four reachable ways to produce that (plus the-1/out-of-range near-miss that the same guard must fix):3.1 Primary: no
-1/ metadata / unknown-key filtering in main loopmap_colour(akascr_unit_draw_data) contains non-colour entries:https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/ColourItem/ColourItem.gml#L186-L222
map_colour = { is_changed: false, block_company_colours: false, left_leg_lower: default_val, // ... all areas ... company_marks: default_val, company_marks_loc: default_val, };The shader only defines these uniforms (no
is_changed,block_company_colours,company_marks_loc, nomain_coloursetter either):https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/shaders/full_livery_shader/full_livery_shader.fsh#L1-L31
The cloth-variation collector explicitly skips
-1and arrays:https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_complex_colour_kit/scr_complex_colour_kit.gml#L115-L126
...but the main shader loop does not. It also does not skip
is_changed/block_company_colours/company_marks_loc. Consequences:company_liveriesare deliberately initialised with-1= "no override":https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/objects/obj_creation/Step_0.gml#L275-L278
The
-1 -> baserepair only runs whencurrent_selection == 2:https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_complex_colour_kit/scr_complex_colour_kit.gml#L56-L68
Any other path that leaves a
-1in_data_set(stalemap_colourafter the Step init quirk above, company-livery clone, save/JSON with-1, hover-restore race) callscol_r[-1]. In GML an out-of-bounds read yieldsundefined, thenundefined / 255throws inside the sametry, producing the same popup.false(bool metadata) coerces to0today so it silently sets a non-existent uniform to white. It works by accident and still issuesshader_get_uniform(..., "is_changed") == -1calls every frame. Any future change that storesundefinedinstead offalsecrashes outright.3.2 Icon entries with missing
.colourValid icon payload (only example in repo):
https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/datafiles/main/chapters/7.JSON#L233
Writer:
https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/ColourPicker/ColourPicker.gml#L203-L211
Reader does not verify
_datais a struct or that.colourexists before dereferencing. An old save, hand-edited JSON, or truncated["icon"]array makes_data.colour == undefined, andset_complex_shader_area(pos, undefined)->col_r[undefined]-> exact reported error.3.3 Hover preview mutates the live
map_colourhttps://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/ColourItem/ColourItem.gml#L651-L671
colour_return = [hover_pos, map_colour[$ hover_pos]]; map_colour[$ hover_pos] = 0;and restore:
https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/ColourItem/ColourItem.gml#L770-L776
If
hover_posever names a key absent frommap_colour(newimage_location_mapsentry, renamed area,company_marks_locconfusion),map_colour[$ hover_pos]readsundefined, the struct gains anundefinedvalue,variable_clonepreserves it,spot_namesincludes it, and the nextComplexSet.drawin the same frame crashes. This matches "hovering mouse over armor customization" perfectly: every hover forcesreset_image()-> immediatedraw_unit_image()->setup_complex_livery_shader()on the mutated struct.Hover areas vs livery keys drift independently:
3.4
personal_liveryoverlay can injectundefinedhttps://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_complex_colour_kit/scr_complex_colour_kit.gml#L96-L102
blindly copies every
personal_liveryvalue over the base. A modded/legacypersonal_liveryentry withundefinedoverwrites a valid colour.DummyMarineuses{}(safe), real marines use e.g.irandom(30)(safe today):https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_culture_visuals/scr_culture_visuals.gml#L2187-L2190
but there is no contract enforcement.
Why the game shows a popup instead of hard-crashing
The
try/catchinget_shader_colour_from_arrayscatches the indexing error and forwards the exception object toassert_popup, which builds the GitHub issue fromdebug_get_callstack(). So the report is the catch handler firing. The returned colour stays black[0,0,0], andshader_set_uniform_f_array(-1_or_valid, black)continues. Repeated hovers = repeated popups.4. Proposed fixes (in priority order)
All line refs below are current
main.Fix 1 (required): validate in
get_shader_colour_from_arrays+set_complex_shader_areaNever index
col_r/col_g/col_bwithundefined,-1, non-numeric, or out-of-range. Fail loud in debug, fall back to a safe colour at runtime.Location: https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_complex_colour_kit/scr_complex_colour_kit.gml#L349-L381
Suggested shape (idiomatic GML, ASCII only):
And in
set_complex_shader_area, early-exit when the uniform does not exist (shader_get_uniformreturns-1) sois_changed/block_company_colours/company_marks_locstop issuing invalid uniform writes every frame:Fix 2 (required): iterate only real shader areas, skip sentinels
In
setup_complex_livery_shader, replace the blindstruct_get_names(_data_set)loop with an allowlist derived fromcomplex_colour_swaps(alreadystaticin the same function):https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_complex_colour_kit/scr_complex_colour_kit.gml#L142-L288
Apply the same
-1/undefinedguard already used by the cloth collector at #L120 to the main loop. Optionally share oneis_valid_livery_colour()helper.Fix 3 (required): guard the
iconbranchhttps://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_complex_colour_kit/scr_complex_colour_kit.gml#L313-L342
Also validate
_colour[0]access itself: checkarray_length(_colour) > 0before_colour[0] == "texture" / "icon"so a truncated[]entry cannot throw a different indexing error.Fix 4 (recommended): stop mutating live
map_colouron hoverhttps://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/ColourItem/ColourItem.gml#L650-L776
Hover preview writes
map_colour[$ hover_pos] = 0then relies on a later-frame restore. Any missed restore (rapid mouse move, popup open,point_and_clickcreatingcolour_pickmid-hover) leaves0orundefinedin the struct that the very nextsetup_complex_livery_shaderclones.Safer: keep
map_colourimmutable during hover and pass a preview override into the draw (e.g. clone once, apply highlight in the clone), or at minimum guard the save/restore:Fix 5 (cleanup): document
array_create(n, struct)aliasinghttps://github.com/Adeptus-Dominus/ChapterMaster/blob/main/objects/obj_creation/Step_0.gml#L275-L278 and https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/ColourItem/ColourItem.gml#L516
array_create(11, variable_clone(map_colour))fills all slots with the same struct reference. Current code happens to break sharing via latervariable_cloneon write, but one direct mutation (e.g._comp_livs[i].foo = xwithout clone) would corrupt all 11 companies. Either loop-clone or add a comment explaining why every write path must clone. Same forarray_create(eROLE.MARINEEND, ...).5. How to verify
obj_creation_Draw_0#L131 path).image_location_mapskeys atColourItem#L225-L380, includingR Trim/L Trim/Lenses/Weapon Primary/Secondary/Company Marksbuttons rewritten at #L716-L740). Before fix:I32 argument is undefinedpopup viaassert_popup; after Fix 1+2: no popup, preview highlights normally.-1(freshscr_unit_draw_data(-1)state) and switchlivery_selection_optionsbetween0/1/2. Before fix: popup on nextComplexSet.draw; after: skipped cleanly.["icon"](no payload) and["icon", {}](no.colour) infull_liveries[0]. Before fix: same popup; after Fix 3: skipped, base colour kept.complex_colour_swapsareas;complex_helmspaths at https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_ComplexSet/scr_ComplexSet.gml#L2176-L2227 still tinteye_lense/ head / muzzle.6. Secondary observations (not the crash, worth a ticket)
full_livery_shader.fshdeclaresuniform vec3 main_colourbutsetup_complex_livery_shadernever sets it. Either dead uniform or missing feature. See https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/shaders/full_livery_shader/full_livery_shader.fsh#L11is_changed/block_company_coloursare iterated as colours every frame today; Fix 2 removes the wastedshader_get_uniform(-1)calls.scr_creationre-init path at https://github.com/Adeptus-Dominus/ChapterMaster/blob/main/scripts/scr_creation/scr_creation.gml#L332-L342 duplicatesStep_0init logic; consider one sharedensure_liveries_exist()helper.7. Confidence
High that the mechanism (unvalidated
colour->col_r[undefined]) is correct: it is the only expression in the reported frame that takes anI32(array index), and thetry/catch -> assert_popupexplains the exact issue format. Medium on which of the four producers fired for this specific user (no save attached,Chapter Name: None, creation screen), with hover-mutated /-1-sentinel / stray-metadata keys as most likely given "hovering mouse over armor customization". Fixes 1-3 cover all of them regardless of which one fired.