Elemental Fracture 6.2.0 – Patch Notes – “Do you wanna make some MONEY?!”
Hey, everyone! Doobs here. I’m pleased to announce the next round of additions I’ve been working on: Elemental Fracture 6.2.0, read as balance.mod.bump.
6.2.0 does two things that I think are nothing short of miraculous. It brings back a whole game mode that Proletariat cut, Clash, the Chapter 2 team mode that got deleted out of the menu when Chapter 3 shipped. And it brings back progression: Mage Rank, Class Mastery, the post-match XP screen, the rank-up cards, the MASTERY tab, the Gold shop, cosmetic ownership, reward unlocks. All of it. Every one of those systems has been dead since the shutdown, because every one of them lived on a service that no longer answers the phone.
They answer now.
And that’s not the whole release. Blocked Players and Report Player work natively. The dedicated servers keep a record of cheat commands, and that record files its own reports. Dominion’s end screen doesn’t yank the server out from under you anymore. There are public Hi-scores on the website, built as intuitively and as straightforward in mind. There’s a Cosmetics Manager that lets you either grind for your collection or just pick what you want to wear, your call. Marla got a report inbox, a cheat-flag feed, and the ability to hand out cosmetics by name. And under all of it, a stability pass that touched every single mod we ship, including a crash safety net that catches access violations mid-flight and keeps your game alive.
I’m going to tell you how all of it works.
That’s the thing I want to say up front about these notes. They’re long, and they’re long on purpose, and this time I went further than I ever have: there are sections marked “How we did it” scattered through every part, and they get genuinely deep. Calling conventions. Vtable slots. Garbage collector write barriers. The exact reason a stack pointer where a heap handle belongs took me four separate bug hunts to find. A lot of you have asked how any of this is even possible on a game whose studio doesn’t exist anymore, and I think the honest answer is more interesting than “magic, don’t worry about it.”
So skip those sections if you just want to know what’s new. Read them if you want to know what it actually looks like to resurrect a dead online game one function call at a time.
Here’s the map:
- Part 1 – Clash is back. A cut Chapter 2 mode, rebuilt and playable.
- Part 2 – Progression. Mage Rank, Class Mastery, and the post-match screen.
- Part 3 – Your collection. Server-owned cosmetics, the Gold shop, rewards, the Cosmetics Manager.
- Part 4 – Safety. Blocked Players, Report Player, and the cheat record.
- Part 5 – Dominion, the queue, and the party.
- Part 6 – The website and Marla. Hi-scores, staff tools, and the bot.
- Part 7 – Stability. Crash guards, performance, and the plumbing.
Part 1 of 7: Clash Is Back
Let me be completely clear about what this is, because it’s the single biggest thing in the release and I don’t want it buried under a bullet point.
Clash is a real Spellbreak game mode that you cannot play in Spellbreak. It shipped in Chapter 2. It’s team elimination on the Hollow Lands: you drop in through the portals with your squad, the circle closes like it does in battle royale, but you respawn, you fight over a team score, and the team that puts the other team away wins. Then Chapter 3 arrived, Clash didn’t make the cut, and Proletariat pulled it out of the menu. The build every single one of us runs is the Chapter 3 build. If you own Spellbreak, or you played it on the day it went down, you could not queue this mode. It was gone.
It isn’t gone here. You can queue it right now. It’s in the GAME MODE carousel, sitting between Dominion and Practice, with its own card. You press Play, you pick your class, you drop in with your party on your team, you fight, you respawn, and at the end you get a VICTORY or DEFEAT board with the team scores on it.
That’s a mode nobody has legitimately played since 2021.
What’s actually in the box
- Queue Clash solo or as a party. It routes to real Clash servers, and your party lands on one team together.
- Team squads. Your party is a squad, with the squad HUD, the squad colors, and the ally outlines.
- Portal drop-in with position arrows. The squad portal-select screen works, including the little directional arrows that show you where your teammates are aiming.
- Respawns. This is the part that makes Clash Clash. You die, you come back, the fight keeps going.
- The in-match HUD panel: how many of your team is alive, your kills and assists, and the circle-close timer.
- The team score bar at the top, ALLY versus ENEMY.
- A VICTORY / DEFEAT match-end board with both team scores, the real Chapter 2 widget.
- You spawn with your offhand. Both weapon slots, your talents, your items. Sounds obvious. Wasn’t.
- Damage feedback works. Hurt audio, the red flash, the low-health effect on screen.
- Teams are visible to clients, which sounds even more obvious, and was somehow the deepest bug in the whole mode.
How we did it: waking up a cut mode
This one goes deep. Buckle up.
Step one: the mode is still in the build, the menu just can’t see it.
Spellbreak’s mode cards aren’t hardcoded UI. Each one is a data asset, and the menu builds its carousel by enumerating a list of asset IDs. That list, in the Chapter 3 client, contains six entries: the battle royale variants and friends. Clash’s data asset is still in the game files. Its game mode class is still compiled into both the client and the dedicated server. The only thing missing is its name on that list.
So the first move was to inject the card in the card builder. When the native code goes to create the Practice category, our mod builds the Clash card first and hands it over, which is also why Clash lands in the carousel slot right before Practice instead of getting stapled onto the end. That got a card on screen.
Clicking it did nothing, because the click path looks the mode up by category ID and gets back null for a mode the enumerator never registered. That needed its own injection so the click resolves to the real mode info.
One thing worth knowing about the internals: Clash’s internal name is Rumble. The mode string is ?game=Rumble, the map is Rumble_Squad_C, the game mode class is AGRumbleGameMode, the game state is a Blueprint called BP_Rumble_GameState_C. There is no AGRumbleGameMode in the marketing material and there’s no “Clash” in the code. Every time you see Rumble below, that’s Clash.
Step two: the double drop, and four failed fixes.
The first time Clash actually booted, every player dropped in twice. You’d portal in, land, and then get re-dropped from the sky a moment later like the match had started over.
I spent a long time treating this as a timing problem. Spawn a beat later. Defer the restart. Re-possess after the drop. Suppress the second drop. Four different patches, four different angles, all of them either did nothing or broke something worse.
The actual answer was in the class hierarchy, and it’s a beautiful little piece of archaeology. AGRumbleGameMode doesn’t inherit from the battle royale game mode. It inherits from AGVersusGameMode, the arena-style base. That’s fine for Versus scoring, which is exactly why Clash has a team score at all. But it means Clash inherits Versus’s spawn, loadout, and phase machinery too. So the mode ran the battle royale portal drop, because that’s what the map and the closure component do, and then Versus’s inherited restart logic looked at a freshly-landed player, decided that player needed spawning, and spawned them. Twice-dropped, every match, by design, because nobody ever ran this class through this map after the inheritance changed.
The fix wasn’t a timing patch. It was redirecting the divergent game-mode virtuals to the battle royale implementations, so Clash gets Versus scoring and BR spawning, which is what the mode always wanted to be: BR gameplay with Versus scoring. That single change made the mode playable.
Step three: standing up the squad system by hand.
Clash squads didn’t exist. The squad component on the game state was never being built for this mode, so there was no squad to put your party in, no squad HUD, and no portal arrows. Worse, touching it crashed: reading the squad component gave a pointer that faulted at a low offset because the component genuinely wasn’t there.
So we build it. On player spawn, both at post-login and again at the point the game finishes restarting a player, the server mod stands the squad system up and assigns squads itself.
There’s a trap in here that cost real time and is worth writing down because it applies to any Unreal modding: Blueprint class pointers go stale. You can resolve BP_Rumble_GameState_C once, cache the pointer, and have the garbage collector move or reclaim it out from under you. The next spawn call gets a dangling class and fails with “None is not an actor class,” which is a wonderfully unhelpful error for what’s actually a lifetime bug. Anything that resolves a Blueprint class has to re-resolve it or validate it, not trust a cached pointer.
Step four: the team ID bug, which is my favorite bug of the entire release.
Clash players couldn’t see their teams. No ally colors, no ally outlines. And worse: when you got hit, you got no feedback at all, no hurt sound, no red flash, nothing. You’d just start dying silently.
Here’s the thing. The server knew the teams. Team balancing worked. Squads were right. Friendly fire logic on the server behaved correctly. It was only clients that were blind.
Unreal has a generic team interface, and the obvious way to set a player’s team is to call its setter. Our team balancing did exactly that, and it’s not wrong, it’s just… incomplete. That setter writes a single byte that only server-side team logic reads. The value that actually replicates down to clients is a separate property on the player state, at a completely different offset. Two fields, both meaning “team,” one of them local and one of them networked, and we’d been writing the local one.
The way we caught it was by probing the client’s hit-effect gate, the code that decides whether to play damage feedback. It compares the attacker’s team to the victim’s team, and if they match, it suppresses the effect as friendly fire. The probe printed both values. Both players reported team 255. Nobody had a team, so everybody was on the same team, so every hit in the mode was friendly fire, so the game helpfully declined to tell you that you were being murdered.
Mirroring every player-state team write into the replicated property fixed teams, ally colors, ally outlines, and the missing damage feedback in one change. Four visible bugs, one root cause, one line of intent.
Step five: the damage feedback that survived the team fix.
Except the hurt feedback still didn’t work on respawn, which is a problem in the one mode that has respawns.
The character’s BeginPlay binds three delegates: the hurt audio, the red HP flash, and the low-health post-process. It binds them only if the pawn is already an autonomous proxy, engine-speak for “this pawn belongs to the local player.” In battle royale that’s always true, because you possess your pawn before BeginPlay runs. In Clash, a respawned pawn gets spawned first and possessed a moment later. BeginPlay runs, checks whether the pawn is yours, sees that it isn’t yet, and skips all three binds. Forever. Your first life was fine. Every life after that was silent.
The client mod now re-runs those three binds from the health replication callback the instant the pawn’s role flips to autonomous, gated to Clash and safe to run more than once.
Step six: the offhand.
Players were spawning without a secondary gauntlet. The inventory reset on spawn was doing a bare engine inventory wipe and then re-granting the class items, which is most of what you need and not all of it. It now calls the game mode’s own ResetPlayerInventory, which re-grants the default weapon into both the primary and secondary slots, hands out the default inventory items, and runs every game-mode component’s own reset hook. Use the game’s function instead of reimplementing 90 percent of it. Lesson repeatedly learned.
Step seven: the match-end board, and a garbage collector that hates RPCs.
Clash’s Chapter 2 match-end screen is a real widget in the files: UI_MatchEnd_Rumble, with team panels and both scores. But if you let the native match-end flow run in Clash, it runs the battle royale triumph sequence, which loads a match-end level and spawns display pawns, and in Clash that either freezes or crashes.
So we don’t use the native flow. We mount the board ourselves as a clean client overlay: create the widget, add it to the viewport, style it victory or defeat, and show it.
Two hard-won details.
The trigger is a hook on the player state’s “show match end” RPC, but you cannot do the mounting inside the RPC handler. The board’s methods are Haxe, and allocating a Haxe object requires a thread-local stack context that the RPC handler doesn’t have. Every attempt to allocate there access-violated inside the allocator, reading a null context at a small offset. The fix is to set a pending flag in the RPC and do the actual mounting from the HUD’s tick, which does have a valid Haxe context. If you take one thing from this whole document: know which of your threads has a Haxe context and which doesn’t.
And the widget has to be wrapped. Every method on that board expects a Haxe object as this, with the native Unreal object hanging off it. Pass the native pointer directly and it faults every time. Wrap it and it works. That’s the single most repeated lesson in this entire release, and it shows up again in Part 2.
The scores come off the game state, where the server keeps both team scores as adjacent integers, and get written into the board’s own score fields. Victory or defeat is decided by comparing the captured scores against your own team.
What’s still on the list. Clash isn’t finished, it’s playable, and I’d rather ship it than sit on it. The original compact Clash HUD, the little top-right panel from Chapter 2, was deleted in Chapter 3 and needs recreating, so right now you get a repurposed panel. And the live team score number on the top bar is computed by the server but doesn’t always make the trip to the client yet. The mode is fun as hell in the meantime, and I want people in it.
Part 2 of 7: Progression, Returned
Here’s the other headline.
When you finish a match now, you get the game’s real post-match screen. The MATCH STATS tiles fill in. The MAGE RANK bar sweeps up from where you were to where you are. The CLASS MASTERY bar sits next to it in your class’s color. If you crossed a level, the full-screen RANK UP card takes over the screen and waits for you. Back in the menu, the MAGE RANK header shows your level, and the MASTERY tab, which was cut out of the Community Version entirely, is back with every class ranked.
None of that is a mod drawing a fake progress bar. It’s Spellbreak’s own UI, running its own animations, reading numbers that Elemental Fracture computed from matches you actually played.
Why it was gone
Spellbreak never calculated your XP on your machine. Not once. At the end of a match the dedicated server tallied your accolades, the backend added them to your account, and the client was handed the totals purely to display them. That’s the right architecture and it’s why your level was never something you could edit.
It’s also why the shutdown killed it stone dead. With no backend, the client takes a path the original developers built for exactly this situation: it exits the match without post-game stats. No screen. No XP. No levels. The code that draws all those bars has been sitting in the binary this whole time, waiting for a data structure that never arrives.
Getting it back needed three things to be true at once.
- The server had to speak first. At match end, the dedicated server now builds the game’s own post-game stats structure for every human player, through the game’s own code, and sends it down the same RPC the original servers used. The client receives exactly what it always expected.
- The backend had to keep score. Every match result gets fed to our metrics service, which projects two progression tracks out of your history: overall Mage XP and a per-class XP total for whichever class you played. Levels come from the real curves, Mage Rank 1 to 100 and Class Mastery 1 to 20.
- The client had to be told. On connect, and again the moment a match you played has been recorded, we push your standing to the game: Mage level and XP, the per-class map, your pre-match standing so the bars know where to start their animation, and the XP requirements for the levels you’re crossing.
Why it matters that it’s built this way: your level isn’t a file a mod increments. It’s a projection of your match history, rebuilt from an append-only log. If I ever change a formula, every account gets recomputed from the same record, and nobody’s history is lost or invented. That’s the difference between a number you can trust and a number that’s just there.
On the post-match screen
- MATCH STATS tiles: exiles, assists, damage, this match’s XP, and time survived, from the server’s own tally.
- MAGE RANK animates the way it always did, including the level-up sequence where the bar fills to full, flips, and refills.
- CLASS MASTERY sits beside it, themed to the class you played: name, diamond icon, and a fill that lands on the class’s own gradient.
- Rank-up cards. “Pyromancer Rank 7.” “Mage Rank 23.” Full screen, themed with the class art, and it waits for you to press Continue instead of auto-closing. Levelled both tracks? You get the class card, then the Mage card. SKIP ALL on the first one skips the rest.
- Reward cards on those level-ups list what you just unlocked, pulled from the game’s own reward tables, with the real icons.
- The whole sequence runs off the game’s own tick and its own animation-finished events, with fixed delays only as a fallback. That’s why it feels native: the bars finish when the game says they finish, not when a timer guesses.
How we did it: three designs, one screen
This was the hardest single piece of work in the release. It went through three complete designs before one held up.
Design one: force the widgets visible. The post-match widgets exist in the client, so the first attempt hooked the moment the game would normally bail out and just forced the progression widgets to show.
It painted a bar! It also left dangling object references inside widgets that had never been initialized, and Unreal’s garbage collector, which runs on a task-graph thread, would eventually walk into one and take the client down seconds later with a stack trace pointing at nothing useful. And no animations played, because the animation state machine was never set up either. Some of you tested that build. That’s why the early crashes happened after the match instead of during it.
Design two: build the thing the widgets are waiting for. The post-match menu reads one Haxe structure off the game instance. If a valid one is sitting there, the whole native flow runs: full initialization, the reveal, the level-up animations, the rank and class sections, all of it, for free. So instead of forcing widgets, call the game’s own constructor for that structure and store it.
The calling convention ate me alive. That constructor returns its result through a hidden output pointer, and every setter on the result takes a handle to the heap object the game’s allocator made, not the address of the local buffer that received it. I passed the stack address. It looked identical in a debugger. What actually happened is that passing a stack pointer into a garbage-collected object’s setter access-violates inside the GC write barrier, the bookkeeping the collector uses to track pointer writes, and that fault got swallowed. The field stayed garbage. The menu then ran its entire setup on nonsense. That one mistake presented as four different bugs over several days before I found it.
Three more traps in that design, each worth a sentence:
- The trigger. The obvious hook, the “exit without post-game stats” call, never fires on Elemental Fracture, because with no backend the client branches somewhere else entirely. The reliable trigger is the menu’s own show event.
- The accolade loader. With no backend the accolades array is null, and the loader throws a Haxe exception into it. A Haxe throw inside an RPC frame is fatal, full stop. So we skip the loader and reveal the rank section by hand.
- The game mode enum. The menu looks the played mode up in a map, and a null key throws. So the mod reports the mode itself from the selected-mode state and resolves the matching enum value. That’s what put the time tile back.
Design three: let the server build it. Design two worked, but it computed XP on the client, and you should never trust a client with your progression. So the final design moves construction to the dedicated server.
At match end, the server walks every human player, resolves that player’s post-match component, and wraps the native component in its Haxe object wrapper, because the constructor demands the wrapper as this and a raw native pointer is exactly what an earlier attempt was missing when it faulted at a garbage address. Then it creates a real Haxe anonymous object with the award flags set true, so the accolade tally actually pays out, calls the game’s own constructor with the wrapper, the output slot, and the context passed by reference, and ships the result down the ClientSetPostGameStats RPC.
Your client receives the same bytes it would have received in 2021 and runs its own code on them. The XP number in that structure is what our metrics service records as the match’s XP. Server-authoritative, native rendering, no compromise.
How we did it: the animation sequencing
This deserves its own section because it’s genuinely strange.
A progress bar’s fill color is not a property you set. I assumed it was for embarrassingly long. It’s written by the bar’s own UMG material-track animations: a reveal animation paints it, a progress animation sweeps it in white, and a fade animation transitions it to the final color. Setting the color and playing the animation means the animation immediately overwrites you.
So the sequence is: write the class’s three overlay colors, pulled from the class’s default object, onto the fill material before the reveal. Play the reveal. Let the sweep paint. Then apply the class gradient at the end.
The beats advance on the widget’s real animation-finished callback, so the pacing matches the game’s own timing rather than a guess. And the beats themselves are driven from the main menu backplate’s Haxe tick component, which took two failed attempts to arrive at. A Win32 timer callback has no Haxe stack context, so anything it touches faults. The WebSocket heartbeat pump never self-sustained because the pong lands inside its own throttle window. The menu’s own tick has a valid context and fires every frame. Use the game’s heartbeat, not your own.
Small details that mattered: the full-screen level-up card has a native auto-close, which we suppress so the card waits for you, and we re-enable Continue. And there’s a “reward animations finished” byte on the card that only gets set by a callback the persistent card never reaches, so the first Continue press was being silently swallowed. We set that byte ourselves when we suppress the auto-close, so your first press does what you expect.
The MASTERY tab
- The MASTERY entry is back in the header nav and the pane opens.
- The Mage row and every class row rank themselves from your live standing, on first paint and again whenever your standing changes, so there’s no boot-time flash of stale numbers.
- The overview cards show lifetime totals from your match history: damage dealt, damage taken, Clash matches.
- The talent rows are back, and their tiers pay Gold now. More on that in Part 3.
How we did it: the tab was removed from the menu’s navigation, not from the game. We generalized the same injection that brought FRIENDS back in 6.1.0, re-injecting the nav entry and forcing the menu’s availability check true. Rows get re-ranked by hooking the pane’s show event through its vtable slot, so they’re populated before the first paint rather than visibly popping in. One subtle one: the re-rank at standing-adoption time threw a Haxe exception at boot when it ran before a valid world existed, so it’s guarded on the world now.
The main menu and in-match
- The MAGE RANK header shows your real level and progress from the second you log in.
- The in-match player card shows your Mage rank, because the server splices your standing into the blob the game server reads.
- Party placards show every member’s Mage rank.
The header widget is fed by a detour on the game’s own GetPlayerXP, which hands back the authoritative total from our push instead of the zero sitting in the local blob. That way the native level display and the native XP curve agree with the backend, rather than us fighting the UI with a second source of truth.
Where the XP comes from
- Every human player in a completed match earns XP from the server’s accolade tally, and a win pays the Winner accolade, 150 XP, on top.
- The class you played earns that same match XP on its own track. The server captures your chosen class at match end from the replicated property on the player state’s classes component, so Class Mastery credits the right class even if you switch between matches.
- New accounts start at Mage Rank 1 with every class at Rank 1. Progression is server-authoritative from this release forward, and nothing gets imported from local files.
How the backend keeps score: the metrics service stores every match result as an immutable event, and everything you see is a projection rebuilt from those events. Mage XP and class XP are two of those projections. Ratings, hiscores, presence and the cheat record are others. Change a curve, run one rederive, and every account rebuilds from the same log. There’s also an XP adjustments ledger, so when staff grant or remove XP they’re appending an event rather than editing a total, which means even a manual correction survives a rebuild.
Part 3 of 7: Your Collection
Progression was only half of what the dead backend owned. The other half is your collection: which outfits, emotes, trails, badges, cards and titles you have, how much Gold is in your wallet, what’s in the shop. In 6.2.0 all of that moves to the server too.
Your inventory used to be a JSON file in your Saved folder that any text editor could rewrite. Now it’s an account record that the game gets handed at login and every time it changes.
The PlayerBlob is server-authoritative
Saved/PlayerBlob.jsonis retired. On your first boot after the update the game loads its clean template, the server seeds your record once from what the client uploads, and from then on the server owns it.- Equipping a cosmetic, spending Gold, any change at all goes through the appserver, which merges it and pushes the result back. Server-owned fields, your XP, wallet, inventory and block list, are never taken from the client’s word.
- Your standing gets pushed on connect, on every change, and the moment a match you played is recorded, so the Gold counter, the MASTERY rows and the cosmetic grids are always current.
How we did it: the original backend delivered this document through an UpdatePlayerBlob message the client handles natively, so we build that document server-side out of three sources: XP from the progression projection, wallet and inventory from the account record, and whatever the client legitimately owns, like loadouts, from its upload.
The two hooks the game uses to load and save its local file are both detoured. The load renames the stale file aside so the clean template loads instead. The save doesn’t write to disk at all: it serializes the live in-memory blob through the game’s own JSON printer and sends it to the appserver as an upload. Using the game’s serializer instead of writing our own is the only reason the round trip is byte-compatible.
The server then strips every server-owned key out of that upload before merging, so nothing the client claims about its XP, Gold or inventory is ever believed. And pushes get deferred while the post-match sequence is running, because a fresh standing arriving mid-animation was clobbering the flourish’s starting point and stranding it halfway.
What you own, and why
The original game gave you cosmetics three ways: some were yours from the start, some you bought, some you earned by ranking up. We mirror that faithfully, plus one deliberate addition.
- Earned rewards. The game’s own reward tables are live again. Hit a Mage rank or a Class rank and you unlock the cosmetics and collect the Gold that rank always paid. The reward card shows you exactly what you got.
- Talents pay Gold. In the original version of the game, you needed to level up your Class Mastery in order to unlock talents, which I thought was quite backwards. I told myself that if I were to ever do this sort of preservation, I’d make it so that gameplay was not affected. Since that time has come, I adjusted the progression table so that every talent tier you unlock pays 50 Gold, so the MASTERY talent tree is a source of currency instead of a dead list.
- Community grants. There’s a set of cosmetics that was never sold in the store and never appeared in a reward table, which means literally nobody could ever have owned them. Those go to everyone: the Aegis Warden, the Alabaster Champion, LobberBot, the Vowguard Soldier, and a dozen trails. Event titles are deliberately excluded but can be granted on request. A title that says you won Twitch Rivals 2020 means something because you were there, and it stays with the people who were there. The staff set stays staff-only (sorry, Exo, I took away your Proletariat skin.)
- The COSMETICS grids tell the truth now. Unowned items simply no longer appear in your cosmetics tab. The grid used to list every asset in the game as though you owned it, because the branch that filters by ownership had been folded away when the backend died. That branch is flipped back on.
How we rebuilt the catalog, because this was genuine detective work: the full list of 1,006 cosmetics comes from the client’s own template blob. Which of them were ever sold comes from the store offer data, 924 of them. Which were earnable comes from the reward tables, 64. Subtract, and what’s left is the set nobody could obtain. Strip out event, league, tester and creator titles plus the test assets, and you land on 18 items. Those are the community grants.
Titles needed extra care. My first pass treated every title as an event reward, which was wrong: 30 of the 67 titles were store items, including all the class titles and the Vowbreaker ranks. Only the remaining 37 are actual participation rewards. The “event” tag you see on the website reflects that split exactly.
And here’s a great example of why a change that sounds trivial isn’t. Making talents pay Gold meant rewriting the reward tables that live in each class’s default object as lazily-built lists. Writing through the accessor changed a copy, and the MASTERY tree responded by dropping the rows entirely. The fix forces the lazy getters to build the real tables first, then rewrites the entries in raw memory. That took two attempts, because the primary asset ID structure is 24 bytes with a 12-byte name at offset 12, and my first version copied 16 bytes and read the name at offset 8. Result: empty asset IDs, and the game politely refusing to add the reward. Reading the real layout out of the debug symbols fixed it in one shot.
The Gold shop
- The SHOP tab is open, with a catalog rebuilt from the game’s own store offers and a 24-hour featured and daily rotation.
- The header Gold counter is back, re-parented next to the options button where the game always kept it.
- Purchases cost Gold, get refused if you already own the item or can’t afford it, and the refusal shows the game’s own dialog, the real “insufficient funds” and “offer does not exist” modals.
- The store’s purchase flow, the appserver’s handlers, and your wallet are one system. Buy something and it’s in your inventory on every device you sign in from.
What things actually cost now, and why
This is the part I want to spend a minute on, because the numbers matter more than the plumbing.
Everything is earned with coins, and I’ve rescaled the prices way, way down from what they were. Not tweaked. Rescaled. A skin that used to sit behind a 3,000 coin wall costs 120 coins here. That’s roughly three rank-ups. Three. Every price in the shop moved by that same fixed rate, so the whole catalog came down together and nothing is stranded at an old number.
The reason is simple. The original prices were built for a live-service economy with real money in it, where the coin grind existed partly to make the store look appealing. There’s no real money here and there never will be, so a price tag whose whole job was to nudge you toward a wallet has no job left. What’s left is the fun part: play, rank up, buy the thing you wanted.
This is the path for people who want to earn their cosmetics. That’s a real thing a lot of you want, and it should feel good rather than feel like a second job. If you don’t want any part of it, the Cosmetics Manager in Part 3 is right there and I’m not going to make you feel weird about using it. Turn it on, tick what you like, go play. For those players, coins stop being a gate entirely and rank-ups just become number go up, which is honestly a perfectly good reason to rank up.
And the most important rule in the whole economy:
Nothing that touches gameplay is locked behind progression. Not one thing.
Every class is unlocked at level 1. Every talent is unlocked at level 1. You are not grinding for power, you are not grinding for options, and you will never load into a match against someone who has a mechanical advantage because they’ve played more than you. Every node on the mastery tree that used to be a talent unlock now just pays you coins to spend on cosmetics instead. The tree still fills in, it still tracks what you’ve mastered, it just pays out in things you wear rather than things that hit harder.
Progression here is a record of what you’ve played and a way to earn what you look like. That’s all it is, and that’s all I want it to be.
How we did it: the store calls two backend operations, fetch offers and purchase, and the appserver answers both in the exact shapes the client parses. The client’s offer fetch needed two dead-code patches, because the shipped binary short-circuits it when no backend is configured. And the purchase refusal carries an error kind field, which the client’s own purchase closure matches against its localized modals, so a refusal shows the real dialog instead of a generic one. Answering in the game’s vocabulary instead of inventing our own is what makes it feel like the store rather than a store.
The Cosmetics Manager on the website
Some of you want to grind for your collection. Some of you had a collection already and have zero interest in re-earning it. Both of those are completely reasonable, so the website lets you choose.
- Under Profile on your account page there’s a Cosmetics Manager with one checkbox: Follow in-game progression system, on by default. Leave it on and your collection is exactly what you’ve earned, bought, been granted or unlocked.
- Turn it off and you can tick any cosmetic in the game and just have it, except the staff set. Nine panels, one per type, with the game’s own icons, rarity-colored borders, real display names, and a hover card with the item’s description. Cosmetic cards show their full card art on hover.
- Search, sort by name or rarity, and filter to collected or uncollected, because a thousand-item catalog needs it.
- Nothing you earned is ever lost. Items you own through progression, purchases or grants are locked in and stay yours in either mode, and progression keeps accruing underneath while you self-select, so flipping the checkbox back on later costs you nothing.
How the icons got there, and this was a fun rabbit hole: 908 of the 1,006 cosmetics on that page show their real in-game icon. Getting them meant reading each cosmetic’s Blueprint out of the game’s pak files to find its display name, rarity, description and icon texture path. Then the textures themselves, which are stored in cooked UE 4.22 format that no off-the-shelf extractor will decode for this game, so we wrote a small decoder: read the cooked texture header, find the block-compressed mip, hand the BC data straight to an image library. Card art gets exported as full-width strips for the hover preview. Titles all share one generic banner in-game, so they render as the game’s own title backplate with the text on it. A community asset pack covered a handful the paks didn’t.
Why the default is progression-on: the progression system is the game, and I’m not going to bury it. But this game shut down once already, and telling someone who lost a four-year collection that they have to re-grind it is a lousy way to welcome them back. Giving both groups the honest version of what they want, with a clear label on which is which, beat picking for everyone.
Part 4 of 7: Safety
A living game needs two things: a way to walk away from someone, and a way to tell a moderator. Spellbreak had both, in the friends tab and the party menu and the match-end screen. Both were dead. They’re alive now, and the second one has teeth.
Blocked Players
- Block works from the friends tab and from the party placard menu, and the Blocked Players tab lists everyone you’ve blocked, with a working Unblock.
- Blocking is enforced everywhere the game checks: list filtering, the comms gate, invite suppression, and the dedicated server’s own squad assignment. Block someone and you won’t get put on a squad with them. Friend requests and invites between a blocked pair are refused, a blocked player reads as offline to you, and blocking drops the friendship and ejects you both from any shared party.
- Unblock is worth calling out. The tab draws an UNBLOCK entry, but nothing in the shipped game implements it, because the original path lived on a service that’s gone. We drive it directly, so the row clears the moment you click.
How we did it: the client’s entire block system reads one field of the PlayerBlob. Emit that list from the server and list filtering, the comms gate, invite suppression and squad avoidance all light up on their own, because the code was always there. That’s the recurring joy of this project: a huge amount of Spellbreak works perfectly the second something tells it the truth.
The Block and Report buttons, though. Those took a real trick.
Both native handlers compile down to the same empty function, because the linker folded every identical empty body into a single stub, and that stub is shared with completely unrelated callers including a presence setter and a debug dump. Detour it and you fire for all of them. Useless.
So we don’t hook the function. We patch the four call sites. Friends-tab Report, friends-tab Block, the party placard Block, and the match-end report-your-killer. Each of those sites has already resolved its target player by the time it makes the call, so a small code cave at each one tail-jumps into a thunk that reads the player ID out of the register it’s already sitting in and emits the WebSocket frame. Every patch verifies the site really is a call to that folded stub before it writes a byte.
Unblock has no call site at all, so that one hooks the Blocked Players tab’s action selection instead. The logic is airtight: the tab’s only action is Unblock, and a player already on your block list can never be the target of a Block, so a selection there is unambiguous.
Then the squad rule needed its own fix on the server, and here’s the archaeology: the dedicated server has no block check compiled into it whatsoever. The symbols exist on the client and are simply absent from the server’s debug data, because the original team ran that check on the backend. Two players who’d blocked each other and queued separately would happily land on the same squad.
So the client mod splices your block list into the blob it hands the game server at login, and the server mod reads both players’ lists at the exact point the server decides whether a squad can accept a player. That decision already had a detour on it, and two detours on one address chain unpredictably, so the block test shares the single owner of that hook rather than stacking a second one. It’s also tighten-only: it can turn an acceptance into a rejection, never the reverse, and any failure degrades to allow.
Report Player
- Report works from the friends tab and from the match-end screen where the game offers to report whoever exiled you.
- Reports are held while the reported player is still in the match, then released with that match’s telemetry attached: exiles, assists, damage dealt and taken, placement, team, class, result, mode and duration. A report about a pattern also carries the reported player’s last 24 hours of matches.
- Repeat reports fold into the open one and count up instead of getting refused.
- Every report reaches moderators in Discord within seconds of the match ending, with a link to the staff telemetry page instead of a wall of numbers. Moderators act through the ban path, and a ban now evicts the player’s live session and closes their social connection immediately.
How we did it: the client never learns a match ID, because the join token doesn’t carry one, so the server resolves the shared match itself out of both players’ histories once the reported player leaves. A sweeper on the router releases held reports, pulls the match context and the recent window from metrics, and posts to Marla’s keyed receiver. A hung server can’t swallow a report either, because a hold timeout releases it with whatever telemetry exists.
The fold-instead-of-refuse behavior came out of a real usability bug: the game’s WebSocket frames for these messages carry no sequence number, which means the client never sees an error reply. A refused duplicate looked exactly like success, so a player would report twice, see nothing happen either time, and reasonably conclude the button was broken.
The cheat record
This one is new, and it’s deliberately quiet.
- The dedicated server now records every cheat command a client can send it: 76 server-side receivers, plus the 82 cheat-manager entry points as a tripwire. Each one is logged with the command, its arguments, the invoking player and the match time, and shipped with the match result.
- Nothing is judged at capture time. Staff use these commands legitimately for events and testing, and the record says so. But when a non-staff account runs a command no normal client ever sends, a report gets filed automatically, tagged as a cheat flag, and lands in the same moderator channel as a player report with the commands listed.
- Arena modes get their exceptions: health and armor top-ups are completely normal in Dominion and TDM, and they’re never flagged there.
- The staff telemetry page shows any player’s stats over seven windows, from the last hour to all time, with the rates that actually make cheating visible: damage per minute, exiles per match and per minute, win rate, all shown next to played duration so the rates can be sanity-checked. Every match row links to the commands recorded in it.
How we did it, including the design that failed: the first version hooked the server’s cheat manager and recorded absolutely nothing. Took a while to understand why, and it’s obvious in hindsight. A remote player’s cheat manager runs on the player’s own client, and each cheat gets relayed to the server as a Cheat_Server* RPC on the player controller or one of its components. The server’s own cheat manager never runs for a remote player at all.
So we hook those receivers. Every script-visible function in Unreal carries a pointer to its native implementation, and swapping that pointer routes every call through a thunk that logs and then chains to the original. The invoking player gets resolved by walking the class chain, controller to component owner to pawn to cheat-manager outer, until something yields a player state. And the parameters get decoded out of the script frame’s locals, which is what makes the record say what was asked for rather than just which command fired. “SetHealth” is ambiguous. “SetHealth 9999” is not.
Writes happen on a dedicated writer thread so the game thread never touches the disk, the file ships with the match result, and it’s cleared before every launch so matches can never inherit each other’s commands.
I want to be really clear about intent here. This is not surveillance of ordinary play. Nothing about your movement, your aim, or your spellcasting is recorded anywhere. What’s recorded is a short list of developer commands that a normal client never sends in its life. The record exists so that when somebody does send them, nobody has to take anyone’s word for anything.
Part 5 of 7: Dominion, the Queue, and the Party
Dominion
- The end screen stays up now. The match-end board with your round points was vanishing about a second after the match ended, followed by a reconnecting overlay and then a boot to the title screen with a network error. It looked exactly like a crash and it wasn’t one: our server supervisor was told to recycle the server at the terminal phase and did so instantly, tearing the world out from under every connected client while they were reading the scoreboard.
- The PARTY column seats five. Dominion is 5v5, so the lobby now shows your placard plus four ADD PLAYER slots, and the podium seats five. Still to be worked on is a proper skill-based matchmaking where your teams are done by some hidden ELO. Not there yet, so you’ll still need to manually swap teams.
- Party size is capped per mode. Solo takes one, Duos two, Squads three, Dominion five. An oversized party gets told before it queues instead of getting split on arrival.
How that end-screen bug hid for so long: the supervisor had a grace hold written for exactly this purpose. It also had a condition that skipped the hold if a restart had been requested. And the terminal-phase handler requested the restart. So the flag that was supposed to protect the end screen was being set by the very code path that needed protecting, every single time, and the hold quietly opted itself out.
The graceful path now leaves the process running and polls the router’s live session count. When it’s been zero for three seconds the server recycles, with a 120-second cap and a delayed terminate as a deterministic fallback so a hung server can’t pin a slot forever.
The queue and readiness
- Your class gauntlet shows in solo. The readiness pip only renders your chosen gauntlet while you read as Ready, which solo never set. Solo now readies the same way duos and squads do when you confirm your class.
- UNREADY? is gone. After a match you couldn’t change modes without the game asking whether to unready you. The backend was missing short matches entirely: mode phase came from a status poll cached for five seconds, so a Dominion match that ended the second it started was never once observed in the “match” phase, and your readiness never got cleared. Now leaving a server clears readiness, period, and solo players get the same refresh a party gets so the menu drops Ready cleanly.
- Reconnecting clears stale readiness. Crashing mid-match or leaving through spectate used to put you back inside the party grace window with Ready still set.
- Party members retry safely. A member whose travel didn’t take now retries for itself only, using the leader’s mode. The old behavior fanned solo tokens out to the entire squad, which is how a leader in Squads and a member in Solo could end up on different servers.
- Readiness survives IN QUEUE and the travel window, so podium classes and pips don’t flicker off while you wait.
One of these hid a genuinely funny regression in the test builds. The queue driver resolves the main-menu game mode by class name, and the class is GMainMenuGameMode, not MainMenuGameMode. Spellbreak drops Unreal’s A prefix but keeps its own G. With the wrong name, the lookup failed silently and Play could not reach a server at all. An hour to find, one character to fix.
Party and presence
- Presence reads like the real thing. Friend rows say “In Lobby (Solo)” or “In Match (Squad)” from the server’s actual knowledge of your mode and phase, not a generic dot. In-match presence requires recent traffic from your client, so a stale session never shows as playing.
- Friend rows apply card and badge art the moment it loads, and party placards fall back to your stored record for class and cosmetics so a placard is never blank.
- The party column shows in Solo too, with your placard and an ADD PLAYER slot, and the “N players online” counter in the lobby is live.
- The lobby podium drops you from the remote slots properly again, using a stable per-account ID.
- Leaving a party or dropping to solo repopulates the placards natively.
- A member’s mode mirrors the leader’s, re-applies if it drifts after a match, and the route hint prefers the mirrored mode so a member always lands on the leader’s server.
How presence labels work: the game renders presence through a localization helper that maps a key to display text. The appserver sends the native keys the game already understands, plus a mode and a phase, and a hook on that helper composes the label. The mode and phase come from the router, which asks the server manager which server your session is on and what phase it’s in. Again: speak the game’s vocabulary and its UI does the work for you.
Equipment
- Hold to swap. Equip Any Rarity now uses the game’s own press-and-hold for replacement pickups. Empty slot equips instantly; swapping shows HOLD TO SWAP and runs the native hold. The prompt text no longer races with prompts for items lying next to each other.
How we did it: the decision lives in a function that answers “should this pickup equip immediately on use,” so a one-branch patch there sends replacements down the hold path and leaves empty slots instant. The prompt relabel took three attempts because I had the calling convention wrong: the real function takes six arguments with a hidden return pointer and by-pointer parameters, and the five-argument version left one argument as garbage, which crashed the moment a player queued up. Reading the actual member layout out of the debug symbols sorted it.
Part 6 of 7: The Website and Marla
Hi-scores
People asked for leaderboards, and I wanted them to look like leaderboards people already know how to read.
- elefrac.com/hiscores is laid out pretty plainly: category rail on the left, one board at a time in the center with Rank, Name, Level and XP, 25 to a page, search tools on the right.
- Progression boards: Overall (Mage Rank) plus each of the six classes, with the real class crests from the game.
- Combat boards: Exiles, K/D ratio (five game minimum), Damage dealt, Wins, Assists, Games played.
- Search by name shows one player’s standing across every board. Search by rank jumps to the page holding that rank and highlights the row. Compare puts two players side by side with the better rank marked.
- Opt-in only. Nobody appears on the Hi-scores unless they turn on “Show me on the Hi-scores” in their profile. The public feed carries names and numbers, nothing else.
How we did it: the boards are one more projection off the metrics event log, computed over opted-in accounts and served through the public sidecar with a short cache, so a popular page never touches the metrics database directly. The website fetches the feed server-side over the private network, doesn’t follow redirects, caches board pages briefly and failures for 30 seconds, and deliberately keeps user-supplied name lookups out of its own cache since the sidecar already handles those.
The Cosmetics Manager
Covered in Part 3. One detail worth knowing: a “select everything” save is roughly a thousand checkboxes, which is more form fields than PHP accepts by default, and the failure mode is silent truncation. So the page joins your picks into a single field before submitting, and the checkboxes remain purely as the no-JavaScript fallback.
The home page
- A press section with four cards: the community wiki, Game Developer’s story on the revival, Rock Paper Shotgun’s piece, and Aftermath’s. Real thumbnails, the site’s own buttons.
- The header shows the live player count and a Hi-scores link, and the nav got trimmed to what people actually click.
- Fixed some copy that was flat-out wrong: accounts are created with an email and password on the site or in the launcher, and Discord gets linked afterward. It used to imply you could register through Discord, which you can’t.
Staff tools
- Player telemetry is a staff page: look up anyone, see the windowed stats, the per-match table with results, class and XP, and the commands recorded in any given match. Discord reports link straight into it.
- Staff can grant and revoke cosmetics, badges, cards and titles by name, and give or remove Mage XP, class XP and Gold, from Discord.
- Dev accounts get a Servers view in the EF Controller: list servers, spool one up, join it, stop it, with Join disabled for servers that are down or mid-match.
Data exports
- The privacy data export is cleaner. The account packet no longer carries columns nothing has written since the first account system, and match rows omit fields the server never captured instead of exporting a wall of nulls.
- Match rows now record the map and whether you were still connected at match end, and the class column is filled in from the played class. Your favorite class in your profile is now the class you’ve played the most games as.
The map fix is a nice little story. The supervisor was watching the server log for the engine’s “bringing world up for play” line to learn the map name, and that line never reaches the supervisor’s log reader at all. Meanwhile the match-tracker mod had been writing the world URL into the match-end file the whole time, right there, unread. Sometimes the data you need is already in your hand.
Marla
- Reports and cheat flags arrive in the moderator channel straight from the game servers, with a link to the telemetry portal.
- /sentinel give and /sentinel remove for cosmetics, badges, cards, titles, Mage XP, class XP and Gold, with autocomplete on cosmetic names so you type “Blood Knight” instead of an asset ID.
- A patch-notes announcer that posts new notes from the site to the updates channel.
- FAQ topics refreshed for the launcher era (controller setup, starting a match, skins, related projects), a rewritten quickstart, and every message updated across all ten languages.
- Retired commands cleaned out: affiliate, cdn, level and publishlauncher are gone.
- Verification reminders now go out to members who never finished verifying, and suspicious joins get flagged to moderators.
Part 7 of 7: Stability
This is the part you never see and always feel. 6.2.0 added a lot of native code surface, and most of the work between the test builds and this release went into making sure that surface can’t take your game down.
The crash safety net
This is the single most important reliability change in the release, and it’s the piece of engineering I’m proudest of.
- A vectored exception handler now sits underneath every risky native call in the appserver mod. If a hook forwards into the game in a state where the game would access-violate, the handler recovers and the hook is skipped instead of the client dying.
- The same guard is now on the dedicated server, wrapped around the post-match stats push, so a bad pointer at match end skips one player’s screen rather than killing the match for everybody in it.
- An engine null guard for a spot where a component unregistering itself mid-match could read a torn-down scene pointer. That was a real crash a bunch of you hit.
- The match-end placard asked the localization table for placement 71 in a bot-filled lobby. Out of range, threw inside an RPC, fatal. The index is clamped now.
- The friends panel could crash when Invite cleared the selection before the handler asked for it. We read the target first now.
- Several hooks that were forwarding a dead
thispointer into the native original got found and guarded.
How the safety net actually works, because it’s genuinely wild and I want it written down somewhere.
Right before a protected native call, the mod captures the CPU context. A vectored exception handler is registered first in line, ahead of everything else. It ignores every exception unless an access violation happens while a protected call is in flight on that same thread. When one does, it copies our captured context over the exception’s context record and tells Windows to continue execution.
The processor resumes at the capture point. Not after the call, at the capture point, with a “recovering” flag set, so the wrapper takes the other branch and returns its default instead of the native result. The faulted stack frames just evaporate. No unwinding, no handler code running inside a broken state, no destructors firing on garbage.
The trade-off is honest: you get a skipped hook, which might mean a stale label until the next repaint. The alternative was your game closing.
Two things make it correct in practice, and both are the kind of detail that bites you at 3am. The wrapper is marked never-inline and reads its state through volatile accesses, because a function that can return twice completely violates the assumptions of a compiler that thinks it returns once, and an optimizer will happily cache a value in a register that our context restore is about to clobber. And the handler is inert unless armed, so ordinary exceptions anywhere else in the game are untouched.
There’s a second layer for the crashes the net can’t catch, the ones that land on the garbage collector’s thread where our per-thread handler has no jurisdiction. A breadcrumb file names the last risky step the mod took on the game thread, overwritten as we go, so after any crash, even an asynchronous one, that file tells us what ran last. And every sub-step of the post-match sequence has a bisect switch, off via a marker file or an environment variable. When a crash lands somewhere invisible, the breadcrumb names the neighborhood and the switches let us disable one step at a time until it stops. That’s how the post-match screen got debugged at all.
Performance and logging
- Per-tick, per-poll and per-packet diagnostics removed across every client mod, and routine flow logging demoted from WARN to INFO. The mod log rotates at 20 MB and old logs get pruned.
- Per-repaint object-table scans replaced with validated pointer caches for the MATCH SETTINGS button, the readiness pips, the main-screen panes, the character classes and the game mode.
- The Wwise audio log got clamped, because it was flooding the log with voice-starvation warnings.
- The server reads its cosmetic catalog from a shipped file instead of parsing it out of every stored player record, which on a few hundred accounts was tens of megabytes of JSON parsing on the event loop.
On those object-table scans, because this is the most common performance mistake in Unreal modding and I made it several times: the engine keeps every live object in one global table, and the easiest way to find a widget is to walk that table matching on class and name. It’s easy, it works, and it’s catastrophic when it runs on every repaint of a menu that repaints constantly.
The version that ships finds each widget once, caches the pointer, and validates it cheaply on use by checking the memory is readable and the vtable word still points where it should. When validation fails, it rescans. You feel the difference as menus that stay crisp while the friends list refreshes behind them.
The backend
- The metrics service projects progression, mastery, hiscores, presence and the cheat record from one immutable event log, so any of them can be rebuilt from history when a formula changes.
- The router owns accounts, cosmetics, purchases, grants, blocks and reports in one database, with every schema change additive, so existing accounts carry straight through an upgrade without a migration that rewrites anything.
- The supervisor keeps the crash logs, crash dumps and stdout of a dynamic game server before removing its container, so a crash is still diagnosable an hour later.
- Match results carry the map, the played class, assists, XP and whether each player finished, so the record of a match is actually complete.
- The client reports 6.2.0 to the version gate, and the launcher updates older clients automatically.
The match result itself deserves a paragraph. The server mod writes the final scoreboard to a file at match end, atomically, via a temp file and a rename, and the supervisor consumes that file. The reason it’s a file and not a network call is that the dedicated server gets recycled immediately after a match, so the metrics post absolutely cannot depend on a process that’s about to stop existing. The supervisor prefers the file, falls back to a live read over its control socket, and falls back again to its last poll cache. Every match you finish gets recorded exactly once.
And before any of this shipped: every service change was rehearsed against a copy of the live account database. 249 real accounts, with their tokens, friendships and bans, run through the new schema setup and verified byte-identical afterward. That rehearsal caught a migration bug, an ordering mistake that would have left the reports table missing two columns until the second restart, and it got fixed before anything touched production. Then four independent code reviews went over the full diff, one each for the services, the server mods, the website and the bot, and every finding from those reviews is in this build.
Getting started
Already playing? Relaunch the launcher. It pulls 6.2.0, patches the game, updates the mods. One thing to expect: your first login after the update seeds your server-side collection, so your outfits will be the base set plus whatever the reward tables say you’ve earned, and you’ll be Mage Rank 1. That’s not a bug. That’s day one of a progression system that’s finally keeping score.
New here? Grab the launcher from elefrac.com, make an account with an email and password, sign in, press Play. Everything in this document is in the box, Clash included.
And if something in here doesn’t match what you’re seeing in-game, that mismatch is exactly the report that makes the next version better, so please tell me. Every match you play now counts toward something. Every bug you find is one fewer for the next person.
Go play Clash. Nobody’s been able to for four years.
See you in the arena. ⚡