How Lighthouse Resolves Mod Loading Priority Conflicts: Alphabetical Order Wins
Lighthouse resolves mod conflicts by loading files alphabetically, with later entries overwriting earlier ones—meaning Z_Mod.o2r always beats A_Mod.o2r when both define the same asset.
Lighthouse, the modding framework behind the HarbourMasters game ports, uses a deterministic conflict resolution system that requires no user configuration. Understanding this priority mechanism helps mod creators predict override behavior and lets players control which assets display without editing configuration files. The entire system relies on simple filesystem ordering rather than manifest files or priority flags.
How the Mod Loading Pipeline Works
When Lighthouse boots, it executes a four-phase pipeline in the resource loader (implemented in src/port/Resource/GfxBridge.c and surrounding boot code):
Phase 1: Recursive Folder Scan
The engine walks the mods directory recursively, collecting every .o2r and .otr file. Subdirectories are fully traversed, meaning mods/subfolder/MyMod.o2r participates in the same priority queue as root-level files.
Phase 2: Lexicographical Sort
All discovered paths are sorted alphabetically using standard string comparison (strcmp). This sort is case-sensitive on most platforms, so Z_mod.o2r precedes a_mod.o2r. The full path string determines order, so directory nesting affects priority—mods/Alpha/Mod.o2r sorts before mods/Beta/Mod.o2r.
Phase 3: Sequential Bundle Loading
Each mod file loads in the sorted order, unpacking its assets into memory structures.
Phase 4: Overwrite-on-Conflict Merge
Assets feed into global lookup tables. When an incoming asset's internal OTR/O2R identifier matches an existing entry, the new asset replaces the old unconditionally.
The last mod alphabetically holding a given asset ID always wins the conflict.
Concrete Code Example
The loading logic in src/port/Resource/GfxBridge.c follows this pattern (simplified for clarity):
/* Representative logic from the boot sequence */
char **mod_paths = scan_mod_folder("mods"); // collect all .o2r/.otr
qsort(mod_paths, count, sizeof(char*), strcmp); // alphabetical ordering
for (int i = 0; i < count; ++i) {
AssetBundle *bundle = load_o2r_or_otr(mod_paths[i]);
merge_into_global_tables(bundle); // later entries win
}
Consider this file structure:
mods/
├── EarlyPack.o2r // defines texture 0x04001200 = "Original logo"
├── Subfolder/
│ └── LatePack.o2r // defines texture 0x04001200 = "Custom logo"
└── ZZZ_Override.o2r // defines texture 0x04001200 = "Final logo"
Loading order becomes:
EarlyPack.o2rSubfolder/LatePack.o2rZZZ_Override.o2r← wins
The player sees "Final logo" regardless of when mods were installed.
Language Pack Special Case
Language packs reside in mods/~lang/ and follow identical rules. The ~ prefix ensures this directory sorts early, isolating language assets from gameplay mods. Within ~lang/, alphabetical order still governs which pack active for each language code.
As documented in docs/modding/LANGUAGE PACKS.md, the language selector UI filters to one pack per language, but the underlying loading mechanism uses the same overwrite behavior—if multiple es-ES packs exist, the alphabetically last one populates the Spanish text tables.
Key Source Files for Mod Loading Priority
| File | Relevance |
|---|---|
src/port/Resource/GfxBridge.c |
OTR texture loading; asset table insertion with overwrite semantics |
include/overlays.h |
Overlay table declarations receiving mod assets |
README.md (lines 84–86) |
Documents the mods/ folder auto-loading behavior |
docs/modding/LANGUAGE PACKS.md |
mods/~lang/ subdirectory rules |
Practical Control Strategies
Mod creators and players can exploit alphabetical ordering without code changes:
- Prefix with numbers:
01_Base.o2r,99_Final.o2r - Use directory depth:
mods/0-Early/vs.mods/9-Late/ - Timestamp in filename:
mods/Pack_2024-01-15.o2r
Because Lighthouse provides no explicit priority metadata, filename engineering becomes the sole conflict resolution tool.
Summary
- Lighthouse scans
mods/recursively and sorts all.o2r/.otrfiles alphabetically by full path - Assets load sequentially; later files overwrite earlier ones on identifier collision
- No configuration files or manifest declarations control priority—only filesystem naming
- Language packs in
mods/~lang/follow identical rules with UI filtering per language - Mod creators control override order through strategic filename and directory naming
Frequently Asked Questions
Can I set explicit loading priority numbers instead of using alphabetical order?
No. Lighthouse has no priority metadata system—alphabetical sorting is hardcoded in the resource loader. The qsort with strcmp in src/port/Resource/GfxBridge.c is the sole ordering mechanism. You must rename files or reorganize directories to achieve desired load order.
Does subdirectory nesting affect loading priority?
Yes. The full path string participates in alphabetical comparison, so mods/A/Mod.o2r loads before mods/B/Mod.o2r. Deep nesting can be used strategically to group mods by priority tier (e.g., mods/0-Foundation/, mods/5-Gameplay/, mods/9-Polish/).
What happens if two mods modify the same texture but I want both effects combined?
Lighthouse does not merge asset contents—complete replacement occurs. To combine modifications, you must manually merge the assets into a single file using external tools, then name that file to load last. The engine has no partial-override or patch-layering capability.
Are there platform differences in alphabetical sorting?
Case sensitivity depends on the host platform's filesystem and C library strcmp implementation. On case-sensitive systems (Linux, macOS APFS), Z precedes a. For cross-platform consistency, use consistent casing and numeric prefixes rather than relying on letter case for ordering.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →