diff --git a/LEGO1/lego/legoomni/src/entity/legocameracontroller.cpp b/LEGO1/lego/legoomni/src/entity/legocameracontroller.cpp
index 90850e47..1ccb98ea 100644
--- a/LEGO1/lego/legoomni/src/entity/legocameracontroller.cpp
+++ b/LEGO1/lego/legoomni/src/entity/legocameracontroller.cpp
@@ -4,6 +4,7 @@
 #include "legonotify.h"
 #include "legovideomanager.h"
 #include "misc.h"
+#include "realtime/realtime.h"
 
 DECOMP_SIZE_ASSERT(LegoCameraController, 0xc8);
 
diff --git a/LEGO1/lego/legoomni/src/entity/legoentity.cpp b/LEGO1/lego/legoomni/src/entity/legoentity.cpp
index bb473c14..5b8aff77 100644
--- a/LEGO1/lego/legoomni/src/entity/legoentity.cpp
+++ b/LEGO1/lego/legoomni/src/entity/legoentity.cpp
@@ -9,6 +9,7 @@
 #include "legoworld.h"
 #include "misc.h"
 #include "mxmisc.h"
+#include "realtime/realtime.h"
 
 DECOMP_SIZE_ASSERT(LegoEntity, 0x68)
 
diff --git a/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp b/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp
index 5aa70e6c..b0cea4c1 100644
--- a/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp
+++ b/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp
@@ -11,6 +11,7 @@
 #include "mxstreamchunk.h"
 #include "mxtimer.h"
 #include "mxvideomanager.h"
+#include "realtime/realtime.h"
 
 DECOMP_SIZE_ASSERT(LegoAnimPresenter, 0xc0)
 
diff --git a/LEGO1/lego/legoomni/src/video/legomodelpresenter.cpp b/LEGO1/lego/legoomni/src/video/legomodelpresenter.cpp
index 83938dfb..a7fc8d5c 100644
--- a/LEGO1/lego/legoomni/src/video/legomodelpresenter.cpp
+++ b/LEGO1/lego/legoomni/src/video/legomodelpresenter.cpp
@@ -13,6 +13,7 @@
 #include "misc/version.h"
 #include "mxcompositepresenter.h"
 #include "mxutilities.h"
+#include "realtime/realtime.h"
 #include "roi/legoroi.h"
 
 // GLOBAL: LEGO1 0x100f7ae0
diff --git a/LEGO1/lego/legoomni/src/video/legovideomanager.cpp b/LEGO1/lego/legoomni/src/video/legovideomanager.cpp
index a45650e3..ac8446da 100644
--- a/LEGO1/lego/legoomni/src/video/legovideomanager.cpp
+++ b/LEGO1/lego/legoomni/src/video/legovideomanager.cpp
@@ -6,6 +6,7 @@
 #include "mxtimer.h"
 #include "mxtransitionmanager.h"
 #include "realtime/matrix.h"
+#include "realtime/realtime.h"
 #include "roi/legoroi.h"
 #include "tgl/d3drm/impl.h"
 #include "viewmanager/viewroi.h"
diff --git a/LEGO1/lego/sources/roi/legoroi.cpp b/LEGO1/lego/sources/roi/legoroi.cpp
index df83cd37..e06f7832 100644
--- a/LEGO1/lego/sources/roi/legoroi.cpp
+++ b/LEGO1/lego/sources/roi/legoroi.cpp
@@ -6,6 +6,7 @@
 #include "legolod.h"
 #include "misc/legocontainer.h"
 #include "misc/legostorage.h"
+#include "realtime/realtime.h"
 
 #include <string.h>
 #include <vec.h>
@@ -539,8 +540,8 @@ float LegoROI::IntrinsicImportance() const
 	return .5;
 }
 
-// STUB: LEGO1 0x100aa350
+// FUNCTION: LEGO1 0x100aa350
 void LegoROI::UpdateWorldBoundingVolumes()
 {
-	// TODO
+	CalcWorldBoundingVolumes(m_sphere, m_local2world, m_world_bounding_box, m_world_bounding_sphere);
 }
diff --git a/LEGO1/realtime/orientableroi.cpp b/LEGO1/realtime/orientableroi.cpp
index 3d2ad0e2..24d84f3c 100644
--- a/LEGO1/realtime/orientableroi.cpp
+++ b/LEGO1/realtime/orientableroi.cpp
@@ -131,6 +131,32 @@ void OrientableROI::UpdateWorldVelocity()
 {
 }
 
+// FUNCTION: LEGO1 0x100a5a60
+void CalcWorldBoundingVolumes(
+	const BoundingSphere& modelling_sphere,
+	const Matrix4& local2world,
+	BoundingBox& world_bounding_box,
+	BoundingSphere& world_bounding_sphere
+)
+{
+	// calculate world bounding volumes given a bounding sphere in modelling
+	// space and local2world transform
+
+	// ??? we need to transform the radius too... if scaling...
+
+	V3XM4(world_bounding_sphere.Center(), modelling_sphere.Center(), local2world);
+
+	world_bounding_sphere.Radius() = modelling_sphere.Radius();
+
+	// update world_bounding_box
+	world_bounding_box.Min()[0] = world_bounding_sphere.Center()[0] - world_bounding_sphere.Radius();
+	world_bounding_box.Min()[1] = world_bounding_sphere.Center()[1] - world_bounding_sphere.Radius();
+	world_bounding_box.Min()[2] = world_bounding_sphere.Center()[2] - world_bounding_sphere.Radius();
+	world_bounding_box.Max()[0] = world_bounding_sphere.Center()[0] + world_bounding_sphere.Radius();
+	world_bounding_box.Max()[1] = world_bounding_sphere.Center()[1] + world_bounding_sphere.Radius();
+	world_bounding_box.Max()[2] = world_bounding_sphere.Center()[2] + world_bounding_sphere.Radius();
+}
+
 // FUNCTION: LEGO1 0x100a5d80
 const float* OrientableROI::GetWorldVelocity() const
 {
diff --git a/LEGO1/realtime/realtime.h b/LEGO1/realtime/realtime.h
index cb8298a1..a9273cfe 100644
--- a/LEGO1/realtime/realtime.h
+++ b/LEGO1/realtime/realtime.h
@@ -2,6 +2,7 @@
 #define REALTIME_H
 
 #include "matrix.h"
+#include "roi.h"
 
 #define NORMVEC3(dst, src)                                                                                             \
 	{                                                                                                                  \
@@ -11,4 +12,8 @@
 
 void CalcLocalTransform(const Vector3& p_posVec, const Vector3& p_dirVec, const Vector3& p_upVec, Matrix4& p_outMatrix);
 
+// utility to help derived ROI classes implement
+// update_world_bounding_volumes() using a modelling sphere
+void CalcWorldBoundingVolumes(const BoundingSphere& modelling_sphere, const Matrix4& local2world, BoundingBox&, BoundingSphere&);
+
 #endif // REALTIME_H
diff --git a/LEGO1/realtime/roi.h b/LEGO1/realtime/roi.h
index 70f335a0..7a5ff69a 100644
--- a/LEGO1/realtime/roi.h
+++ b/LEGO1/realtime/roi.h
@@ -4,10 +4,10 @@
 // ROI stands for Real-time Object Instance.
 
 #include "compat.h"
+#include "decomp.h"
 #include "lodlist.h"
 #include "mxgeometry/mxgeometry3d.h"
 #include "mxstl/stlcompat.h"
-#include "realtime/realtime.h"
 
 /*
  * A simple bounding box object with Min and Max accessor methods.