Tutorial: How to easily "self-check" script mods for compatibility/performance issues.
Hello everyone! I wrote up a post on how to self-check script mods for basic known compatibility/performance issues. Not a full tutorial, but some of you may find this useful.
Disclaimer: I did run this through ChatGPT to reformat and clean up my wording in the hopes of making it easier to follow and read. That being said, here we go!
You don’t need to be “good at Lua” to spot the biggest compatibility problems. You just need to see how a mod hooks base game functions.
What you’re checking for
Open the mod’s .lua files and search (Ctrl+F) for:
Utils.appendedFunction
Utils.prependedFunction
Utils.overwrittenFunction
Important FS25 fact:
✅ Utils.appendedFunction and Utils.prependedFunction wrappers return nothing. Period.
They drop the base function’s returns every time, even if your hook returns values.
6-Line Spot-Check (quick and dirty)
Find Utils.overwrittenFunction → If the hook doesn’t call + return superFunc(self, ...), compatibility is broken (the chain ends there).
Find pasted base function bodies inside an overwrite → If they also call superFunc, that’s the double-run disaster (bugs + major perf hit).
Find static params in overwrites (e.g., function(self, superFunc, a, b)) → risk of truncating params later; prefer ....
Find static returns in overwrites (e.g., return ok) → risk of dropping returns later; prefer returning superFunc(self, ...) (or forwarding all results).
Find Utils.appendedFunction / Utils.prependedFunction → remember: they return nothing, always; avoid using them where returns matter (common for get / can / is*).
In hot paths (update/draw/onUpdate/onDraw) → watch for allocations and heavy work: {} table creation, closure creation, big string building, pairs/ipairs over large tables, table.sort.
Step 1) APPEND hook: Good vs Bad
✅ GOOD (side effects only)
❌ BAD (using append where returns matter)
Simple rule: append/prepend are “do extra stuff,” not “change what the function returns.”
Step 2) PREPEND hook: Good vs Bad
✅ GOOD (side effects only)
❌ BAD (trying to “change parameters” like a real wrapper)
Good mental model: append/prepend are “extra side effects.” Not “wrap and control the call.”
Step 3) OVERWRITE hook: Good vs Compatibility Break
Overwrites are where compatibility is either preserved… or terminated.
✅ GOOD overwrite (preserves original chain + preserves params/returns)
❌ COMPATIBILITY BREAK: overwrite without calling + returning superFunc
This is not “high risk.” It breaks compatibility because the function chain terminates at this mod.
Reality check: If you do this, you’re taking full responsibility for:
re-implementing all base behavior you replaced
supporting other mods that expected the chain to continue
surviving future GIANTS patches to that function
Step 4) Double-run disaster (bugs + performance)
If someone copies the original function body into the overwrite and also calls superFunc, the function effectively runs twice.
This commonly causes:
duplicated side effects
weird “happened twice” bugs
major performance hits (especially in hot functions)
Step 5) Static params/returns: silent breakage (especially in overwrites)
✅ GOOD: forward varargs (...) and forward returns
❌ BAD: fixed params + fixed returns
Rule of thumb:
Static params/returns are less important for append/prepend (they drop returns anyway).
Static params/returns are a big deal for overwrites.
Step 6) Hot-path performance self-check (the real hitters)
If a hook touches update, draw, onUpdate, onDraw, HUD, or other per-frame/per-tick code:
🚩 Red flags inside that hook:
creating tables {} every call (or new arrays/maps repeatedly)
heavy string work (string.format, lots of concatenation .., building big strings)
creating closures repeatedly inside hot functions (inner function() ... end created each tick)
repeated scans over big tables (pairs/ipairs over lots of entries every tick)
repeated sorting every tick (table.sort in hot path)
Simple rule: if it runs constantly, it should do almost nothing and allocate almost nothing.
Quick scoring (fast judgement)
✅ Likely compatible
append/prepend used only for side effects on functions where returns don’t matter
overwrites call + return superFunc(self, ...)
no pasted base code inside overwrites
overwrites use ... forwarding
⚠️ Needs scrutiny
append/prepend used on “getter-ish” functions where returns might matter
overwrites have complex branching where superFunc might not run
hot-path hooks allocate tables / build strings / create closures
❌ Compatibility break
overwrite does not call + return superFunc (chain terminates at this mod)
Note for script authors (please read)
If you intentionally overwrite a base function and don’t call + return the original (superFunc), you are ending the function chain at your mod. That means compatibility is no longer “automatic” — you’re taking ownership of how that function behaves for the base game and for every other mod that hooks it.
Just be honest about it. If you’re replacing behavior on purpose, add a clear note in your mod description like:
Compatibility note (author): This mod overwrites base functions without calling the original (superFunc). That means it may conflict with other mods that hook the same functions. If you use multiple script mods that touch the same systems, you may need to choose which mod “wins.”
Being upfront saves users (and you) a lot of headaches.
Final thoughts
Not trying to be the bad guy here, just trying to help. Believe it or not, I hate all the issues as much as everyone else does and I don't even play lol. That's why I made up this post, to hopefully give you the tools to be able to know what you're getting into when you grab a new script mod (including any of mine).
Later!
