diff --git a/AGRARIAN_DEVELOPMENT_ROADMAP.md b/AGRARIAN_DEVELOPMENT_ROADMAP.md index 00062f2..ad0a768 100644 --- a/AGRARIAN_DEVELOPMENT_ROADMAP.md +++ b/AGRARIAN_DEVELOPMENT_ROADMAP.md @@ -445,7 +445,11 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe - [x] Choose Ground Zero real-world 1 km x 1 km MVP tile. - [x] Choose MVP biome based on Ground Zero location. - [x] Link Ground Zero tile coordinates to real-world weather lookup coordinates. Added explicit Ground Zero `weather_lookup_metadata` with tile-center latitude `37.5925`, longitude `-122.4995`, Open-Meteo primary routing, NOAA/NWS fallback eligibility, manifest generation support, registry/schema documentation, and a verifier so future source-backed tiles use declared lookup metadata instead of one-off hard-coded coordinates. -- [~] Create playable test map. +- [x] Create playable test map. Updated the Ground Zero playable-map verifier + to validate the current real-terrain demo map instead of the deprecated flat + prototype labels: safe player start, nearby wood/fiber/water/fire/shelter/ + wildlife loop actors, map boundary, sky/weather controllers, and absence of + legacy flat-test placements. - [x] Add terrain base from real elevation data. - [x] Add first-pass water depth/shoreline handling if applicable. - [x] Add first-pass hill, mountain, river, stream, lake, and coastline handling if present in Ground Zero. @@ -455,7 +459,10 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe - [x] Verify terrain tile origin and centered Unreal bounds for the Ground Zero test map. - [x] Verify neighboring tile edge coordinates against the registry before multi-tile stitching. - [x] Add foliage pass. -- [~] Add resource nodes. +- [x] Add resource nodes. Locked the Ground Zero wood, fiber, edible-plant, + and stone node pass with verification for stable node IDs, yield item IDs, + remaining harvests, and readable first-pass resource material assignments so + the MVP map proves both gameplay function and investor-facing readability. - [x] Add biome-appropriate natural resources based on Ground Zero. - [x] Add water source. - [x] Replace grey-box environment presentation with an MVP natural environment pass: terrain material, grass, shrubs, bushes, trees, water-source visuals, and clearer Ground Zero biome dressing. Added repeatable Ground Zero environment material generation, applied terrain/foliage/resource/water materials in the map setup, regenerated the demo map, documented the pass, and added verification for material assets plus map assignments. diff --git a/Docs/Terrain/GroundZeroNaturalEnvironmentPass.md b/Docs/Terrain/GroundZeroNaturalEnvironmentPass.md index ed3f469..797382f 100644 --- a/Docs/Terrain/GroundZeroNaturalEnvironmentPass.md +++ b/Docs/Terrain/GroundZeroNaturalEnvironmentPass.md @@ -38,6 +38,7 @@ space. - `/Game/Agrarian/Materials/M_AGR_GZ_Grass_DryCoastal` - `/Game/Agrarian/Materials/M_AGR_GZ_Wood_Resource` - `/Game/Agrarian/Materials/M_AGR_GZ_Fiber_Resource` +- `/Game/Agrarian/Materials/M_AGR_GZ_EdiblePlant_Resource` - `/Game/Agrarian/Materials/M_AGR_GZ_Stone_Sandstone` - `/Game/Agrarian/Materials/M_AGR_GZ_FreshWater` diff --git a/Docs/Terrain/GroundZeroResourcePass.md b/Docs/Terrain/GroundZeroResourcePass.md index a6f4672..d2350b5 100644 --- a/Docs/Terrain/GroundZeroResourcePass.md +++ b/Docs/Terrain/GroundZeroResourcePass.md @@ -24,7 +24,8 @@ the survival component. - Updated `Scripts/setup_ground_zero_demo_map.py` to place deterministic Ground Zero resource nodes. - Added `Scripts/verify_ground_zero_resources.py` to validate node presence, - yield item IDs, and remaining harvests. + yield item IDs, remaining harvests, stable persistence IDs, and readable + first-pass resource material assignments. - Freshwater placement and interaction are covered by `Scripts/verify_ground_zero_water_source.py` and `Scripts/verify_water_gathering_interaction.py`. diff --git a/Scripts/verify_ground_zero_natural_environment_pass.py b/Scripts/verify_ground_zero_natural_environment_pass.py index 180d0b2..a39d930 100644 --- a/Scripts/verify_ground_zero_natural_environment_pass.py +++ b/Scripts/verify_ground_zero_natural_environment_pass.py @@ -10,6 +10,7 @@ MATERIALS = { "grass": "/Game/Agrarian/Materials/M_AGR_GZ_Grass_DryCoastal", "wood_resource": "/Game/Agrarian/Materials/M_AGR_GZ_Wood_Resource", "fiber_resource": "/Game/Agrarian/Materials/M_AGR_GZ_Fiber_Resource", + "edible_plant_resource": "/Game/Agrarian/Materials/M_AGR_GZ_EdiblePlant_Resource", "stone_resource": "/Game/Agrarian/Materials/M_AGR_GZ_Stone_Sandstone", "fresh_water": "/Game/Agrarian/Materials/M_AGR_GZ_FreshWater", } @@ -21,6 +22,7 @@ EXPECTED_FOLIAGE_COUNTS = { RESOURCE_MATERIALS = { "AGR_GZ_Wood": "wood_resource", "AGR_GZ_Fiber": "fiber_resource", + "AGR_GZ_EdiblePlant": "edible_plant_resource", "AGR_GZ_Stone": "stone_resource", "AGR_DemoWoodResource": "wood_resource", "AGR_DemoFiberResource": "fiber_resource", diff --git a/Scripts/verify_ground_zero_resources.py b/Scripts/verify_ground_zero_resources.py index 01fbea0..6c0f445 100644 --- a/Scripts/verify_ground_zero_resources.py +++ b/Scripts/verify_ground_zero_resources.py @@ -2,6 +2,12 @@ import unreal MAP_PATH = "/Game/Agrarian/Maps/L_GroundZeroTerrain_Test" +MATERIALS = { + "wood": "/Game/Agrarian/Materials/M_AGR_GZ_Wood_Resource", + "fiber": "/Game/Agrarian/Materials/M_AGR_GZ_Fiber_Resource", + "food": "/Game/Agrarian/Materials/M_AGR_GZ_EdiblePlant_Resource", + "stone": "/Game/Agrarian/Materials/M_AGR_GZ_Stone_Sandstone", +} EXPECTED_RESOURCE_LABELS = { "wood": [ @@ -31,6 +37,12 @@ EXPECTED_RESOURCE_LABELS = { } +def material_path(material): + if not material: + return "" + return material.get_path_name().split(".", 1)[0] + + def get_actor_label(actor): try: return actor.get_actor_label() @@ -48,6 +60,10 @@ def main(): if not unreal.EditorLevelLibrary.load_level(MAP_PATH): raise RuntimeError(f"Could not load map: {MAP_PATH}") + for path in MATERIALS.values(): + if not unreal.EditorAssetLibrary.load_asset(path): + raise RuntimeError(f"Missing required resource material: {path}") + actors_by_label = {get_actor_label(actor): actor for actor in unreal.EditorLevelLibrary.get_all_level_actors()} failures = [] @@ -69,6 +85,14 @@ def main(): if persistence_node_id != label: failures.append(f"{label} persistence node id expected {label}, got {persistence_node_id}") + mesh_components = actor.get_components_by_class(unreal.StaticMeshComponent) + if not mesh_components: + failures.append(f"{label} has no static mesh component for MVP resource readability") + continue + expected_material = MATERIALS[expected_item_id] + if not any(material_path(component.get_material(0)) == expected_material for component in mesh_components): + failures.append(f"{label} expected readable material {expected_material}") + if failures: raise RuntimeError("Ground Zero resource verification failed: " + "; ".join(failures)) diff --git a/Scripts/verify_playable_loop_smoke.py b/Scripts/verify_playable_loop_smoke.py index fb863ef..b9ee3f1 100644 --- a/Scripts/verify_playable_loop_smoke.py +++ b/Scripts/verify_playable_loop_smoke.py @@ -8,9 +8,9 @@ FRAME_RECIPE_PATH = "/Game/Agrarian/DataAssets/Recipes/DA_Recipe_PrimitiveFrame" WALL_PANEL_RECIPE_PATH = "/Game/Agrarian/DataAssets/Recipes/DA_Recipe_PrimitiveWallPanel" ROOF_PANEL_RECIPE_PATH = "/Game/Agrarian/DataAssets/Recipes/DA_Recipe_PrimitiveRoofPanel" SHELTER_CLASS_PATH = "/Game/Agrarian/Blueprints/Structures/BP_PrimitiveShelter" -WOOD_NODE_LABEL = "AGR_WoodResourceNode_01" -FIBER_NODE_LABEL = "AGR_FiberResourceNode_01" -RABBIT_LABEL = "AGR_RabbitWildlife_01" +WOOD_NODE_LABEL = "AGR_DemoWoodResource_01" +FIBER_NODE_LABEL = "AGR_DemoFiberResource_01" +RABBIT_LABEL = "AGR_DemoRabbitWildlife_01" def get_actor_label(actor): diff --git a/Scripts/verify_test_map_placements.py b/Scripts/verify_test_map_placements.py index a817cc0..1d25f56 100644 --- a/Scripts/verify_test_map_placements.py +++ b/Scripts/verify_test_map_placements.py @@ -3,46 +3,82 @@ import unreal MAP_PATH = "/Game/Agrarian/Maps/L_GroundZeroTerrain_Test" -EXPECTED_PLACEMENTS = { - "AGR_WoodResourceNode_01": { +EXPECTED_PLAYABLE_ACTORS = { + "AGR_DemoPlayerStart": { + "class": unreal.PlayerStart, + }, + "AGR_DemoWoodResource_01": { "class_path": "/Game/Agrarian/Blueprints/Resources/BP_WoodResourceNode", - "location": unreal.Vector(650.0, -150.0, 120.0), "properties": { "remaining_harvests": 16, "quantity_per_harvest": 2, + "persistence_node_id": "AGR_DemoWoodResource_01", }, }, - "AGR_FiberResourceNode_01": { + "AGR_DemoFiberResource_01": { "class_path": "/Game/Agrarian/Blueprints/Resources/BP_FiberResourceNode", - "location": unreal.Vector(560.0, 140.0, 90.0), "properties": { "remaining_harvests": 10, "quantity_per_harvest": 3, + "persistence_node_id": "AGR_DemoFiberResource_01", }, }, - "AGR_Campfire_01": { + "AGR_DemoCampfire_01": { "class_path": "/Game/Agrarian/Blueprints/Structures/BP_Campfire", - "location": unreal.Vector(900.0, 120.0, 60.0), "properties": { "fuel_seconds": 180.0, "warmth_radius": 650.0, }, }, - "AGR_PrimitiveShelter_01": { + "AGR_DemoPrimitiveShelter_01": { "class_path": "/Game/Agrarian/Blueprints/Structures/BP_PrimitiveShelter", - "location": unreal.Vector(1200.0, -260.0, 140.0), "properties": { "weather_protection": 0.7, }, }, - "AGR_RabbitWildlife_01": { + "AGR_DemoRabbitWildlife_01": { "class_path": "/Game/Agrarian/Blueprints/Wildlife/BP_RabbitWildlife", - "location": unreal.Vector(450.0, 420.0, 100.0), "properties": { "wildlife_id": "rabbit", "max_health": 12.0, }, }, + "AGR_GZ_FreshWaterSource_01": { + "class_path": "/Game/Agrarian/Blueprints/World/BP_FreshWaterSource", + }, + "AGR_GroundZeroMapBoundary": { + "class": unreal.AgrarianMapBoundaryVolume, + "properties": { + "boundary_id": "ground_zero_mvp_tile", + "clamp_players_at_boundary": True, + }, + }, + "AGR_DemoSkyLightingController": { + "class": unreal.AgrarianSkyLightingController, + }, + "AGR_DemoWeatherAudioController": { + "class": unreal.AgrarianWeatherAudioController, + }, + "AGR_DemoNoticeActor": { + "class": unreal.AgrarianDemoNoticeActor, + }, +} + +LEGACY_FLAT_TEST_LABELS = { + "AGR_WoodResourceNode_01", + "AGR_FiberResourceNode_01", + "AGR_Campfire_01", + "AGR_PrimitiveShelter_01", + "AGR_RabbitWildlife_01", +} + +NEAR_SPAWN_ACTORS = { + "AGR_DemoWoodResource_01": 12000.0, + "AGR_DemoFiberResource_01": 12000.0, + "AGR_DemoCampfire_01": 12000.0, + "AGR_DemoPrimitiveShelter_01": 12000.0, + "AGR_DemoRabbitWildlife_01": 12000.0, + "AGR_GZ_FreshWaterSource_01": 25000.0, } @@ -50,12 +86,12 @@ def nearly_equal(left, right): return abs(float(left) - float(right)) < 0.001 -def vectors_close(left, right): - return ( - abs(left.x - right.x) < 0.1 - and abs(left.y - right.y) < 0.1 - and abs(left.z - right.z) < 0.1 - ) +def distance_2d(left, right): + return ((left.x - right.x) ** 2.0 + (left.y - right.y) ** 2.0) ** 0.5 + + +def class_path(actor): + return actor.get_class().get_path_name() def get_actor_label(actor): @@ -65,6 +101,18 @@ def get_actor_label(actor): return actor.get_name() +def verify_class(label, actor, expected, failures): + expected_class = expected.get("class") + if expected_class and not isinstance(actor, expected_class): + failures.append(f"{label} expected class {expected_class.__name__}, got {actor.get_class().get_name()}") + + expected_class_path = expected.get("class_path") + if expected_class_path: + actual_class_path = class_path(actor) + if not actual_class_path.startswith(expected_class_path + "."): + failures.append(f"{label} expected class under {expected_class_path}, got {actual_class_path}") + + def main(): if not unreal.EditorLevelLibrary.load_level(MAP_PATH): raise RuntimeError(f"Could not load map: {MAP_PATH}") @@ -72,28 +120,50 @@ def main(): failures = [] actors_by_label = {get_actor_label(actor): actor for actor in unreal.EditorLevelLibrary.get_all_level_actors()} - for label, expected in EXPECTED_PLACEMENTS.items(): + for legacy_label in LEGACY_FLAT_TEST_LABELS: + if legacy_label in actors_by_label: + failures.append(f"legacy flat-test actor should not remain in Ground Zero map: {legacy_label}") + + for label, expected in EXPECTED_PLAYABLE_ACTORS.items(): actor = actors_by_label.get(label) if not actor: failures.append(f"{label} missing") continue - actor_location = actor.get_actor_location() - if not vectors_close(actor_location, expected["location"]): - failures.append(f"{label} expected location {expected['location']}, got {actor_location}") + verify_class(label, actor, expected, failures) for property_name, expected_value in expected.get("properties", {}).items(): actual_value = actor.get_editor_property(property_name) if isinstance(expected_value, (int, float)): if not nearly_equal(actual_value, expected_value): failures.append(f"{label} {property_name} expected {expected_value}, got {actual_value}") + elif isinstance(expected_value, bool): + if bool(actual_value) != expected_value: + failures.append(f"{label} {property_name} expected {expected_value}, got {actual_value}") elif str(actual_value) != expected_value: failures.append(f"{label} {property_name} expected {expected_value}, got {actual_value}") + player_start = actors_by_label.get("AGR_DemoPlayerStart") + if player_start: + spawn_location = player_start.get_actor_location() + if spawn_location.z < 150.0: + failures.append(f"AGR_DemoPlayerStart expected safe above-terrain offset, got z {spawn_location.z}") + + for label, max_distance in NEAR_SPAWN_ACTORS.items(): + actor = actors_by_label.get(label) + if not actor: + continue + actual_distance = distance_2d(spawn_location, actor.get_actor_location()) + if actual_distance > max_distance: + failures.append(f"{label} is {actual_distance:.0f}cm from spawn, above {max_distance:.0f}cm playable radius") + if failures: raise RuntimeError("Test map placement verification failed: " + "; ".join(failures)) - unreal.log("Agrarian test map placement verification complete.") + unreal.log( + "Agrarian Ground Zero playable test map verification complete: " + f"{len(EXPECTED_PLAYABLE_ACTORS)} critical actors checked." + ) main()