diff --git a/AGRARIAN_DEVELOPMENT_ROADMAP.md b/AGRARIAN_DEVELOPMENT_ROADMAP.md index c2f869f..b74f1fe 100644 --- a/AGRARIAN_DEVELOPMENT_ROADMAP.md +++ b/AGRARIAN_DEVELOPMENT_ROADMAP.md @@ -718,7 +718,10 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe - [x] Add replicated weather. - [x] Add replicated resource nodes. - [x] Add replicated build pieces. -- [ ] Add network relevancy rules. +- [x] Add network relevancy rules. Added explicit MVP net cull distances for + pickups, resources, campfires, shelters, wildlife, water sources, weather + exposure zones, and wildlife spawn managers, with the matching relevancy + strategy documented for Ground Zero multiplayer tests. - [ ] Add basic latency testing. - [ ] Add disconnect/reconnect handling. diff --git a/Docs/MultiplayerNetworkingDesign.md b/Docs/MultiplayerNetworkingDesign.md index 01badf8..a7bb388 100644 --- a/Docs/MultiplayerNetworkingDesign.md +++ b/Docs/MultiplayerNetworkingDesign.md @@ -224,6 +224,18 @@ Near-term rules: - not replicated as gameplay actors: static terrain package files and large tile metadata payloads. +MVP actor cull distances: + +- item pickups: 30 meters; +- resource nodes: 45 meters; +- campfires, shelters, and wildlife: 60 meters; +- water sources and weather exposure zones: 65 meters; +- wildlife spawn managers: 80 meters. + +These are intentionally conservative prototype values. They prevent the first +Ground Zero multiplayer test from replicating every interactable to every +client while still keeping nearby survival objects responsive. + Future rules should align with World Partition, tile boundaries, settlement density, and player population. diff --git a/Scripts/verify_network_relevancy_rules.py b/Scripts/verify_network_relevancy_rules.py new file mode 100644 index 0000000..58a59f4 --- /dev/null +++ b/Scripts/verify_network_relevancy_rules.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +"""Validate MVP network relevancy rules.""" + +from pathlib import Path +import sys + +ROOT = Path(__file__).resolve().parents[1] + + +def read(relative_path: str) -> str: + path = ROOT / relative_path + if not path.exists(): + raise AssertionError(f"Missing required file: {relative_path}") + return path.read_text(encoding="utf-8") + + +def require(content: str, needle: str, context: str) -> None: + if needle not in content: + raise AssertionError(f"Missing {needle!r} in {context}") + + +def main() -> int: + errors: list[str] = [] + checks = { + "Source/AgrarianGame/AgrarianItemPickup.cpp": ["NetCullDistanceSquared = FMath::Square(3000.0f);"], + "Source/AgrarianGame/AgrarianResourceNode.cpp": ["NetCullDistanceSquared = FMath::Square(4500.0f);"], + "Source/AgrarianGame/AgrarianCampfire.cpp": ["NetCullDistanceSquared = FMath::Square(6000.0f);"], + "Source/AgrarianGame/AgrarianShelterActor.cpp": ["NetCullDistanceSquared = FMath::Square(6000.0f);"], + "Source/AgrarianGame/AgrarianWildlifeBase.cpp": ["NetCullDistanceSquared = FMath::Square(6000.0f);"], + "Source/AgrarianGame/AgrarianWaterSource.cpp": ["NetCullDistanceSquared = FMath::Square(6500.0f);"], + "Source/AgrarianGame/AgrarianWeatherExposureZone.cpp": ["NetCullDistanceSquared = FMath::Square(6500.0f);"], + "Source/AgrarianGame/AgrarianWildlifeSpawnManager.cpp": ["NetCullDistanceSquared = FMath::Square(8000.0f);"], + "Docs/MultiplayerNetworkingDesign.md": [ + "MVP actor cull distances", + "item pickups: 30 meters", + "resource nodes: 45 meters", + "campfires, shelters, and wildlife: 60 meters", + "water sources and weather exposure zones: 65 meters", + "wildlife spawn managers: 80 meters", + ], + "AGRARIAN_DEVELOPMENT_ROADMAP.md": [ + "[x] Add network relevancy rules.", + "explicit MVP net cull distances", + ], + } + + for relative_path, needles in checks.items(): + try: + content = read(relative_path) + for needle in needles: + require(content, needle, relative_path) + except AssertionError as exc: + errors.append(str(exc)) + + if errors: + for error in errors: + print(f"ERROR: {error}", file=sys.stderr) + return 1 + + print("PASS: MVP network relevancy rules are wired and documented.") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/Source/AgrarianGame/AgrarianCampfire.cpp b/Source/AgrarianGame/AgrarianCampfire.cpp index 7ba99e5..359652a 100644 --- a/Source/AgrarianGame/AgrarianCampfire.cpp +++ b/Source/AgrarianGame/AgrarianCampfire.cpp @@ -16,6 +16,7 @@ AAgrarianCampfire::AAgrarianCampfire() { PrimaryActorTick.bCanEverTick = true; bReplicates = true; + NetCullDistanceSquared = FMath::Square(6000.0f); Mesh = CreateDefaultSubobject(TEXT("Mesh")); RootComponent = Mesh; diff --git a/Source/AgrarianGame/AgrarianItemPickup.cpp b/Source/AgrarianGame/AgrarianItemPickup.cpp index 5ca6baf..72b60f2 100644 --- a/Source/AgrarianGame/AgrarianItemPickup.cpp +++ b/Source/AgrarianGame/AgrarianItemPickup.cpp @@ -9,6 +9,7 @@ AAgrarianItemPickup::AAgrarianItemPickup() { bReplicates = true; + NetCullDistanceSquared = FMath::Square(3000.0f); Mesh = CreateDefaultSubobject(TEXT("Mesh")); RootComponent = Mesh; diff --git a/Source/AgrarianGame/AgrarianResourceNode.cpp b/Source/AgrarianGame/AgrarianResourceNode.cpp index 8cb96c9..b75c1a6 100644 --- a/Source/AgrarianGame/AgrarianResourceNode.cpp +++ b/Source/AgrarianGame/AgrarianResourceNode.cpp @@ -12,6 +12,7 @@ AAgrarianResourceNode::AAgrarianResourceNode() { bReplicates = true; + NetCullDistanceSquared = FMath::Square(4500.0f); Mesh = CreateDefaultSubobject(TEXT("Mesh")); RootComponent = Mesh; diff --git a/Source/AgrarianGame/AgrarianShelterActor.cpp b/Source/AgrarianGame/AgrarianShelterActor.cpp index ad5b3a8..44d3135 100644 --- a/Source/AgrarianGame/AgrarianShelterActor.cpp +++ b/Source/AgrarianGame/AgrarianShelterActor.cpp @@ -9,6 +9,7 @@ AAgrarianShelterActor::AAgrarianShelterActor() { bReplicates = true; + NetCullDistanceSquared = FMath::Square(6000.0f); Mesh = CreateDefaultSubobject(TEXT("Mesh")); RootComponent = Mesh; diff --git a/Source/AgrarianGame/AgrarianWaterSource.cpp b/Source/AgrarianGame/AgrarianWaterSource.cpp index 804d19c..398c4aa 100644 --- a/Source/AgrarianGame/AgrarianWaterSource.cpp +++ b/Source/AgrarianGame/AgrarianWaterSource.cpp @@ -9,6 +9,7 @@ AAgrarianWaterSource::AAgrarianWaterSource() { bReplicates = true; + NetCullDistanceSquared = FMath::Square(6500.0f); Mesh = CreateDefaultSubobject(TEXT("Mesh")); RootComponent = Mesh; diff --git a/Source/AgrarianGame/AgrarianWeatherExposureZone.cpp b/Source/AgrarianGame/AgrarianWeatherExposureZone.cpp index 4f00346..1a8e1a2 100644 --- a/Source/AgrarianGame/AgrarianWeatherExposureZone.cpp +++ b/Source/AgrarianGame/AgrarianWeatherExposureZone.cpp @@ -6,6 +6,7 @@ AAgrarianWeatherExposureZone::AAgrarianWeatherExposureZone() { bReplicates = true; + NetCullDistanceSquared = FMath::Square(6500.0f); ExposureVolume = CreateDefaultSubobject(TEXT("ExposureVolume")); RootComponent = ExposureVolume; diff --git a/Source/AgrarianGame/AgrarianWildlifeBase.cpp b/Source/AgrarianGame/AgrarianWildlifeBase.cpp index 4679600..275171f 100644 --- a/Source/AgrarianGame/AgrarianWildlifeBase.cpp +++ b/Source/AgrarianGame/AgrarianWildlifeBase.cpp @@ -14,6 +14,7 @@ AAgrarianWildlifeBase::AAgrarianWildlifeBase() { PrimaryActorTick.bCanEverTick = true; bReplicates = true; + NetCullDistanceSquared = FMath::Square(6000.0f); AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned; AIControllerClass = AAIController::StaticClass(); diff --git a/Source/AgrarianGame/AgrarianWildlifeSpawnManager.cpp b/Source/AgrarianGame/AgrarianWildlifeSpawnManager.cpp index f34f8fe..7bad06b 100644 --- a/Source/AgrarianGame/AgrarianWildlifeSpawnManager.cpp +++ b/Source/AgrarianGame/AgrarianWildlifeSpawnManager.cpp @@ -9,6 +9,7 @@ AAgrarianWildlifeSpawnManager::AAgrarianWildlifeSpawnManager() { PrimaryActorTick.bCanEverTick = true; bReplicates = true; + NetCullDistanceSquared = FMath::Square(8000.0f); SetReplicatingMovement(false); }