Header menu logo Mibo

Migrating to Mibo v4

This page collects the breaking changes between the last v3 release (3.3.0) and v4 (4.0.0), with the exact steps to update your code. Work through the sections that match your code — most games are affected by none of them.

_The short answer for most games: upgrade the package, recompile, done. Every source-level break is in 3D skeletal-animation internals, multi-camera-block lighting, or the layered-grid getOrAddLayer destructuring. If your game draws animated models through buffer.animatedModel(...), uses a single camera, and does not call getOrAddLayer, v4 is a drop-in recompile._

The headline features of v4 — bone pose queries and attachment draws, skinned + instanced draws, and one shared BonePose evaluation per frame — are additive and need no migration. See Animation 3D.

1. Recompile against the new assemblies (binary break)

Who is affected: everyone with pre-compiled assemblies referencing Mibo.

buffer.animatedModel / animatedModelWith / animatedModelWithPerMesh gained an optional pose parameter, which changes their compiled (IL) signature. Existing source compiles unchanged — but assemblies built against v3 must be recompiled. A plain dotnet build of your game is enough; there is nothing to change in code.

2. Animation types are now struct records

Who is affected: code that constructs AnimatedMesh literally, or relies on reference identity of the animation types.

Animation3DChannel, Animation3DClip, Animation3DClips, and AnimatedMesh (MonoGame), and AnimatedMesh (raylib) changed from reference records to [<Struct>] records. Consequences:

// v3 — no longer compiles
let mesh = { Mesh = m; InverseBindPose = ibp; BoneNames = names; ... }

// v4 — add BindPose, or (better) let the loader build the record
let mesh = { Mesh = m; InverseBindPose = ibp; BoneNames = names
             BindPose = bindPose; ... }

// recommended — populated for you, no literal construction
match AnimatedMesh.fromModel model with
| ValueSome mesh -> ...
| ValueNone -> ...

3. raylib: bone palettes are plain row-major now

Who is affected: raylib code that builds its own bone palettes and feeds them to buffer.skinnedMesh(...) / DrawSkinnedMesh.

Symptom after upgrading: skinned meshes render distorted or garbled.

AnimatedMesh.computeBoneMatrices now returns the palette in plain System.Numerics row-major layout (result[i] = InverseBindPose[i] * pose[i]) instead of pre-transposed into raylib's native layout. The framework transposes at upload time where the shader contract needs it.

What to do: drop any manual pre-transpose around palettes you pass in. Palettes produced by computeBoneMatrices or a computed BonePose are unchanged and render the same as before.

4. Lights and shadows are scoped per camera block

Who is affected: frames with more than one camera block (split-screen, minimaps, rear-view mirrors). Single-camera frames are unchanged.

Symptom after upgrading: lights "leak" differently between views — a view that set its own lights no longer also gets the lights emitted before or after it, and each view renders its own shadow map.

The new rules, on both backends:

What to do: emit the lights every view shares before the first camera block, and per-view lights inside that view's block. If you relied on lights accumulating across blocks, move the shared ones to the frame defaults.

5. Only the first directional light is shaded

Who is affected: scenes with more than one directional light.

Symptom after upgrading: the second (and later) directional lights no longer contribute any light, and a non-casting first light no longer "borrows" a later casting light's shadow map.

On both backends, only the first directional light is shaded, and only it can cast shadows. Previously a frame whose first directional light didn't cast could still be shadowed by a later casting light's map, and a casting light could render a shadow map nothing sampled.

What to do: merge your directional lights into one sun (combine color and intensity), and make sure the shadow-casting directional light is the first one emitted.

6. Deprecation warnings on the piped draw modules (not breaking)

After upgrading, code using the piped draw modules — Draw, Draw3D, LightDraw, ParticleDraw — builds with warning FS0044 pointing at the fluent draw DSL. The modules still work and will not be removed before a future major release, so you can migrate at your own pace, file by file:

// piped (deprecated — still works)
buffer |> Draw3D.drawModel model transform |> Draw3D.drop

// fluent (recommended)
buffer.model(model, transform).drop()

The full mapping, including lighting, particles, and grid rendering, is in Draw DSL → Migrating from the piped DSL. To silence the warnings until you migrate, add FS0044 to your project's NoWarn — but prefer migrating, since the modules will be removed in a future release.

7. Layered grids: getOrAddLayer returns a struct tuple

Who is affected: code that calls getOrAddLayer on LayeredGrid2D, LayeredHexGrid, LayeredGrid3D, or LayeredHexGrid3D.

Symptom after upgrading: the call no longer compiles — the returned tuple can no longer be destructured with the plain let a, b = ... form.

getOrAddLayer now returns a struct tuple (allocation-free). Destructure with let struct instead:

// v3 — no longer compiles
let terrainGrid, _ = LayeredGrid2D.getOrAddLayer Layer.Terrain chunk.Grids

// v4
let struct (terrainGrid, _) = LayeredGrid2D.getOrAddLayer Layer.Terrain chunk.Grids

There is no runtime behavior change — the layer is created on demand and returned exactly as before.

See also

val mesh: obj
union case ValueOption.ValueSome: 'T -> ValueOption<'T>
union case ValueOption.ValueNone: ValueOption<'T>
val terrainGrid: obj

Type something to start searching.