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 = [] + } + ] + } + }; + } + } +}