Research + feasibility study into making virtually all objects and buildings destructible by reusing BF3's existing destruction systems rather than inventing one.
Full document: docs/destruction.md (branch docs/destruction). Doc only — no code changes.
Verdict
Real fracture destruction is blocked by tooling, not by design.
Rime cannot write mesh resources and cannot write Havok collision. It reads them, exports to glTF for viewing, and packages arbitrary bytes into bundles the game loads — but it cannot manufacture the bytes.
Evidence (all re-verified by direct file read):
RelocPtr.Serialize — the pointer primitive every Frostbite mesh layout is built from — is throw new NotImplementedException() (RimeLibLite/Frostbite/Core/RelocPtr.cs:38-47). RelocArray.Serialize writes only Count/BaseAddress, never the contents.
IMeshConverter has no write method — five methods, all Frostbite → OBJ/glTF (RimeLib.Mesh/IMeshConverter.cs:9-27). RimeLib.Mesh/Generation/ does not exist, though RimeLib.Texture/Generation/ITextureGenerator.cs shows exactly what one would look like.
IHavokConverter has exactly one method: GetTransforms. HavokPhysicsData.Serialize and HavokInstance.Serialize both throw. No convex hull generation, no MOPP compiler. hkpExtendedMeshShape is 200 bytes of un-decoded header.
- The only Havok mutation in the toolkit,
RaiseWaterPhysicsCommand, works only because "The MOPP tree is relative, so it needs no recompile" (:16). Fracturing changes topology, which invalidates the MOPP — the exact dodge fracturing cannot use.
NoHavokGen confirms the boundary. It rewires ~131,926 objects across 41 maps and authors zero geometry — generated superbundles contain only EBX partitions (confirmed by strings on the deployed .sb). When it needs a collision hull at an unbaked scale it cannot make one; it snaps or drops the instance. It proves bulk reference rewiring is tractable at scale, and proves nothing about authoring geometry.
Rime's EBX writer, bundle/superbundle and cat+cas writers are all mature and non-stub. It is a container toolkit with an EBX compiler — the asymmetry is the whole story.
Scale of the ambition
BF3 ships 6,577 StaticModelEntityData assets: 4,878 fully static, 1,697 with a death state, and only 209 true breakable buildings totalling 2,561 hand-authored parts. Only 4 DestructionVolumeAssets exist game-wide. AnimatedDestructionComponentData and DebrisSystemAsset have 0 occurrences in Venice despite existing in the schema.
Ranked approaches
| # |
Approach |
New mesh data? |
Effort |
Verdict |
| 1 |
Runtime damage/impulse API — ApplyDamage, ApplyImpulse, SetActiveHealthState on existing destructibles |
none |
days |
Do this first. Entirely unexploited today. |
| 2 |
Static → banger swap — one Health 0.0 HealthStateData + SpawnedBangerBlueprint |
none |
1–2 wks |
The pragmatic win. |
| 3 |
Static → dynamic physics prop — flip MotionType_Fixed → _Dynamic pre-spawn |
none |
days |
Cheap, cosmetic. |
| 4 |
Reuse the 209 shipped breakables where dimensions match |
none |
1–2 wks |
Narrow but high fidelity. |
| 5 |
Generate real fracture destruction |
yes — cannot write it |
blocked |
Not possible. |
The key data finding: the minimal authoring delta between a static prop and a breakable one is one HealthStateData and one blueprint reference. A banger needs no per-part meshes — just an intact mesh, a banger mesh, and a dynamic Havok asset. That is what makes #2 viable.
Note MapEditor's current lever is the opposite one — HealthStatePatcher.lua:10-14 sets health = 10000000 on every HealthStateData, a global "make everything indestructible" switch. Making it bidirectional and per-object is a natural first slice, and any destruction work must coordinate with it or be silently neutralised.
Cost — network IDs are the hard wall
- Bundle size: not the constraint (~160 B of EBX per object in NoHavokGen's real numbers).
- Level load: NoHavokGen already raised client timeouts to 50 s for ~7k entities and still shipped a timeout fix. A destruction pass runs on top of that. Little headroom.
- Network: measured on the 209 shipped breakables,
NetworkIdCount ≈ 2 × parts + 2 (6 for a 2-part wall, 20 for a 9-part house, up to 78). NoHavokGen computes needs_network_id then hardcodes it False right after a commit titled "fix crash/desync state when destroying nohavok objects". A destruction feature cannot take that escape hatch — destruction must be networked or clients disagree about the map. Converting all ~4,878 statics is not shippable; a per-map budget spent on props players actually shoot at is.
Phased plan
- Unlock what exists (days) — server module wrapping
ServerPhysicsEntity:ApplyDamage/SetActiveHealthState/ApplyImpulse/internalHealth; damage/destroy/reset tools; health in the inspector. Zero new data, zero risk. Ship regardless.
- Prove the banger path (1–2 wks) — experiments E1/E2 below. If it works, this is the feature; if not, fall back to dynamic-physics props.
- Budgeted bulk conversion (wks) — offline-generated EBX following NoHavokGen's proven shape,
WorldPartData bucketing, measured continuously.
- Breakable catalogue (wks, optional) — index the 209 by bbox/parts/material, offer "replace with destructible equivalent".
- Real fracture generation — blocked. Needs a Frostbite 2 MeshSet serializer and a Havok packfile writer with a MOPP compiler. Do not start expecting to finish.
Experiments needed (none run — server was in use)
- E1 — spawn a shipped banger via
CreateEntitiesFromBlueprint. Isolates whether the LevelInjector.lua:513-533 banger blacklist is path-specific or fundamental.
- E2 (decisive) — add a
Health 0.0 / PartIndex 0xFFFFFFFF state to one static prop pointing at a generated BangerEntityData that reuses the prop's own mesh and HavokAsset with MotionType_Dynamic. Shoot it. If it tips over, Phase 2 succeeds.
- E3 — network budget: convert 10/50/200/1000 props, destroy all at once, watch for desync and ID exhaustion.
- E4 — load time and server frame time on MP_Subway before/after.
- E5 —
GetIterator("ServerPhysicsEntity") + ApplyDamage sweep. Confirms Phase 1 in one command.
- E6 — degenerate
BreakableModelEntityData with a plain RigidMeshAsset: does it degrade gracefully or crash?
The doc separates VERIFIED from INFERRED throughout. The highest-value unverified claim is that a prop's existing fixed HavokAsset can be reused as a dynamic rigid body's collision — the whole cheap banger path rests on it.
Research + feasibility study into making virtually all objects and buildings destructible by reusing BF3's existing destruction systems rather than inventing one.
Full document:
docs/destruction.md(branchdocs/destruction). Doc only — no code changes.Verdict
Real fracture destruction is blocked by tooling, not by design.
Evidence (all re-verified by direct file read):
RelocPtr.Serialize— the pointer primitive every Frostbite mesh layout is built from — isthrow new NotImplementedException()(RimeLibLite/Frostbite/Core/RelocPtr.cs:38-47).RelocArray.Serializewrites onlyCount/BaseAddress, never the contents.IMeshConverterhas no write method — five methods, all Frostbite → OBJ/glTF (RimeLib.Mesh/IMeshConverter.cs:9-27).RimeLib.Mesh/Generation/does not exist, thoughRimeLib.Texture/Generation/ITextureGenerator.csshows exactly what one would look like.IHavokConverterhas exactly one method:GetTransforms.HavokPhysicsData.SerializeandHavokInstance.Serializeboth throw. No convex hull generation, no MOPP compiler.hkpExtendedMeshShapeis 200 bytes of un-decoded header.RaiseWaterPhysicsCommand, works only because "The MOPP tree is relative, so it needs no recompile" (:16). Fracturing changes topology, which invalidates the MOPP — the exact dodge fracturing cannot use.NoHavokGen confirms the boundary. It rewires ~131,926 objects across 41 maps and authors zero geometry — generated superbundles contain only EBX partitions (confirmed by
stringson the deployed.sb). When it needs a collision hull at an unbaked scale it cannot make one; it snaps or drops the instance. It proves bulk reference rewiring is tractable at scale, and proves nothing about authoring geometry.Rime's EBX writer, bundle/superbundle and cat+cas writers are all mature and non-stub. It is a container toolkit with an EBX compiler — the asymmetry is the whole story.
Scale of the ambition
BF3 ships 6,577
StaticModelEntityDataassets: 4,878 fully static, 1,697 with a death state, and only 209 true breakable buildings totalling 2,561 hand-authored parts. Only 4DestructionVolumeAssets exist game-wide.AnimatedDestructionComponentDataandDebrisSystemAssethave 0 occurrences in Venice despite existing in the schema.Ranked approaches
ApplyDamage,ApplyImpulse,SetActiveHealthStateon existing destructiblesHealth 0.0HealthStateData+SpawnedBangerBlueprintMotionType_Fixed→_Dynamicpre-spawnThe key data finding: the minimal authoring delta between a static prop and a breakable one is one
HealthStateDataand one blueprint reference. A banger needs no per-part meshes — just an intact mesh, a banger mesh, and a dynamic Havok asset. That is what makes #2 viable.Note MapEditor's current lever is the opposite one —
HealthStatePatcher.lua:10-14setshealth = 10000000on everyHealthStateData, a global "make everything indestructible" switch. Making it bidirectional and per-object is a natural first slice, and any destruction work must coordinate with it or be silently neutralised.Cost — network IDs are the hard wall
NetworkIdCount ≈ 2 × parts + 2(6 for a 2-part wall, 20 for a 9-part house, up to 78). NoHavokGen computesneeds_network_idthen hardcodes itFalseright after a commit titled "fix crash/desync state when destroying nohavok objects". A destruction feature cannot take that escape hatch — destruction must be networked or clients disagree about the map. Converting all ~4,878 statics is not shippable; a per-map budget spent on props players actually shoot at is.Phased plan
ServerPhysicsEntity:ApplyDamage/SetActiveHealthState/ApplyImpulse/internalHealth; damage/destroy/reset tools; health in the inspector. Zero new data, zero risk. Ship regardless.WorldPartDatabucketing, measured continuously.Experiments needed (none run — server was in use)
CreateEntitiesFromBlueprint. Isolates whether theLevelInjector.lua:513-533banger blacklist is path-specific or fundamental.Health 0.0/PartIndex 0xFFFFFFFFstate to one static prop pointing at a generatedBangerEntityDatathat reuses the prop's own mesh andHavokAssetwithMotionType_Dynamic. Shoot it. If it tips over, Phase 2 succeeds.GetIterator("ServerPhysicsEntity")+ApplyDamagesweep. Confirms Phase 1 in one command.BreakableModelEntityDatawith a plainRigidMeshAsset: does it degrade gracefully or crash?The doc separates VERIFIED from INFERRED throughout. The highest-value unverified claim is that a prop's existing fixed
HavokAssetcan be reused as a dynamic rigid body's collision — the whole cheap banger path rests on it.