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:

  1. EarlyPack.o2r
  2. Subfolder/LatePack.o2r
  3. ZZZ_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/.otr files 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →