This is a write-up of how a plugin I built for Chaos Destruction works and why it is designed this way: what happens to a fragment after it breaks off, and when its physics can end while the debris stays on screen. It’s not a step-by-step tutorial. The original is in Russian on Habr.
Where this came from
On one project we leaned heavily on Chaos Destruction. A large part of the environment was breakable: walls, panels, crates, level props. I owned the whole destruction system, and requirements came at me from two sides.
Art and the leads wanted everything to break in a physically convincing way: panels fall off, chunks of wall drop, fragments collide with each other and with other physics objects. There was also one hard rule: debris does not disappear. If a piece of wall fell, it stays where it landed. The debris on the floor tells the player what happened in that spot, and for art it was a visual trace that must not be erased.
Tech art saw it from the other side. One performance pass showed a lot of fragments lying around the scene, barely moving, yet still taking part in the simulation. They jostled in piles, snagged on their neighbours, didn’t go to sleep when we needed them to, and all of that added up to CPU cost.
Simply turning physics off right after a piece breaks away isn’t an option. The fragment needs time to move, fall and settle into place. Otherwise pieces hang in mid-air, or the ground under the player changes at an unexpected moment. I needed something that worked for both art and tech art, and that didn’t turn setting up every new crate into an engineering task of its own.

Demo scene with 1,800 fragments. The walls are destroyed and the debris stays on the floor. The counter on the wall shows how many candidates the optimizer is still tracking.
Why the built-in tools weren’t enough
The engine has the mechanisms you need: Chaos provides sleep, body disabling, removal settings and break events. They’re just spread across different places (Geometry Collection, physics, collision, dozens of checkboxes), and assembling them into one coherent, predictable cleanup mechanism for a whole project turned out to be hard. When I did assemble one, it didn’t always behave the way I expected, and it wasn’t clear why.
I didn’t want to build yet another panel exposing the same properties. I wanted to control something easier to reason about: what happens to a fragment at each stage of its life.
A lifecycle instead of settings
From the fragment’s point of view, life is simple. While it’s still part of the intact object, it costs nothing and there’s no reason to watch it. Once it breaks off, it flies, falls and bumps into the world, and that’s where it costs CPU. Once it stops, after a while it should “know” on its own that it’s time to sleep or die, and if needed, leave a simple collision shape behind in its place.
Fragment lifecycle. The visible geometry doesn’t change at any stage.
Two clarifications that the rest of the article relies on.
By “removal” I mean taking the particle out of the physics simulation, not removing the geometry. The fragment stays where it last was and keeps rendering, but it no longer lives as a separate body. The optimizer itself doesn’t hide anything or swap materials.
Also: sleeping and disabling are easy to mix up, but they are different states. A sleeping body stays in the physics scene: it has collision and can be woken up. A disabled body is not in the physics scene at all. That’s why sleep is enough for some objects, while others really need their physical life to end.
| Sleep | Disable | Disable + proxy | |
|---|---|---|---|
| Fragment visible | yes | yes | yes |
| Stays in the physics scene | yes, asleep | no | simple invisible shape |
| Can be woken up | yes | no | no |
| Character can stand on it | yes | no, falls through | yes |
| Collides with simulated bodies | yes, and wakes up | no | no (by default) |
That’s how I ended up with a lightweight control layer on top of the stock Chaos classes. The engine handles the destruction and the motion; my layer decides when simulation is no longer needed, what to keep for gameplay, and how to show all of this to whoever is setting up the scene. That layer grew into the Chaos Debris Optimizer plugin (everything below refers to version 1.1.0 for UE 5.7–5.8, and all numbers are default settings).
When can a fragment be considered stopped?
The first real difficulty. Three pieces break off at the same moment: one lands right away, the second rolls down a slope, the third gets kicked by a neighbour. A single lifetime of “die after N seconds” doesn’t work here: it either cuts the motion short or keeps bodies alive for no reason.
So a fragment is first allowed to move for a while, and then its state is checked. Speed below the threshold means we can act; above it, we check again in a second. Waiting forever isn’t an option either: micro-contacts in a pile, a moving support, repeated collisions can keep a fragment “alive” for a very long time. Rechecks are capped at 10 retries (MaxRetries), after which the fragment is processed regardless. It’s a trade-off: the time a body can stay physically active is bounded, even if it’s technically still twitching.
A separate case is when we wait for Chaos to put the body to sleep itself. That’s the most natural signal, but in a dense pile it comes late or never comes at all, because the pieces keep each other half-awake. I added my own settle check: if a fragment barely moves for five checks in a row, its physical life can be ended without waiting for the engine’s sleep. I also had to handle the “edge dance”, where a piece isn’t moving anywhere but keeps spinning in place. That gets its own condition.
In simplified form, a single fragment check looks like this. It doesn’t run every frame; it’s called by the recheck timer:
// Pseudocode: the check logic, not the plugin's source.
// "Wait for engine sleep" mode: this is where the custom settle check is used
bool LooksSettled(float LinearSpeed, float AngularSpeed)
{
if (LinearSpeed < SpeedLimit)
return true; // barely moving
// "Edge dance": almost no translation, but the piece spins in place
return LinearSpeed < SpeedLimit * 2.f && AngularSpeed > SpinLimit;
}
void CheckFragment(FFragment& Fragment)
{
if (Fragment.IsAsleepInEngine())
{
FinishPhysics(Fragment); // Chaos put it to sleep, no need to wait
return;
}
if (LooksSettled(Fragment.LinearSpeed(), Fragment.AngularSpeed()))
{
// One low reading isn't a stop yet: it takes several checks in a row
if (++Fragment.CalmChecks >= ChecksRequired)
FinishPhysics(Fragment);
}
else
{
Fragment.CalmChecks = 0; // it moved, start counting again
}
}
There’s a trap here that I walked into myself: low speed doesn’t mean there’s anything supporting the piece. A fragment in free flight is also slow for an instant at the top of its arc. Thresholds that are too lenient stop the simulation too early, and it looks unnatural. Tune the thresholds on real trajectories; a pile that has already settled is no good for that.
In the end, time, speed and engine state became three separate signals, and what to do with them is decided by the selected policy: sleep, disable, sleep-then-disable, or wait for the engine.
Fragments that slipped past tracking
I started with the obvious: catch the break event and register the piece that broke off. It quickly turned out that real destruction doesn’t fit into a single wave of events. First a large chunk breaks off, then that chunk breaks apart in turn. And with connectivity enabled, the engine builds internal clusters on the fly that didn’t exist in the source geometry. Some bodies simply slipped past tracking and went on living their own physical lives.
Since then I don’t rely on events alone. Roughly every half second, the optimizer checks its tracking against the actual particle state on the physics thread: which eligible bodies exist in the simulation right now. Ones that are already disabled, ineligible, or still controlled by a parent are filtered out; the rest are tracked the same way as the ones registered through events. For the user, a fragment found late is no different: it’s the same kind of fragment with the same rules.
This reconciliation isn’t free, and it isn’t global: it’s tied to a specific component and its collections. Several optimizers on the same solver will each walk it on their own, so keep that in mind when profiling.
A cluster is not one fragment
Not everything that looks like one big piece of debris is a single body in Chaos. There are leaf fragments, and there are clusters: several parts combined into a single physics body. From the outside I wanted one rule for everything, but internally they had to be treated differently.
Disabling the cluster parent isn’t enough: its child parts keep their connections, and orphaned collision can be left hanging in the scene. Clusters get their own path. First, remember the direct children; then disable the cluster through the stock clustering mechanism; then disable the children and clear their link to the former parent. This works for direct children; I don’t treat deeper nesting as verified.
Clusters that the engine built on the fly added an identification problem: they have no regular index in the source geometry. I had to address them by physics particle ID and fetch their position and state from the physics thread asynchronously.
By default I don’t disable clusters, I put them to sleep. It’s the more cautious policy and you can change it, but with the default left in place, clusters go to sleep even when the general mode is set to disable.
No physics, but you still need to stand on the debris
Once a fragment stops simulating, there’s one question left: does the player need a surface there, not just a picture? Decorative rubble only needs to render. But a debris pile can lie in the character’s path, and if you disable the bodies without replacing them, the debris stays visible but there’s nothing left to stand on.
The way out came when I separated three things: what the player sees, what is still simulated, and what collision gameplay actually needs. The visuals stay as they are. The simulation ends. And when collision is needed, it comes from an invisible proxy component: a box, sphere, capsule or convex hull. Position and shape data are captured before the original body is disabled (for clusters, directly on the physics thread, one shape for the whole cluster).
By default this collision only takes part in queries (traces and sweeps), not in simulation collisions. A character that checks the ground with sweeps can walk on it, and the new proxy doesn’t start shoving neighbouring debris that is still moving. If you need a physics grenade to roll across it, that’s a different mode and different collision channels.
Whether the proxy affects navigation is a separate toggle, off by default: stepping on a surface and pathfinding across it are different problems. The proxy also has its own lifetime, so the debris stays visible while its collision can be removed later.
A character standing on a debris pile. Left: the game with debug draw; orange outlines are merged convex proxy hulls, green boxes are their bounds. Right: the physics scene at the same moment; the debris bodies are already gone from it.
Convex hulls aren’t free either
The very first large-scale scene showed that if you build a convex hull for every killed fragment immediately, the cost piles up in a single frame on the game thread. I’d just swapped one problem for another.
What helped was a cap on hull creation per processing pass, plus a queue. There’s no cap by default: it’s enabled in the settings, and the convex-hull preset already has one set. Geometry is captured up front, while the source particles are still available, and the components themselves are created gradually. The price: a particle can be disabled before its deferred hull appears, so a short gap with no support is possible. For the surface directly under the player, test this separately and, if needed, use a simpler shape, a different cap, or different transition timing.
Neighbouring hulls can also be merged into one. That means fewer components, but rebuilding the combined shape is expensive and smooths over the gaps between pieces. I added it as an option with deferred rebuilds and a ceiling of 2,000 accumulated vertices. Sometimes a few simple boxes fit the budget better than a merged hull that keeps getting rebuilt.
What the optimizer itself costs
Saving on physics is pointless if monitoring the fragments becomes a new source of hitches. So the component has no per-frame tick. Processing is started by a timer once candidates appear, and it stops when the queues are empty and a grace window for late results, about two seconds, has passed. In a scene with nothing to process, the optimizer stays idle.
There’s a separate group of settings for dense destruction. The first checks are spread out in time a little, so a big wave of candidates doesn’t hit the same threshold all at once. The number of transitions per pass is capped. Commands of the same kind are sent to physics as a single batch.
These mechanisms have limits. A cap on the number of operations is not a cap in milliseconds: different operations cost different amounts. Each component has its own budget; there’s no shared budget for the level. All of this is off by default and is worth enabling for a specific problem you’ve actually observed.
Rules that depend on the scene
One lifecycle doesn’t mean one answer for every object. On top of it there are a few rules for specific situations.
If a piece falls below a set height, it’s disabled with no proxy at all: no gameplay surface is needed down there. A piece that stays close to where it broke off is kept asleep and periodically re-evaluated, and if it moves, it goes back into normal processing. At the end of each work cycle a final pass over residual bodies runs as a safety net. And there are two manual commands: kill all residual debris, and a more aggressive one that kills everything, including pieces still in the air (that one is for “kill zone” triggers).
In practice, all of this comes together as presets for different kinds of destruction. Six ship with the plugin. A preset is only a starting point; the artistic decision stays with the team.
How to see a difference you can’t see
Being able to check what’s going on was one of the original reasons for all this work. If there’s debris on the floor, you can’t tell from the picture whether it’s simulating, sleeping or already disabled. And in our case the picture was supposed to stay exactly the same, so checking by eye isn’t enough.
So the optimizer provides events for registration, sleep and disable, counters, and debug drawing of transitions: you can see where sleep was applied, where a regular disable happened, and where the height rule or settle detection kicked in. Proxies are drawn as a separate layer.

Debug draw in “sleep, then disable” mode: yellow spheres mean the piece was put to sleep, red ones mean it was disabled. The debris stays where it is.

The same scene: the game on the left, Chaos Visual Debugger on the right. In the game the debris stays on the floor; in the physics scene it disappears.
There are two places where it’s easy to fool yourself. The optimizer’s counter shows the number of candidates it is tracking, not the number of active bodies in Chaos; a processed candidate may simply have gone to sleep. A “disabled” event only means the command was sent: the physics thread will apply it later, and a snapshot in the same tick may not see the result yet. For deeper checks you still have the stock tools. Chaos Visual Debugger shows what is really happening in the physics scene, and the optimizer explains why it made a decision.
How to measure it
I’m not giving speed-up percentages here: without a reproducible set of measurements, numbers like that create false precision. Instead, here’s how to measure it on your own project.
Same scene, same destruction setup, same sequence of destruction inputs. Variants: no optimizer, sleep only, disable without proxies, disable with proxies; boxes and convex hulls measured separately. Three phases: the destruction itself, the transition to sleep or disable along with proxy creation, and the settled scene. The optimizer works on the second and third; it doesn’t reduce the cost of the first. Look at the median and the tails, because a rare long frame gets lost in the average. And you definitely need a visual criterion: after disabling, the pieces are in place, nothing hangs in mid-air, and the surfaces you need are still there.
What’s still unsolved
The optimizer manages the life of fragments after destruction. It doesn’t make the destruction itself cheap, it doesn’t fix heavy source collision, and it doesn’t cancel out rendering cost: visible debris still takes memory and still gets drawn. Only the dynamic work you don’t need goes away.
There’s technical debt too. Some reads of internal physics data happen on the game thread; that’s marked in the code, but I wouldn’t call it a model of thread safety. These reads should go through consistent snapshots from the physics thread, as runtime clusters already do. Deferred proxies can leave a short gap with no support. Deep cluster nesting isn’t fully verified.
About multiplayer. The optimizer works as a local optimization and has no synchronization protocol of its own. Proxies aren’t created on a dedicated server, so a client-side surface can’t automatically be assumed valid for server-side checks.
Wrapping up
Art needed convincing destruction and a trace that doesn’t disappear; tech art needed controlled cost and an understanding of what’s happening in the scene. I managed to reconcile the two without yet another panel of Chaos properties and without a blanket lifetime for debris: what worked was managing fragment states on top of the stock engine.
For simple experiments I also made a free Chaos Debris Optimizer Lite on Fab. It’s deliberately simplified: fixed-delay processing for flat, non-clustered collections, without waiting for fragments to settle, and without replacement collision. You can use it to try the idea itself; it is far from covering everything described above.

