.agents/skills/galaxy-code-organization/SKILL.md
File structure, include order, and modular layout for Galaxy script projects. Use when splitting a monolithic script into multiple files, establishing the MapScript.galaxy bootstrap chain, organizing UI into sub-files, or setting up Enums/GlobalVariables/Header/MapInit. Covers which files are auto-generated by the editor and must never be edited (MapScript.galaxy, LibHASH.galaxy, LibHASH_h.galaxy). Do not use for language syntax questions (use galaxy-language-fundamentals).
npx skillsauth add KimPlaybit/Starcraft-2-Editor-Skills galaxy-code-organizationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Galaxy scripts in a SC2 map can be split across many .galaxy files using include. This keeps code maintainable and modular. The SC2-IngameDevTools mod is the #1 primary reference for file structure and module layout.
| Resource | URL | |---|---| | SC2-IngameDevTools Script/ folder (PRIMARY — #1 example) | https://github.com/abrahamYG/SC2-IngameDevTools/tree/main/DevToolsIngame.SC2Mod/Script | | SSF scripts (secondary style reference) | https://github.com/Cristall/SC2-SwarmSpecialForces/tree/main/SwarmSpecialForces.SC2Map/scripts | | Alcyone Frontlines scripts | https://github.com/KimPlaybit/Alcyone_Frontlines/tree/master/ProximaFrontlines.SC2Mod/scripts | | Native function reference | https://mapster.talv.space/galaxy/reference | | GalaxyScript guide | https://s2editor-guides.readthedocs.io/New_Tutorials/03_Trigger_Editor/058_GalaxyScript/ | | SC2 editor guides index | https://s2editor-guides.readthedocs.io | | SC2Mapster wiki | https://sc2mapster.wiki.gg/ |
The SC2-IngameDevTools project organises each feature as a self-contained handler file in Script/. There is one main coordinator, a shared utilities folder, and a _h.galaxy header for forward declarations.
Script/
├── DevToolsMain.galaxy ← coordinator: includes all handlers, calls all _Init()
├── debug.galaxy ← global debug helpers: print(), console(), err()
├── debug_h.galaxy ← forward declarations only (header pattern)
├── split_string.galaxy ← utility (string split)
├── ItemList.galaxy ← shared data structure (aggregator)
├── ItemListListBoxFormat.galaxy ← formatting helpers for ItemList
├── AbilityHandler.galaxy ← feature handler (one per module)
├── AbilityOrderHandler.galaxy
├── ActorMessageHandler.galaxy
├── BehaviorHandler.galaxy
├── CameraHandler.galaxy
├── CameraShakeHandler.galaxy
├── CatalogLinkHandler.galaxy
├── CatalogValueHandler.galaxy
├── CheatHandler.galaxy
├── DataEditorHandler.galaxy
├── DataTableHandler.galaxy
├── DoodadHandler.galaxy
├── EffectHandler.galaxy
├── FogHandler.galaxy
├── LightingHandler.galaxy
├── PlayerHandler.galaxy
├── PortraitHandler.galaxy
├── RaceHandler.galaxy
├── SkinHandler.galaxy
├── SoundtrackHandler.galaxy
├── UnitHandler.galaxy
├── UpgradeHandler.galaxy
├── UserDataHandler.galaxy
├── WeaponHandler.galaxy
├── FreeCamHandler.galaxy
├── ItemList/
│ ├── index.galaxy ← the actual ItemList implementation
│ └── Listbox.galaxy ← listbox sub-feature
└── DevTools/
├── helpers.galaxy ← shared helper functions (spawn point, movement tracker)
├── helpers_h.galaxy ← forward declarations for helpers
├── ChatCommand.galaxy ← chat command subsystem
└── ChatCommand/
└── Commands.galaxy ← registered chat commands
DevToolsMain.galaxy)include "Script/debug"
include "Script/DevTools/helpers"
include "Script/ActorMessageHandler"
include "Script/BehaviorHandler"
include "Script/EffectHandler"
include "Script/UnitHandler"
include "Script/UpgradeHandler"
include "Script/WeaponHandler"
include "Script/AbilityHandler"
include "Script/CatalogValueHandler"
include "Script/CatalogLinkHandler"
include "Script/LightingHandler"
include "Script/DataTableHandler"
include "Script/DataEditorHandler"
include "Script/CameraHandler"
include "Script/FogHandler"
include "Script/DevTools/ChatCommand"
include "Script/DevTools/ChatCommand/Commands"
void DevToolsMain_Init() {
DevTools_ChatCommand_Init();
helpersInit();
ActorMessageHandler_Init();
CatalogValueHandler_Init();
UnitHandler_Init();
BehaviorHandler_Init();
EffectHandler_Init();
LightingHandler_Init();
// ... every handler's _Init() called here
DevTools_ChatCommand_Commands_Init();
}
Lib7C0075CB.galaxy — editor-generated lib file)include "TriggerLibs/natives"
include "Lib7C0075CB_h"
include "TriggerLibs/NativeLib"
include "Script/DevToolsMain"
void TestMap_main() {
DevToolsMain_Init();
}
void lib7C0075CB_InitCustomScript() {
TestMap_main();
}
bool lib7C0075CB_InitLib_completed = false;
void lib7C0075CB_InitLib() {
if (lib7C0075CB_InitLib_completed) { return; }
lib7C0075CB_InitLib_completed = true;
lib7C0075CB_InitCustomScript();
}
debug_h.galaxy)// ONLY forward declarations — no implementations
void print(string s);
void printT(text t);
void console(string s);
void err(string s);
BehaviorHandler.galaxy)// 1. Include shared utilities
include "Script/debug_h"
include "Script/ItemList"
// 2. Path constants with UPPER_SNAKE_CASE
static const string CONTAINERDLG_PATH =
"UIContainer/ConsoleUIContainer/CatalogManager/BehaviorManager";
// 3. Module struct + global instance
ItemListContainerStruct BehaviorContainer;
// 4. File-private state
static string ItemList;
static ListBoxFilterStruct ListBoxFilter;
// 5. Trigger event functions (bool a, bool b signature)
bool BehaviorListBoxFilterQuery(bool a, bool b) { ... }
bool BehaviorListBoxSelectionChanged(bool a, bool b) { ... }
bool BehaviorContainerSendHandler(bool a, bool b) { ... }
// 6. One public Init function
void BehaviorHandler_Init() {
playergroup pg = PlayerGroupAll();
ItemList = "BehaviorList";
ItemListContainer_InitStandard(BehaviorContainer, CONTAINERDLG_PATH,
"BehaviorContainerSendHandler"); // trigger registered by STRING name
ItemListInitFromCatalog(ItemList, c_gameCatalogBehavior, ItemListCatalogFilter);
ItemList_FilterListInitStandard(ListBoxFilter, "BehaviorListBox",
BehaviorListBoxSetActive, ItemListItemTextValue,
CONTAINERDLG_PATH+"/NavList");
ItemList_FilterListRebuild(ItemList, ListBoxFilter, "", pg);
}
scripts/main.galaxy) — secondary referenceinclude "scripts/Lib/Starcode" // third-party lib first
include "scripts/Enums" // pure constants
include "scripts/GlobalVariables" // structs, global vars, typedefs
include "scripts/secrets"
include "scripts/Header" // forward declarations
include "scripts/Utilities" // shared helpers
include "scripts/AchievementsTemplates"
include "scripts/Achievements"
include "scripts/UI/UI-Achievements" // UI panels first (alphabetical)
include "scripts/UI/UI-CustomDefeatFrame"
include "scripts/UI/UI-HeroPanel"
include "scripts/UI/UI-Objectives"
include "scripts/UI/UI-Options"
include "scripts/UI/UI-PlayerBoard"
include "scripts/UI/UI-Popup"
include "scripts/UI/UI-Speedruns"
include "scripts/UI/UI-Stats"
include "scripts/UI/UI-Votekick"
include "scripts/UI/UI-HivePanel"
include "scripts/UI/UI-Revive"
include "scripts/UI/UI-Main" // UI coordinator last
include "scripts/Bank"
include "scripts/Enemy"
include "scripts/PartTerran" // content parts
include "scripts/PartProtoss"
include "scripts/PartZerg"
include "scripts/Parts" // part coordinator last
include "scripts/Hive"
include "scripts/HeroAbilities"
include "scripts/Player"
include "scripts/Collectibles"
include "scripts/HeroSelection"
include "scripts/Tutorial"
include "scripts/Debug"
include "scripts/MapInit" // map init always last
void main(){
TriggerAddEventMapInit(TriggerCreate("MapInit_Main"));
}
scripts/main.galaxy)include "scripts/GlobalVariables"
include "scripts/MapInit"
include "scripts/Bank"
include "scripts/Player"
include "scripts/Spawner"
include "scripts/Jungle"
include "scripts/UI"
include "scripts/Buildings"
include "scripts/AI"
include "scripts/HeroSelection"
include "scripts/HeroLevelUp"
The SC2 editor auto-generates certain .galaxy files. Do not write code in these files — the editor will overwrite any manual changes.
| File | Why it is auto-generated |
|---|---|
| MapScript.galaxy | The editor rebuilds this on every save. It wires trigger libraries and calls InitMap(). |
| LibHASH.galaxy (e.g. Lib5A1C9904.galaxy) | The library wrapper file for an .SC2Mod. The editor generates it from the mod's trigger data. It wraps calls into scripts/main.galaxy via include. |
| LibHASH_h.galaxy | The auto-generated header for the library wrapper. |
MapScript.galaxy — bootstraps the engine:
include "TriggerLibs/NativeLib"
include "LibHASH" // pulls in the mod library if present
void InitLibs() { libNtve_InitVariables(); libHASH_InitLib(); }
include "scripts/main" // custom scripts (if standalone map)
void InitCustomScript() { main(); }
void InitMap() { InitLibs(); InitCustomScript(); }
LibHASH.galaxy — a thin editor-generated wrapper that:
libNtve_InitVariables() as its library initincludes scripts/main oncelibHASH_gf_* wrappersLoadTriggers trigger that calls ProximaInitTriggers() (or equivalent)libHASH_InitLib() / libHASH_InitTriggers()All actual logic lives in scripts/ — that is where you write code.
MapScript.galaxy is the map's entry point — it is generated by the editor and should contain minimal code:
include "TriggerLibs/NativeLib"
include "TriggerLibs/LibertyLib"
void InitLibs() { libNtve_InitLib(); libLbty_InitLib(); }
include "scripts/main" // <-- pulls in all custom scripts
void InitCustomScript() { main(); }
void InitMap() { InitLibs(); InitCustomScript(); }
scripts/main.galaxy is the coordinator — it lists every include in dependency order and defines main():
include "scripts/Lib/Starcode"
include "scripts/Enums"
include "scripts/GlobalVariables"
include "scripts/Header"
include "scripts/Utilities"
// ... all other files ...
include "scripts/MapInit"
void main() {
TriggerAddEventMapInit(TriggerCreate("MapInit_Main"));
}
main() only registers a single map-init trigger. All actual initialization happens inside that trigger function in MapInit.galaxy.
include "scripts/Utilities" // relative to map root, no .galaxy extension
include "scripts/UI/UI-Main" // subdirectories are supported
include "scripts/Lib/Starcode" // third-party / library code in Lib/
Critical rules:
.galaxy extension — the engine appends it.| File | Role |
|---|---|
| MapScript.galaxy | Auto-generated by editor. NEVER edit. Entry point that wires libraries and calls InitMap(). |
| scripts/main.galaxy | Coordinator: all includes + void main(). |
| scripts/Enums.galaxy | Pure const int constants, grouped by category with comment headers. |
| scripts/GlobalVariables.galaxy | All global const, static, struct definitions, and typedef funcref. |
| scripts/Header.galaxy | Forward declarations for every cross-file function. |
| scripts/Utilities.galaxy | Shared helper functions used by many other files. |
| scripts/MapInit.galaxy | Map initialization logic (alliances, player groups, startup sequence). |
| scripts/Enemy.galaxy | Enemy spawning and behavior. |
| scripts/Bank.galaxy | Bank read/write logic. |
| scripts/Player.galaxy | Player stats, XP, upgrades. |
| scripts/HeroSelection.galaxy | Hero selection UI and unlock logic. |
| scripts/HeroAbilities.galaxy | Hero ability implementations. |
| scripts/Parts.galaxy | Coordinator for multi-part content (calls into Part* files). |
| scripts/PartTerran.galaxy | Content specific to the Terran part. |
| scripts/PartProtoss.galaxy | Content specific to the Protoss part. |
| scripts/PartZerg.galaxy | Content specific to the Zerg part. |
| scripts/Debug.galaxy | Debug helpers, override functions. |
| scripts/UI/UI-Main.galaxy | Initializes all UI subsystems. |
| scripts/UI/UI-*.galaxy | One file per UI panel/system. |
| scripts/Lib/Starcode.galaxy | Third-party library. |
All game-wide integer constants go in one file:
// parts
const int c_Part_Terran = 0;
const int c_Part_Protoss = 1;
const int c_Part_Zerg = 2;
// game modes
const int c_GameMode_Normal = 0;
const int c_GameMode_Endless = 1;
// bitflags
const int c_BossFightState_Alive = 1 << 0;
const int c_BossFightState_Enraged = 1 << 1;
const int c_BossFightState_Dead = 1 << 2;
// boss IDs
const int c_BossFightID_Flamer = 0;
const int c_BossFightID_Crusher = 1;
Naming convention: c_CategoryName_IdentifierName
Grouping: Use // category name comment headers to separate groups.
Bitflags: Use 1 << n pattern.
Include Enums.galaxy before GlobalVariables.galaxy so struct fields can reference these constants for array sizes.
Declare all structs, global variables, and typedef funcref here:
// global consts
const int gv_MaxAmountPlayers = 6;
const int gv_MaxAmountParts = 3;
// funcref blueprints
bool blueprint_BossAbility(unitgroup ug, unit boss);
typedef funcref<blueprint_BossAbility> Blueprint_BossAbility;
// structs
struct PlayerStruct {
bool activeFlag;
bank bankfile;
unit heroUnit;
int points;
int[gv_MaxAmountParts] wins; // array sized by const
int heroUnlocked; // stored as bitflag
};
// global arrays
PlayerStruct[gv_MaxAmountPlayers + 1] gv_PlayerStats;
Naming convention: gv_SystemName_VariableName for globals, gv_VariableName for simple globals.
Static locals that are cross-trigger: Declare as static at file scope in the file that owns them.
Galaxy requires functions to be declared before they are called. When file A calls a function defined in file B (included later), use a forward declaration in Header.galaxy:
// Header.galaxy — comment groups match their source file
// UI-PlayerBoard.galaxy
void PlayerBoard_UpdatePlayer(int playerID);
// Bank.galaxy
void Bank_Save_ForcedAll(int playerID);
void Bank_Save_RequestSave(int playerID);
// Parts.galaxy
void Part_PartFinished();
void Part_InitVariables();
// Player.galaxy
void Player_AddExp(int playerID, fixed amount);
Include Header.galaxy after GlobalVariables.galaxy so declarations can reference struct types.
Each UI panel gets its own file. A coordinator file (UI-Main.galaxy) initializes all of them:
// scripts/UI/UI-Main.galaxy
void SSFCustomUI_Init() {
gv_UI_MasterFrame = DialogControlHookupStandard(c_triggerControlTypePanel, "UIContainer/...");
StatsInterface_Init();
Votekick_Init();
Options_Init();
HeroPanel_Init();
PlayerBoard_Init();
// ...
}
In main.galaxy, include individual UI files before UI-Main.galaxy:
include "scripts/UI/UI-HeroPanel"
include "scripts/UI/UI-PlayerBoard"
include "scripts/UI/UI-Options"
// ... all panels first ...
include "scripts/UI/UI-Main" // coordinator last, so it can call the others
When content is split by campaign mission / chapter / faction, use a coordinator + per-part files:
// In main.galaxy — include individual parts BEFORE coordinator
include "scripts/PartTerran"
include "scripts/PartProtoss"
include "scripts/PartZerg"
include "scripts/Parts" // coordinator included last
Each Part*.galaxy owns its content and exposes a TriggerCreate() function:
// PartTerran.galaxy
void PartTerran_AreaJunker_Second_Open() { ... }
void PartTerran_TriggerCreate() {
TriggerAddEventXxx(TriggerCreate("PartTerran_SomeHandler"), ...);
}
Parts.galaxy coordinates between parts (handles part transitions, shared state):
// Parts.galaxy
static const int observer_AmountWaypoints = 11;
static point[observer_AmountWaypoints] observer_Waypoints;
void Part_InitVariables() { ... }
void Part_PartFinished() {
PartTerran_TriggerCreate(); // calls into part files
}
Put external/library code in scripts/Lib/:
include "scripts/Lib/Starcode" // third-party utility library
This keeps library code clearly separated from your own code.
All functions follow a SystemName_ActionName pattern:
MapInit_ActivePlayers() — belongs to MapInit systemBank_Save_RequestSave(int playerID) — belongs to Bank system, Save subsystemPlayerBoard_UpdatePlayer(int playerID) — belongs to PlayerBoard UIUtility_IsNumber(string input) — belongs to UtilitiesPartTerran_AreaJunker_Second_Open() — Part + Area + ActionThis makes it immediately obvious which file a function lives in.
// 1. Third-party libraries (no dependencies)
include "scripts/Lib/Starcode"
// 2. Pure constants (no dependencies)
include "scripts/Enums"
// 3. Global state (depends on Enums for array sizes)
include "scripts/GlobalVariables"
// 4. Secret/config (if any)
include "scripts/secrets"
// 5. Forward declarations (depends on GlobalVariables for types)
include "scripts/Header"
// 6. Shared utilities (may depend on GlobalVariables)
include "scripts/Utilities"
// 7. Templates/data-only files
include "scripts/AchievementsTemplates"
// 8. Feature files alphabetical (depend on headers + globals)
include "scripts/Achievements"
include "scripts/Bank"
include "scripts/Enemy"
// 9. UI files — panels first, coordinator last
include "scripts/UI/UI-HeroPanel"
include "scripts/UI/UI-PlayerBoard"
include "scripts/UI/UI-Main"
// 10. Part files — individual parts first, coordinator last
include "scripts/PartTerran"
include "scripts/PartProtoss"
include "scripts/PartZerg"
include "scripts/Parts"
// 11. High-level systems that depend on parts
include "scripts/Hive"
include "scripts/HeroAbilities"
include "scripts/Player"
include "scripts/Collectibles"
include "scripts/HeroSelection"
include "scripts/Tutorial"
// 12. Debug and init last
include "scripts/Debug"
include "scripts/MapInit"
When a trigger needs to pass data to its handler function (since trigger functions take no parameters), use static file-scope variables as a parameter register:
// In Utilities.galaxy
static int Utility_DelayedTextTagDestroyer_ParamTextTag;
static trigger Utility_DelayedTextTagDestroyer_Trigger;
void Utility_DelayedTextTagCreate(text inText, color inColor, point position, playergroup pg, fixed offset) {
Utility_DelayedTextTagDestroyer_ParamTextTag = TextTagCreate(...);
TriggerExecute(Utility_DelayedTextTagDestroyer_Trigger, false, false);
}
bool Utility_DelayedTextTagDestroyer(bool testCond, bool runActions) {
int textTag = Utility_DelayedTextTagDestroyer_ParamTextTag; // capture immediately
Wait(3.5, c_timeGame);
TextTagDestroy(textTag);
return true;
}
Capture the static into a local variable at the top of the handler before any Wait() calls.
If the SC2 editor or test map throws:
Scri: Script load failed: Function not found
and the function clearly exists in your .galaxy files, the most common cause is file encoding.
Fix: Save the offending .galaxy file as UTF-8 without BOM (not "UTF-8 with BOM").
In VS Code:
UTF-8 with BOM).The SC2 engine cannot parse files saved with a BOM (EF BB BF) prefix, which causes it to fail to locate any functions defined in that file, even if the code itself is correct.
testing
SC2 Data Editor — Wizards for automating complex or repetitive data creation/modification in XML. Use when creating .BlizWiz XML files to define templates for generating catalog entries (units, abilities, effects, actors, etc.) with user inputs, conditions, validations, and macros. Covers wizard elements (input, entry, condition, validate, macro), string evaluation (tokens, catalog references, arithmetic), and file placement. Always reference the Data Wizard Documentation for syntax. Do not use for direct data editing (use other sc2data-* skills) or Galaxy scripting.
data-ai
SC2 Data Editor — Units, Abilities, Movers, Turrets, Requirements, and Races in XML. Use when creating or modifying units (CUnit/CUnitHero), abilities (CAbilEffectTarget, CAbilEffectInstant, CAbilResearch, etc.), movement (CMover), turrets (CTurret), or tech requirements in GameData XML files. Always consult the catalogsData.xsd schema for exact fields and structure — do not assume unsupported fields exist. Do not use for Actors/visuals (use sc2data-actors-visuals), damage/effects (use sc2data-effects-weapons), or Galaxy scripting (use the galaxy-* skills).
data-ai
SC2 Data Editor — Effects, Weapons, Upgrades, and the damage chain in XML. Use when creating or modifying CEffect* (damage, search, apply behavior, launch missile, set), CWeapon, CUpgrade, or the full chain from weapon through to damage application. Also covers TargetFind, TargetSort, and Footprints. Always consult the catalogsData.xsd schema for exact fields and structure — do not assume unsupported fields exist. Do not use for actors/visuals (sc2data-actors-visuals) or unit/ability containers (sc2data-units-abilities).
testing
SC2 Data Editor — Behaviors (buffs, debuffs, auras, timers) and Validators (conditional tests) in XML. Use when creating or modifying CBehavior* (buff, attribute modifier, unit tracker, reveal) or CValidator* (unit type, unit order, comparison, combine) entries. Also covers behavior stacking, duration, Vitals modification, and how validators gate effects, abilities, and behaviors. Always consult the catalogsData.xsd schema for exact fields and structure — do not assume unsupported fields exist. Do not use for applying a behavior via an effect (use sc2data-effects-weapons) or actor visuals tied to a behavior (use sc2data-actors-visuals).