diff --git a/CultLib.sln b/CultLib.sln
index 4d90a321..506fe5f7 100644
--- a/CultLib.sln
+++ b/CultLib.sln
@@ -25,61 +25,167 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCult.Networking.Tests",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCult.Caching.Tests", "tests\GameCult.Caching.Tests\GameCult.Caching.Tests.csproj", "{EB15435B-7147-4BD9-BB9C-B0A14318D3D5}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCult.Geometry", "src\GameCult.Geometry\GameCult.Geometry.csproj", "{94C086DA-3359-424B-9688-29A198806481}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCult.Geometry.Tests", "tests\GameCult.Geometry.Tests\GameCult.Geometry.Tests.csproj", "{C95A9A21-34FA-4D82-BE5B-88FBDA836D31}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{72AEDD42-E881-4EB7-99A8-FD3B9667189C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{72AEDD42-E881-4EB7-99A8-FD3B9667189C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {72AEDD42-E881-4EB7-99A8-FD3B9667189C}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {72AEDD42-E881-4EB7-99A8-FD3B9667189C}.Debug|x64.Build.0 = Debug|Any CPU
+ {72AEDD42-E881-4EB7-99A8-FD3B9667189C}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {72AEDD42-E881-4EB7-99A8-FD3B9667189C}.Debug|x86.Build.0 = Debug|Any CPU
{72AEDD42-E881-4EB7-99A8-FD3B9667189C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{72AEDD42-E881-4EB7-99A8-FD3B9667189C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {72AEDD42-E881-4EB7-99A8-FD3B9667189C}.Release|x64.ActiveCfg = Release|Any CPU
+ {72AEDD42-E881-4EB7-99A8-FD3B9667189C}.Release|x64.Build.0 = Release|Any CPU
+ {72AEDD42-E881-4EB7-99A8-FD3B9667189C}.Release|x86.ActiveCfg = Release|Any CPU
+ {72AEDD42-E881-4EB7-99A8-FD3B9667189C}.Release|x86.Build.0 = Release|Any CPU
{9DDA917D-B4DB-4AC1-9B9A-4ECF06A5ABA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9DDA917D-B4DB-4AC1-9B9A-4ECF06A5ABA1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9DDA917D-B4DB-4AC1-9B9A-4ECF06A5ABA1}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {9DDA917D-B4DB-4AC1-9B9A-4ECF06A5ABA1}.Debug|x64.Build.0 = Debug|Any CPU
+ {9DDA917D-B4DB-4AC1-9B9A-4ECF06A5ABA1}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {9DDA917D-B4DB-4AC1-9B9A-4ECF06A5ABA1}.Debug|x86.Build.0 = Debug|Any CPU
{9DDA917D-B4DB-4AC1-9B9A-4ECF06A5ABA1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9DDA917D-B4DB-4AC1-9B9A-4ECF06A5ABA1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9DDA917D-B4DB-4AC1-9B9A-4ECF06A5ABA1}.Release|x64.ActiveCfg = Release|Any CPU
+ {9DDA917D-B4DB-4AC1-9B9A-4ECF06A5ABA1}.Release|x64.Build.0 = Release|Any CPU
+ {9DDA917D-B4DB-4AC1-9B9A-4ECF06A5ABA1}.Release|x86.ActiveCfg = Release|Any CPU
+ {9DDA917D-B4DB-4AC1-9B9A-4ECF06A5ABA1}.Release|x86.Build.0 = Release|Any CPU
{26EE4C38-F06E-4E7A-A89C-908318BCB0DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{26EE4C38-F06E-4E7A-A89C-908318BCB0DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {26EE4C38-F06E-4E7A-A89C-908318BCB0DB}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {26EE4C38-F06E-4E7A-A89C-908318BCB0DB}.Debug|x64.Build.0 = Debug|Any CPU
+ {26EE4C38-F06E-4E7A-A89C-908318BCB0DB}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {26EE4C38-F06E-4E7A-A89C-908318BCB0DB}.Debug|x86.Build.0 = Debug|Any CPU
{26EE4C38-F06E-4E7A-A89C-908318BCB0DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{26EE4C38-F06E-4E7A-A89C-908318BCB0DB}.Release|Any CPU.Build.0 = Release|Any CPU
- {67383F6C-C9C3-4C5B-95D4-5C50C20EDD4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {67383F6C-C9C3-4C5B-95D4-5C50C20EDD4F}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {67383F6C-C9C3-4C5B-95D4-5C50C20EDD4F}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {67383F6C-C9C3-4C5B-95D4-5C50C20EDD4F}.Release|Any CPU.Build.0 = Release|Any CPU
- {8D1B01A7-BF9E-44E6-88E1-B17255728B42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {8D1B01A7-BF9E-44E6-88E1-B17255728B42}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {8D1B01A7-BF9E-44E6-88E1-B17255728B42}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {8D1B01A7-BF9E-44E6-88E1-B17255728B42}.Release|Any CPU.Build.0 = Release|Any CPU
- {E2DC8FF6-D39E-4FCA-9549-1F7681F0AA81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {E2DC8FF6-D39E-4FCA-9549-1F7681F0AA81}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {E2DC8FF6-D39E-4FCA-9549-1F7681F0AA81}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {E2DC8FF6-D39E-4FCA-9549-1F7681F0AA81}.Release|Any CPU.Build.0 = Release|Any CPU
- {EB15435B-7147-4BD9-BB9C-B0A14318D3D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {EB15435B-7147-4BD9-BB9C-B0A14318D3D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {EB15435B-7147-4BD9-BB9C-B0A14318D3D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {EB15435B-7147-4BD9-BB9C-B0A14318D3D5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {26EE4C38-F06E-4E7A-A89C-908318BCB0DB}.Release|x64.ActiveCfg = Release|Any CPU
+ {26EE4C38-F06E-4E7A-A89C-908318BCB0DB}.Release|x64.Build.0 = Release|Any CPU
+ {26EE4C38-F06E-4E7A-A89C-908318BCB0DB}.Release|x86.ActiveCfg = Release|Any CPU
+ {26EE4C38-F06E-4E7A-A89C-908318BCB0DB}.Release|x86.Build.0 = Release|Any CPU
{0E990C96-3808-450E-9349-A0C3262195ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0E990C96-3808-450E-9349-A0C3262195ED}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0E990C96-3808-450E-9349-A0C3262195ED}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {0E990C96-3808-450E-9349-A0C3262195ED}.Debug|x64.Build.0 = Debug|Any CPU
+ {0E990C96-3808-450E-9349-A0C3262195ED}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {0E990C96-3808-450E-9349-A0C3262195ED}.Debug|x86.Build.0 = Debug|Any CPU
{0E990C96-3808-450E-9349-A0C3262195ED}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0E990C96-3808-450E-9349-A0C3262195ED}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0E990C96-3808-450E-9349-A0C3262195ED}.Release|x64.ActiveCfg = Release|Any CPU
+ {0E990C96-3808-450E-9349-A0C3262195ED}.Release|x64.Build.0 = Release|Any CPU
+ {0E990C96-3808-450E-9349-A0C3262195ED}.Release|x86.ActiveCfg = Release|Any CPU
+ {0E990C96-3808-450E-9349-A0C3262195ED}.Release|x86.Build.0 = Release|Any CPU
{DBE06954-EC85-4034-9049-72C7D27556DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DBE06954-EC85-4034-9049-72C7D27556DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DBE06954-EC85-4034-9049-72C7D27556DF}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {DBE06954-EC85-4034-9049-72C7D27556DF}.Debug|x64.Build.0 = Debug|Any CPU
+ {DBE06954-EC85-4034-9049-72C7D27556DF}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {DBE06954-EC85-4034-9049-72C7D27556DF}.Debug|x86.Build.0 = Debug|Any CPU
{DBE06954-EC85-4034-9049-72C7D27556DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DBE06954-EC85-4034-9049-72C7D27556DF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DBE06954-EC85-4034-9049-72C7D27556DF}.Release|x64.ActiveCfg = Release|Any CPU
+ {DBE06954-EC85-4034-9049-72C7D27556DF}.Release|x64.Build.0 = Release|Any CPU
+ {DBE06954-EC85-4034-9049-72C7D27556DF}.Release|x86.ActiveCfg = Release|Any CPU
+ {DBE06954-EC85-4034-9049-72C7D27556DF}.Release|x86.Build.0 = Release|Any CPU
+ {67383F6C-C9C3-4C5B-95D4-5C50C20EDD4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {67383F6C-C9C3-4C5B-95D4-5C50C20EDD4F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {67383F6C-C9C3-4C5B-95D4-5C50C20EDD4F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {67383F6C-C9C3-4C5B-95D4-5C50C20EDD4F}.Debug|x64.Build.0 = Debug|Any CPU
+ {67383F6C-C9C3-4C5B-95D4-5C50C20EDD4F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {67383F6C-C9C3-4C5B-95D4-5C50C20EDD4F}.Debug|x86.Build.0 = Debug|Any CPU
+ {67383F6C-C9C3-4C5B-95D4-5C50C20EDD4F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {67383F6C-C9C3-4C5B-95D4-5C50C20EDD4F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {67383F6C-C9C3-4C5B-95D4-5C50C20EDD4F}.Release|x64.ActiveCfg = Release|Any CPU
+ {67383F6C-C9C3-4C5B-95D4-5C50C20EDD4F}.Release|x64.Build.0 = Release|Any CPU
+ {67383F6C-C9C3-4C5B-95D4-5C50C20EDD4F}.Release|x86.ActiveCfg = Release|Any CPU
+ {67383F6C-C9C3-4C5B-95D4-5C50C20EDD4F}.Release|x86.Build.0 = Release|Any CPU
+ {8D1B01A7-BF9E-44E6-88E1-B17255728B42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8D1B01A7-BF9E-44E6-88E1-B17255728B42}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8D1B01A7-BF9E-44E6-88E1-B17255728B42}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {8D1B01A7-BF9E-44E6-88E1-B17255728B42}.Debug|x64.Build.0 = Debug|Any CPU
+ {8D1B01A7-BF9E-44E6-88E1-B17255728B42}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {8D1B01A7-BF9E-44E6-88E1-B17255728B42}.Debug|x86.Build.0 = Debug|Any CPU
+ {8D1B01A7-BF9E-44E6-88E1-B17255728B42}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8D1B01A7-BF9E-44E6-88E1-B17255728B42}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8D1B01A7-BF9E-44E6-88E1-B17255728B42}.Release|x64.ActiveCfg = Release|Any CPU
+ {8D1B01A7-BF9E-44E6-88E1-B17255728B42}.Release|x64.Build.0 = Release|Any CPU
+ {8D1B01A7-BF9E-44E6-88E1-B17255728B42}.Release|x86.ActiveCfg = Release|Any CPU
+ {8D1B01A7-BF9E-44E6-88E1-B17255728B42}.Release|x86.Build.0 = Release|Any CPU
+ {E2DC8FF6-D39E-4FCA-9549-1F7681F0AA81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E2DC8FF6-D39E-4FCA-9549-1F7681F0AA81}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E2DC8FF6-D39E-4FCA-9549-1F7681F0AA81}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {E2DC8FF6-D39E-4FCA-9549-1F7681F0AA81}.Debug|x64.Build.0 = Debug|Any CPU
+ {E2DC8FF6-D39E-4FCA-9549-1F7681F0AA81}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {E2DC8FF6-D39E-4FCA-9549-1F7681F0AA81}.Debug|x86.Build.0 = Debug|Any CPU
+ {E2DC8FF6-D39E-4FCA-9549-1F7681F0AA81}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E2DC8FF6-D39E-4FCA-9549-1F7681F0AA81}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E2DC8FF6-D39E-4FCA-9549-1F7681F0AA81}.Release|x64.ActiveCfg = Release|Any CPU
+ {E2DC8FF6-D39E-4FCA-9549-1F7681F0AA81}.Release|x64.Build.0 = Release|Any CPU
+ {E2DC8FF6-D39E-4FCA-9549-1F7681F0AA81}.Release|x86.ActiveCfg = Release|Any CPU
+ {E2DC8FF6-D39E-4FCA-9549-1F7681F0AA81}.Release|x86.Build.0 = Release|Any CPU
+ {EB15435B-7147-4BD9-BB9C-B0A14318D3D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EB15435B-7147-4BD9-BB9C-B0A14318D3D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EB15435B-7147-4BD9-BB9C-B0A14318D3D5}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {EB15435B-7147-4BD9-BB9C-B0A14318D3D5}.Debug|x64.Build.0 = Debug|Any CPU
+ {EB15435B-7147-4BD9-BB9C-B0A14318D3D5}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {EB15435B-7147-4BD9-BB9C-B0A14318D3D5}.Debug|x86.Build.0 = Debug|Any CPU
+ {EB15435B-7147-4BD9-BB9C-B0A14318D3D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EB15435B-7147-4BD9-BB9C-B0A14318D3D5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EB15435B-7147-4BD9-BB9C-B0A14318D3D5}.Release|x64.ActiveCfg = Release|Any CPU
+ {EB15435B-7147-4BD9-BB9C-B0A14318D3D5}.Release|x64.Build.0 = Release|Any CPU
+ {EB15435B-7147-4BD9-BB9C-B0A14318D3D5}.Release|x86.ActiveCfg = Release|Any CPU
+ {EB15435B-7147-4BD9-BB9C-B0A14318D3D5}.Release|x86.Build.0 = Release|Any CPU
+ {94C086DA-3359-424B-9688-29A198806481}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {94C086DA-3359-424B-9688-29A198806481}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {94C086DA-3359-424B-9688-29A198806481}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {94C086DA-3359-424B-9688-29A198806481}.Debug|x64.Build.0 = Debug|Any CPU
+ {94C086DA-3359-424B-9688-29A198806481}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {94C086DA-3359-424B-9688-29A198806481}.Debug|x86.Build.0 = Debug|Any CPU
+ {94C086DA-3359-424B-9688-29A198806481}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {94C086DA-3359-424B-9688-29A198806481}.Release|Any CPU.Build.0 = Release|Any CPU
+ {94C086DA-3359-424B-9688-29A198806481}.Release|x64.ActiveCfg = Release|Any CPU
+ {94C086DA-3359-424B-9688-29A198806481}.Release|x64.Build.0 = Release|Any CPU
+ {94C086DA-3359-424B-9688-29A198806481}.Release|x86.ActiveCfg = Release|Any CPU
+ {94C086DA-3359-424B-9688-29A198806481}.Release|x86.Build.0 = Release|Any CPU
+ {C95A9A21-34FA-4D82-BE5B-88FBDA836D31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C95A9A21-34FA-4D82-BE5B-88FBDA836D31}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C95A9A21-34FA-4D82-BE5B-88FBDA836D31}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C95A9A21-34FA-4D82-BE5B-88FBDA836D31}.Debug|x64.Build.0 = Debug|Any CPU
+ {C95A9A21-34FA-4D82-BE5B-88FBDA836D31}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C95A9A21-34FA-4D82-BE5B-88FBDA836D31}.Debug|x86.Build.0 = Debug|Any CPU
+ {C95A9A21-34FA-4D82-BE5B-88FBDA836D31}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C95A9A21-34FA-4D82-BE5B-88FBDA836D31}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C95A9A21-34FA-4D82-BE5B-88FBDA836D31}.Release|x64.ActiveCfg = Release|Any CPU
+ {C95A9A21-34FA-4D82-BE5B-88FBDA836D31}.Release|x64.Build.0 = Release|Any CPU
+ {C95A9A21-34FA-4D82-BE5B-88FBDA836D31}.Release|x86.ActiveCfg = Release|Any CPU
+ {C95A9A21-34FA-4D82-BE5B-88FBDA836D31}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{72AEDD42-E881-4EB7-99A8-FD3B9667189C} = {E733CBFF-42AE-4B6A-B5B9-0071ECFB45F1}
{9DDA917D-B4DB-4AC1-9B9A-4ECF06A5ABA1} = {E733CBFF-42AE-4B6A-B5B9-0071ECFB45F1}
{26EE4C38-F06E-4E7A-A89C-908318BCB0DB} = {E733CBFF-42AE-4B6A-B5B9-0071ECFB45F1}
+ {0E990C96-3808-450E-9349-A0C3262195ED} = {E733CBFF-42AE-4B6A-B5B9-0071ECFB45F1}
+ {DBE06954-EC85-4034-9049-72C7D27556DF} = {E733CBFF-42AE-4B6A-B5B9-0071ECFB45F1}
{67383F6C-C9C3-4C5B-95D4-5C50C20EDD4F} = {E733CBFF-42AE-4B6A-B5B9-0071ECFB45F1}
{8D1B01A7-BF9E-44E6-88E1-B17255728B42} = {E733CBFF-42AE-4B6A-B5B9-0071ECFB45F1}
{E2DC8FF6-D39E-4FCA-9549-1F7681F0AA81} = {39D316AB-ABB5-49A2-8E83-CAF4C75B48F4}
{EB15435B-7147-4BD9-BB9C-B0A14318D3D5} = {39D316AB-ABB5-49A2-8E83-CAF4C75B48F4}
- {0E990C96-3808-450E-9349-A0C3262195ED} = {E733CBFF-42AE-4B6A-B5B9-0071ECFB45F1}
- {DBE06954-EC85-4034-9049-72C7D27556DF} = {E733CBFF-42AE-4B6A-B5B9-0071ECFB45F1}
+ {94C086DA-3359-424B-9688-29A198806481} = {E733CBFF-42AE-4B6A-B5B9-0071ECFB45F1}
+ {C95A9A21-34FA-4D82-BE5B-88FBDA836D31} = {39D316AB-ABB5-49A2-8E83-CAF4C75B48F4}
EndGlobalSection
EndGlobal
diff --git a/README.md b/README.md
index ae2887ee..b40a1b52 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,7 @@ The libraries cover three main areas:
- a typed in-memory cache with pluggable persistence
- LiteNetLib-based networking with encrypted credential exchange and signed session tokens
- CultMesh distributed realtime database and simulation-consensus primitives
+- typed geometry domain/chunk documents for distributed CSG and LOD streaming
- declarative Unity UI composition and reflective runtime inspector tooling
## Which Package Do I Want?
@@ -18,12 +19,15 @@ and pretending the label makes it furniture.
| Job | Start With | Owns | Use When | Do Not Use It For |
| --- | --- | --- | --- | --- |
| Local typed state | `GameCult.Caching` / CultCache | Document identity, schema compatibility, record keys, indexes, globals, and local persistence | You need a typed cache, file-compatible save data, local reactive reads, or a stable domain document model | Peer discovery, transport security, shard routing, or mesh consensus |
+| Procedural geometry state | `GameCult.Geometry` | CultCache-native domain trees, LOD build requests, selected-cut diagnostics, and chunk artifact payloads | Rust, Unity, or remote workers need to share CSG/LOD geometry and graph metadata as typed state | Transport policy, peer discovery, or gameplay authority |
| Network transport and database plumbing | `GameCult.Networking` / CultNet | LiteNetLib transport, authentication, schema-v0 wire contracts, shard authority, raw document replication, snapshots, and subscriptions | You need a client/server pipe, login/session flow, schema discovery, or a low-level distributed CultCache lane | Gameplay-facing mesh ergonomics, Verse policy, mod branches, or simulation consensus composition |
| Distributed realtime gameplay state | `GameCult.Mesh` / CultMesh | Public mesh entrypoints, Verse discovery, peer exchange, shard replication defaults, authority leases, client prediction, and witness consensus | You want the game to treat clients and servers as one reactive database for persistent state, input state, and simulation facts | A tiny local-only tool, a bare transport client, or a storage format contract |
Quick rule:
- Choose CultCache when the problem is "how do I model and persist typed state?"
+- Choose GameCult.Geometry when the problem is "how do geometry workers share
+ domain trees, LOD build requests, and mesh chunks as typed state?"
- Choose CultNet when the problem is "how do peers exchange authenticated,
schema-aware database messages?"
- Choose CultMesh when the problem is "how does a game join a Verse and share
@@ -51,6 +55,7 @@ The solution includes:
- `GameCult.Caching.NewtonsoftJson`: Newtonsoft.Json-backed persistence for the cache
- `GameCult.Caching.MessagePack.Generator`: source generator for MessagePack formatters for cache models
- `GameCult.Caching.MessagePack.Analyzers`: packaging project that delivers the generator to consuming projects
+- `GameCult.Geometry`: CultCache-native geometry domain, selected-cut, and chunk artifact documents for VibeGeometry/Fensalir-style pipelines
- `GameCult.Networking`: encrypted login/register/verify flows and message dispatch over LiteNetLib
- `GameCult.Mesh`: CultMesh package home for distributed realtime database, shard replication, client prediction, Verse discovery, and mesh witness consensus
- `GameCult.Caching.Tests`: NUnit tests for cache and backing-store behavior
@@ -67,11 +72,13 @@ src/
GameCult.Caching.NewtonsoftJson/
GameCult.Caching.MessagePack.Generator/
GameCult.Caching.MessagePack.Analyzers/
+ GameCult.Geometry/
GameCult.Networking/
GameCult.Mesh/
GameCult.Unity/
tests/
GameCult.Caching.Tests/
+ GameCult.Geometry.Tests/
GameCult.Networking.Tests/
```
diff --git a/src/GameCult.Geometry/CultGeometryDocuments.cs b/src/GameCult.Geometry/CultGeometryDocuments.cs
new file mode 100644
index 00000000..cd8d6cdb
--- /dev/null
+++ b/src/GameCult.Geometry/CultGeometryDocuments.cs
@@ -0,0 +1,435 @@
+using System;
+using System.Globalization;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+using GameCult.Caching;
+using MessagePack;
+using static GameCult.Geometry.CultGeometryDocuments;
+
+namespace GameCult.Geometry
+{
+ ///
+ /// Schema versions for CultCache-native geometry pipeline documents.
+ ///
+ public static class CultGeometrySchemaVersions
+ {
+ public const string Domain = "gamecult.geometry.domain.v1";
+ public const string BuildRequest = "gamecult.geometry.build_request.v1";
+ public const string SelectedCut = "gamecult.geometry.selected_cut.v1";
+ public const string ChunkArtifact = "gamecult.geometry.chunk_artifact.v1";
+ }
+
+ ///
+ /// CultCache document containing a hierarchical geometry domain tree.
+ ///
+ [MessagePackObject]
+ [CultDocument("gamecult.geometry.domain", CultGeometrySchemaVersions.Domain)]
+ public sealed class CultGeometryDomainDocument
+ {
+ [Key(0)]
+ [CultName]
+ public string DomainId { get; set; } = string.Empty;
+
+ [Key(1)]
+ [CultIndex]
+ public string RootKey { get; set; } = string.Empty;
+
+ [Key(2)]
+ public string SourceRuntime { get; set; } = string.Empty;
+
+ [Key(3)]
+ public CultGeometryDomainNode Root { get; set; } = new CultGeometryDomainNode();
+
+ [Key(4)]
+ public string CreatedAt { get; set; } = string.Empty;
+
+ public static CultRecordKey CreateRecordKey(CultGeometryDomainDocument domain)
+ {
+ if (domain == null) throw new ArgumentNullException(nameof(domain));
+ return new CultRecordKey("geometry:domain:" + StableHash(
+ domain.RootKey,
+ domain.SourceRuntime,
+ domain.Root.StableFingerprint()));
+ }
+ }
+
+ ///
+ /// One node in a geometry domain tree.
+ ///
+ [MessagePackObject]
+ public sealed class CultGeometryDomainNode
+ {
+ [Key(0)]
+ public string Name { get; set; } = string.Empty;
+
+ [Key(1)]
+ public string Kind { get; set; } = string.Empty;
+
+ [Key(2)]
+ public float[] Translation { get; set; } = Array.Empty();
+
+ [Key(3)]
+ public float[] RotationXyzw { get; set; } = Array.Empty();
+
+ [Key(4)]
+ public ulong Seed { get; set; }
+
+ [Key(5)]
+ public CultGeometryFeatureClaim[] Claims { get; set; } = Array.Empty();
+
+ [Key(6)]
+ public CultGeometryDomainNode[] Children { get; set; } = Array.Empty();
+
+ public string StableFingerprint()
+ {
+ var builder = new StringBuilder()
+ .Append(Name).Append('\u001f')
+ .Append(Kind).Append('\u001f')
+ .Append(Seed.ToString(CultureInfo.InvariantCulture)).Append('\u001f')
+ .AppendVector(Translation).Append('\u001f')
+ .AppendVector(RotationXyzw);
+ foreach (var claim in Claims)
+ {
+ builder.Append('\u001e').Append(claim.StableFingerprint());
+ }
+
+ foreach (var child in Children)
+ {
+ builder.Append('\u001d').Append(child.StableFingerprint());
+ }
+
+ return builder.ToString();
+ }
+ }
+
+ ///
+ /// A domain feature claim before CSG lowering.
+ ///
+ [MessagePackObject]
+ public sealed class CultGeometryFeatureClaim
+ {
+ [Key(0)]
+ public string Name { get; set; } = string.Empty;
+
+ [Key(1)]
+ public float[] Translation { get; set; } = Array.Empty();
+
+ [Key(2)]
+ public float[] RotationXyzw { get; set; } = Array.Empty();
+
+ [Key(3)]
+ public float[] SupportCenter { get; set; } = Array.Empty();
+
+ [Key(4)]
+ public float[] SupportSize { get; set; } = Array.Empty();
+
+ [Key(5)]
+ public string Kind { get; set; } = string.Empty;
+
+ [Key(6)]
+ public uint Material { get; set; }
+
+ [Key(7)]
+ public string LoweringPolicy { get; set; } = string.Empty;
+
+ public string StableFingerprint() => new StringBuilder()
+ .Append(Name).Append('\u001f')
+ .Append(Kind).Append('\u001f')
+ .Append(Material.ToString(CultureInfo.InvariantCulture)).Append('\u001f')
+ .Append(LoweringPolicy).Append('\u001f')
+ .AppendVector(Translation).Append('\u001f')
+ .AppendVector(RotationXyzw).Append('\u001f')
+ .AppendVector(SupportCenter).Append('\u001f')
+ .AppendVector(SupportSize)
+ .ToString();
+ }
+
+ ///
+ /// CultCache document requesting one geometry LOD build.
+ ///
+ [MessagePackObject]
+ [CultDocument("gamecult.geometry.build_request", CultGeometrySchemaVersions.BuildRequest)]
+ public sealed class CultGeometryBuildRequest
+ {
+ [Key(0)]
+ [CultName]
+ public string RequestId { get; set; } = string.Empty;
+
+ [Key(1)]
+ [CultReference(typeof(CultGeometryDomainDocument))]
+ public string DomainKey { get; set; } = string.Empty;
+
+ [Key(2)]
+ [CultIndex]
+ public string WorkerGroup { get; set; } = string.Empty;
+
+ [Key(3)]
+ public float[] CameraPosition { get; set; } = Array.Empty();
+
+ [Key(4)]
+ public float[] FrustumMin { get; set; } = Array.Empty();
+
+ [Key(5)]
+ public float[] FrustumMax { get; set; } = Array.Empty();
+
+ [Key(6)]
+ public float ViewportHeightPixels { get; set; }
+
+ [Key(7)]
+ public float VerticalFovRadians { get; set; }
+
+ [Key(8)]
+ public float TargetError { get; set; }
+
+ [Key(9)]
+ public int TriangleBudget { get; set; }
+
+ [Key(10)]
+ public int ColliderBudget { get; set; }
+
+ [Key(11)]
+ public string[] SemanticFilter { get; set; } = Array.Empty();
+
+ [Key(12)]
+ public string[] RequestedChunkKeys { get; set; } = Array.Empty();
+
+ [Key(13)]
+ public string[] DirtyDomainKeys { get; set; } = Array.Empty();
+
+ [Key(14)]
+ public string CreatedAt { get; set; } = string.Empty;
+
+ public static CultRecordKey CreateRecordKey(CultGeometryBuildRequest request)
+ {
+ if (request == null) throw new ArgumentNullException(nameof(request));
+ return new CultRecordKey("geometry:request:" + StableHash(
+ request.DomainKey,
+ request.WorkerGroup,
+ StableVector(request.CameraPosition),
+ StableVector(request.FrustumMin),
+ StableVector(request.FrustumMax),
+ request.ViewportHeightPixels.ToString("R", CultureInfo.InvariantCulture),
+ request.VerticalFovRadians.ToString("R", CultureInfo.InvariantCulture),
+ request.TargetError.ToString("R", CultureInfo.InvariantCulture),
+ request.TriangleBudget.ToString(CultureInfo.InvariantCulture),
+ request.ColliderBudget.ToString(CultureInfo.InvariantCulture),
+ StableArray(request.SemanticFilter),
+ StableArray(request.RequestedChunkKeys),
+ StableArray(request.DirtyDomainKeys)));
+ }
+ }
+
+ ///
+ /// CultCache document describing the selected domain cut produced for one build request.
+ ///
+ [MessagePackObject]
+ [CultDocument("gamecult.geometry.selected_cut", CultGeometrySchemaVersions.SelectedCut)]
+ public sealed class CultGeometrySelectedCutManifest
+ {
+ [Key(0)]
+ [CultName]
+ public string CutId { get; set; } = string.Empty;
+
+ [Key(1)]
+ [CultReference(typeof(CultGeometryBuildRequest))]
+ public string RequestKey { get; set; } = string.Empty;
+
+ [Key(2)]
+ public string[] SelectedNodes { get; set; } = Array.Empty();
+
+ [Key(3)]
+ public string[] DeferredChildRequests { get; set; } = Array.Empty();
+
+ [Key(4)]
+ public string[] ParentFallbackNodes { get; set; } = Array.Empty();
+
+ [Key(5)]
+ public CultGeometryContributionRow[] Diagnostics { get; set; } = Array.Empty();
+
+ public static CultRecordKey CreateRecordKey(CultGeometrySelectedCutManifest cut)
+ {
+ if (cut == null) throw new ArgumentNullException(nameof(cut));
+ return new CultRecordKey("geometry:cut:" + StableHash(
+ cut.RequestKey,
+ cut.CutId,
+ StableArray(cut.SelectedNodes),
+ StableArray(cut.DeferredChildRequests),
+ StableArray(cut.ParentFallbackNodes)));
+ }
+ }
+
+ ///
+ /// One diagnostic row explaining selected-cut pressure.
+ ///
+ [MessagePackObject]
+ public sealed class CultGeometryContributionRow
+ {
+ [Key(0)]
+ public string DomainKey { get; set; } = string.Empty;
+
+ [Key(1)]
+ public string Kind { get; set; } = string.Empty;
+
+ [Key(2)]
+ public float Contribution { get; set; }
+
+ [Key(3)]
+ public float ProjectedScreenError { get; set; }
+
+ [Key(4)]
+ public float SemanticPriority { get; set; }
+
+ [Key(5)]
+ public int EstimatedTriangleCost { get; set; }
+
+ [Key(6)]
+ public int ChildCost { get; set; }
+
+ [Key(7)]
+ public int RemainingTriangleBudget { get; set; }
+
+ [Key(8)]
+ public bool Requested { get; set; }
+
+ [Key(9)]
+ public bool Dirty { get; set; }
+
+ [Key(10)]
+ public bool Selected { get; set; }
+
+ [Key(11)]
+ public bool UsedFallback { get; set; }
+
+ [Key(12)]
+ public bool DeferredByBudget { get; set; }
+ }
+
+ ///
+ /// CultCache document carrying one emitted geometry chunk and its collider payload.
+ ///
+ [MessagePackObject]
+ [CultDocument("gamecult.geometry.chunk_artifact", CultGeometrySchemaVersions.ChunkArtifact)]
+ public sealed class CultGeometryChunkArtifact
+ {
+ [Key(0)]
+ [CultName]
+ public string ChunkId { get; set; } = string.Empty;
+
+ [Key(1)]
+ [CultReference(typeof(CultGeometrySelectedCutManifest))]
+ public string CutKey { get; set; } = string.Empty;
+
+ [Key(2)]
+ [CultIndex]
+ public string SelectedCutId { get; set; } = string.Empty;
+
+ [Key(3)]
+ public float[] BoundsMin { get; set; } = Array.Empty();
+
+ [Key(4)]
+ public float[] BoundsMax { get; set; } = Array.Empty();
+
+ [Key(5)]
+ public string[] SourceDomainKeys { get; set; } = Array.Empty();
+
+ [Key(6)]
+ public string[] SourceClaimKeys { get; set; } = Array.Empty();
+
+ [Key(7)]
+ public CultGeometryTriangleMesh RenderMesh { get; set; } = new CultGeometryTriangleMesh();
+
+ [Key(8)]
+ public CultGeometryTriangleMesh? ColliderMesh { get; set; }
+
+ [Key(9)]
+ public int InputBrushes { get; set; }
+
+ [Key(10)]
+ public int CandidatePairs { get; set; }
+
+ [Key(11)]
+ public int RejectedPairs { get; set; }
+
+ [Key(12)]
+ public ulong StableClipSeed { get; set; }
+
+ [Key(13)]
+ public bool SupportsParentChildCoexistence { get; set; }
+
+ public static CultRecordKey CreateRecordKey(CultGeometryChunkArtifact chunk)
+ {
+ if (chunk == null) throw new ArgumentNullException(nameof(chunk));
+ return new CultRecordKey("geometry:chunk:" + StableHash(
+ chunk.CutKey,
+ chunk.ChunkId,
+ chunk.SelectedCutId,
+ StableArray(chunk.SourceDomainKeys),
+ StableArray(chunk.SourceClaimKeys),
+ chunk.RenderMesh.StableFingerprint(),
+ chunk.ColliderMesh?.StableFingerprint() ?? string.Empty));
+ }
+ }
+
+ ///
+ /// Packed triangle mesh payload for CultCache persistence and CultNet replication.
+ ///
+ [MessagePackObject]
+ public sealed class CultGeometryTriangleMesh
+ {
+ [Key(0)]
+ public float[] Positions { get; set; } = Array.Empty();
+
+ [Key(1)]
+ public float[] Normals { get; set; } = Array.Empty();
+
+ [Key(2)]
+ public float[] Uvs { get; set; } = Array.Empty();
+
+ [Key(3)]
+ public uint[] Indices { get; set; } = Array.Empty();
+
+ [Key(4)]
+ public uint[] TriangleMaterials { get; set; } = Array.Empty();
+
+ [IgnoreMember]
+ public int TriangleCount => Indices.Length / 3;
+
+ public string StableFingerprint() => StableHash(
+ StableVector(Positions),
+ StableVector(Normals),
+ StableVector(Uvs),
+ string.Join(",", Indices.Select(value => value.ToString(CultureInfo.InvariantCulture))),
+ string.Join(",", TriangleMaterials.Select(value => value.ToString(CultureInfo.InvariantCulture))));
+ }
+
+ internal static class CultGeometryDocuments
+ {
+ public static string StableHash(params string[] parts)
+ {
+ using var sha = SHA256.Create();
+ var canonical = string.Join("\u001f", parts);
+ var bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(canonical));
+ return string.Concat(bytes.Select(value => value.ToString("x2")));
+ }
+
+ public static string StableVector(float[] values)
+ {
+ return string.Join(",", values.Select(value => value.ToString("R", CultureInfo.InvariantCulture)));
+ }
+
+ public static string StableArray(string[] values)
+ {
+ return string.Join("\u001e", values);
+ }
+ }
+
+ internal static class CultGeometryStringBuilderExtensions
+ {
+ public static StringBuilder AppendVector(this StringBuilder builder, float[] values)
+ {
+ builder.Append(StableVector(values));
+ return builder;
+ }
+ }
+}
diff --git a/src/GameCult.Geometry/GameCult.Geometry.csproj b/src/GameCult.Geometry/GameCult.Geometry.csproj
new file mode 100644
index 00000000..52f4eeb0
--- /dev/null
+++ b/src/GameCult.Geometry/GameCult.Geometry.csproj
@@ -0,0 +1,18 @@
+
+
+ Typed CultCache geometry domain and chunk artifact documents for VibeGeometry and Fensalir
+ GameCult.Geometry
+ game;geometry;csg;lod;cultcache;cultmesh;vibegeometry;fensalir
+ README.md
+ $(NoWarn);1591
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/GameCult.Geometry/README.md b/src/GameCult.Geometry/README.md
new file mode 100644
index 00000000..8142f93a
--- /dev/null
+++ b/src/GameCult.Geometry/README.md
@@ -0,0 +1,16 @@
+# GameCult.Geometry
+
+`GameCult.Geometry` defines CultCache-native documents for procedural geometry
+domains, LOD build requests, selected-cut manifests, and mesh chunk artifacts.
+
+The package is deliberately substrate-shaped:
+
+- CultCache owns document identity, schema compatibility, references, and local
+ persistence.
+- CultNet can distribute these documents through the normal raw document lane.
+- CultMesh can use them as worker-visible shared state for simulation and
+ geometry pipelines.
+- JSON is not a runtime transport here. It may publish schema descriptions at
+ the xeno boundary, but the live machine eats and emits typed CultCache
+ documents.
+
diff --git a/tests/GameCult.Geometry.Tests/GameCult.Geometry.Tests.csproj b/tests/GameCult.Geometry.Tests/GameCult.Geometry.Tests.csproj
new file mode 100644
index 00000000..f43c68c2
--- /dev/null
+++ b/tests/GameCult.Geometry.Tests/GameCult.Geometry.Tests.csproj
@@ -0,0 +1,19 @@
+
+
+ net10.0
+ false
+ GameCult.Geometry.Tests
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/GameCult.Geometry.Tests/GeometryDocumentTests.cs b/tests/GameCult.Geometry.Tests/GeometryDocumentTests.cs
new file mode 100644
index 00000000..7263ff63
--- /dev/null
+++ b/tests/GameCult.Geometry.Tests/GeometryDocumentTests.cs
@@ -0,0 +1,138 @@
+using FluentAssertions;
+using GameCult.Caching;
+using GameCult.Caching.MessagePack;
+using NUnit.Framework;
+
+namespace GameCult.Geometry.Tests
+{
+ public sealed class GeometryDocumentTests
+ {
+ [Test]
+ public void GeometryDocuments_Register_AsCultCacheSchemas()
+ {
+ var cache = new CultCache();
+
+ cache.Registry.GetRequired().SchemaVersion
+ .Should().Be(CultGeometrySchemaVersions.Domain);
+ cache.Registry.GetRequired().SchemaVersion
+ .Should().Be(CultGeometrySchemaVersions.BuildRequest);
+ cache.Registry.GetRequired().SchemaVersion
+ .Should().Be(CultGeometrySchemaVersions.SelectedCut);
+ cache.Registry.GetRequired().SchemaVersion
+ .Should().Be(CultGeometrySchemaVersions.ChunkArtifact);
+ }
+
+ [Test]
+ public void GeometryBuildRequest_UsesStableCultCacheKey()
+ {
+ var domain = SampleDomain();
+ var domainKey = CultGeometryDomainDocument.CreateRecordKey(domain);
+ var request = new CultGeometryBuildRequest
+ {
+ DomainKey = domainKey.Value,
+ WorkerGroup = "ragnarok-column-workers",
+ CameraPosition = [36f, -42f, 30f],
+ FrustumMin = [-75f, -75f, -30f],
+ FrustumMax = [75f, 75f, 120f],
+ ViewportHeightPixels = 1080f,
+ VerticalFovRadians = 1.0471976f,
+ TargetError = 0.01f,
+ TriangleBudget = 10_000,
+ ColliderBudget = 10_000,
+ SemanticFilter = [],
+ RequestedChunkKeys = [],
+ DirtyDomainKeys = []
+ };
+
+ var first = CultGeometryBuildRequest.CreateRecordKey(request);
+ var second = CultGeometryBuildRequest.CreateRecordKey(request);
+ request.TriangleBudget++;
+ var changed = CultGeometryBuildRequest.CreateRecordKey(request);
+
+ second.Should().Be(first);
+ changed.Should().NotBe(first);
+ first.Value.Should().StartWith("geometry:request:");
+ }
+
+ [Test]
+ public void ChunkArtifact_RoundTrips_ThroughCultCacheMessagePack()
+ {
+ var chunk = new CultGeometryChunkArtifact
+ {
+ ChunkId = "chunk/ragnarok-column/column-00",
+ CutKey = "geometry:cut:test",
+ SelectedCutId = "cut-test",
+ BoundsMin = [-1f, -1f, 0f],
+ BoundsMax = [1f, 1f, 1f],
+ SourceDomainKeys = ["ragnarok-column/stellarator-column-00"],
+ SourceClaimKeys = ["ragnarok-column/stellarator-column-00/claim/support"],
+ RenderMesh = new CultGeometryTriangleMesh
+ {
+ Positions = [0f, 0f, 0f, 1f, 0f, 0f, 0f, 1f, 0f],
+ Normals = [0f, 0f, 1f, 0f, 0f, 1f, 0f, 0f, 1f],
+ Uvs = [0f, 0f, 1f, 0f, 0f, 1f],
+ Indices = [0u, 1u, 2u],
+ TriangleMaterials = [20u]
+ },
+ ColliderMesh = null,
+ InputBrushes = 1,
+ StableClipSeed = 1234,
+ SupportsParentChildCoexistence = true
+ };
+
+ var payload = CultDocumentMessagePackSerialization.Serialize(chunk);
+ var decoded = CultDocumentMessagePackSerialization.Deserialize(payload);
+
+ decoded.ChunkId.Should().Be(chunk.ChunkId);
+ decoded.RenderMesh.TriangleCount.Should().Be(1);
+ CultGeometryChunkArtifact.CreateRecordKey(decoded)
+ .Should().Be(CultGeometryChunkArtifact.CreateRecordKey(chunk));
+ }
+
+ private static CultGeometryDomainDocument SampleDomain()
+ {
+ return new CultGeometryDomainDocument
+ {
+ DomainId = "ragnarok-column",
+ RootKey = "ragnarok-column",
+ SourceRuntime = "vg-csg",
+ CreatedAt = "2026-05-29T00:00:00.0000000Z",
+ Root = new CultGeometryDomainNode
+ {
+ Name = "ragnarok-column",
+ Kind = "Root",
+ Translation = [0f, 0f, 0f],
+ RotationXyzw = [0f, 0f, 0f, 1f],
+ Seed = 0x5EED,
+ Claims = [],
+ Children =
+ [
+ new CultGeometryDomainNode
+ {
+ Name = "stellarator-column-00",
+ Kind = "Column",
+ Translation = [0f, 0f, 0f],
+ RotationXyzw = [0f, 0f, 0f, 1f],
+ Seed = 0xC0110000,
+ Claims =
+ [
+ new CultGeometryFeatureClaim
+ {
+ Name = "column-support-shell",
+ Translation = [0f, 0f, 0f],
+ RotationXyzw = [0f, 0f, 0f, 1f],
+ SupportCenter = [0f, 0f, 45f],
+ SupportSize = [18f, 18f, 96f],
+ Kind = "SupportShell",
+ Material = 10,
+ LoweringPolicy = "RenderAndCollider"
+ }
+ ],
+ Children = []
+ }
+ ]
+ }
+ };
+ }
+ }
+}