Add Ground Zero water source visual gate

This commit is contained in:
2026-05-19 11:01:14 -07:00
parent be6486202c
commit d0c1e22d98
5 changed files with 198 additions and 6 deletions
+1 -1
View File
@@ -823,7 +823,7 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
- [x] Add first realistic playable character proxies for the selected young adult male and female archetypes, replacing the default mannequin/dummy presentation for investor builds. Added selected male/female MVP proxy application to the player controller, workwear material generation, cook coverage for Agrarian character assets, documentation, and verification so the menu-selected archetype changes the possessed pawn instead of leaving one default dummy presentation.
- [x] Replace box/sphere/cylinder survival objects with readable MVP meshes for campfires, primitive shelter pieces, resource pickups, water sources, wildlife, and gathered items. Added composed native proxy visuals for campfires, primitive shelters, pickups, resource nodes, water sources, and wildlife using cooked Agrarian placeholder mesh assets plus Ground Zero materials, with child visuals set to no collision so gameplay behavior remains unchanged while investor builds read as intentional objects instead of primitive debug shapes.
- [x] Replace the placeholder Ground Zero environment presentation with investor-facing biome dressing: believable terrain material, grass, brush, shrubs, bushes, trees, rocks, water visuals, and local coastal-scrub color variation. Upgraded the repeatable Ground Zero setup to require denser investor-facing foliage counts and twenty-three labeled variation actors covering trees, brush, shrubs, dry grass mats, rock slabs, water-bank pieces, reeds, and freshwater surface material variation, then extended the verifier/docs so the map no longer qualifies if the visual dressing falls back to sparse placeholder presentation.
- [ ] Add a real water-source visual pass with surface material, edge treatment, scale, and placement that reads as collectable freshwater instead of a placeholder plane.
- [x] Add a real water-source visual pass with surface material, edge treatment, scale, and placement that reads as collectable freshwater instead of a placeholder plane. Formalized the MVP freshwater presentation around the native `AAgrarianWaterSource` water-surface, stone-bank, and collect-marker proxies, documented the Ground Zero drainage-candidate placement and nearby water-bank/reed dressing, and added an Unreal verifier that checks surface material, edge treatment, scale, placement, and nearby dressing actors in the actual map.
- [ ] Add density and sightline tuning so grasses, shrubs, trees, and resource clusters are visible enough to sell the world without hiding gameplay-critical objects.
- [ ] Preserve realism as the target: use assets, materials, lighting, and environmental dressing that can survive toward MVP production rather than cosmetic throwaways where practical.
- [ ] Define default, recommended, and cinematic investor rendering presets, with ray tracing available only as an optional high-end/cinematic mode and never required for baseline visual credibility.
Binary file not shown.
@@ -0,0 +1,41 @@
# Ground Zero Water Source Visual Pass
The 0.1.O investor visual pass gives the MVP freshwater source an explicit
readable presentation instead of relying on a flat placeholder or a generic
pickup shape.
## Required Presentation
- The native `AAgrarianWaterSource` actor exposes a visible water surface proxy
using `/Game/Agrarian/Materials/M_AGR_GZ_FreshWater`.
- The same actor exposes stone bank/edge treatment using
`/Game/Agrarian/Materials/M_AGR_GZ_Stone_Sandstone`.
- A small collect marker remains on the actor so the source reads as gameplay
relevant without requiring tutorial text.
- The Ground Zero map keeps the freshwater source at the drainage-candidate
location near `(-7200, 10400)` so it remains reachable but not on top of the
safe spawn point.
- Nearby environmental dressing includes a visible water-surface plane,
stone-bank piece, and reed/grass pieces so the source reads as a small
collectable freshwater feature rather than an isolated debug primitive.
## Validation
`Scripts/verify_ground_zero_water_source_visual_pass.py` loads the Ground Zero
map and checks:
- exactly one `AGR_GZ_FreshWaterSource_01` actor exists;
- the actor is an `AAgrarianWaterSource`;
- the native `Mesh`, `WaterSurfaceProxy`, `StoneBankProxy`, and
`CollectMarkerProxy` components exist;
- the water surface uses the freshwater material and is scaled large enough to
be readable;
- the bank/root components use the sandstone material;
- the actor remains at the intended drainage-candidate placement;
- the nearby water surface, water bank, and reed variation actors are present
within a short radius of the source.
This pass is still MVP proxy art. Final water rendering should replace the
proxy meshes with production water material, bank geometry, wet edge blending,
sound, and collection animation without changing the gameplay interaction
contract.
+19
View File
@@ -723,6 +723,20 @@ def apply_material_to_actor_meshes(actor, material):
return applied_count
def apply_water_source_materials(actor, materials):
component_materials = {
"Mesh": materials["stone_resource"],
"StoneBankProxy": materials["stone_resource"],
"WaterSurfaceProxy": materials["fresh_water"],
"CollectMarkerProxy": materials["fresh_water"],
}
for component in actor.get_components_by_class(unreal.StaticMeshComponent):
material = component_materials.get(component.get_name())
if material:
component.set_material(0, material)
def material_key_for_actor_label(label):
for prefix, material_key in RESOURCE_MATERIAL_BY_LABEL_PREFIX.items():
if label.startswith(prefix):
@@ -976,9 +990,14 @@ def spawn_demo_actor(spec, height_values, materials, safe_spawn_location_xy=None
actor.set_editor_property("persistence_node_id", spec["label"])
except Exception:
pass
if spec["label"].startswith("AGR_GZ_FreshWaterSource"):
apply_water_source_materials(actor, materials)
else:
material_key = material_key_for_actor_label(spec["label"])
if material_key:
apply_material_to_actor_meshes(actor, materials[material_key])
unreal.log(f"Placed {spec['label']} at {actor.get_actor_location()}")
return actor
@@ -0,0 +1,132 @@
import math
from pathlib import Path
import unreal
MAP_PATH = "/Game/Agrarian/Maps/L_GroundZeroTerrain_Test"
WATER_SOURCE_LABEL = "AGR_GZ_FreshWaterSource_01"
EXPECTED_WATER_LOCATION = unreal.Vector(-7200.0, 10400.0, 0.0)
MAX_WATER_LOCATION_DISTANCE_CM = 500.0
MAX_DRESSING_DISTANCE_CM = 3500.0
FRESH_WATER_MATERIAL = "/Game/Agrarian/Materials/M_AGR_GZ_FreshWater"
STONE_MATERIAL = "/Game/Agrarian/Materials/M_AGR_GZ_Stone_Sandstone"
REQUIRED_COMPONENTS = {
"Mesh",
"WaterSurfaceProxy",
"StoneBankProxy",
"CollectMarkerProxy",
}
REQUIRED_NEARBY_DRESSING = {
"AGR_GZ_EnvVar_Water_Surface_01",
"AGR_GZ_EnvVar_Water_Bank_01",
"AGR_GZ_EnvVar_Water_Reed_01",
"AGR_GZ_EnvVar_Water_Reed_02",
}
def get_actor_label(actor):
try:
return actor.get_actor_label()
except Exception:
return actor.get_name()
def material_path(material):
if not material:
return ""
return material.get_path_name().split(".", 1)[0]
def distance_2d(a, b):
return math.hypot(a.x - b.x, a.y - b.y)
def component_by_name(actor, component_name):
for component in actor.get_components_by_class(unreal.StaticMeshComponent):
if component.get_name() == component_name:
return component
return None
def main():
if not unreal.EditorLevelLibrary.load_level(MAP_PATH):
raise RuntimeError(f"Could not load map: {MAP_PATH}")
actors = unreal.EditorLevelLibrary.get_all_level_actors()
failures = []
water_sources = [actor for actor in actors if get_actor_label(actor) == WATER_SOURCE_LABEL]
if len(water_sources) != 1:
raise RuntimeError(f"Expected exactly one {WATER_SOURCE_LABEL}, found {len(water_sources)}")
water_source = water_sources[0]
if not isinstance(water_source, unreal.AgrarianWaterSource):
failures.append(f"{WATER_SOURCE_LABEL} is not an AgrarianWaterSource")
components = {
component_name: component_by_name(water_source, component_name)
for component_name in REQUIRED_COMPONENTS
}
for component_name, component in components.items():
if component is None:
failures.append(f"{WATER_SOURCE_LABEL} missing {component_name}")
water_surface = components.get("WaterSurfaceProxy")
if water_surface:
if material_path(water_surface.get_material(0)) != FRESH_WATER_MATERIAL:
failures.append("WaterSurfaceProxy does not use the freshwater material")
scale = water_surface.get_editor_property("relative_scale3d")
if scale.x < 2.0 or scale.y < 1.3:
failures.append(f"WaterSurfaceProxy scale is too small: {scale}")
collect_marker = components.get("CollectMarkerProxy")
if collect_marker and material_path(collect_marker.get_material(0)) != FRESH_WATER_MATERIAL:
failures.append("CollectMarkerProxy does not use the freshwater material")
for component_name in ("Mesh", "StoneBankProxy"):
component = components.get(component_name)
if component and material_path(component.get_material(0)) != STONE_MATERIAL:
failures.append(f"{component_name} does not use the sandstone material")
water_location = water_source.get_actor_location()
if distance_2d(water_location, EXPECTED_WATER_LOCATION) > MAX_WATER_LOCATION_DISTANCE_CM:
failures.append(f"{WATER_SOURCE_LABEL} moved too far from drainage-candidate placement: {water_location}")
nearby_labels = {}
for actor in actors:
label = get_actor_label(actor)
if label in REQUIRED_NEARBY_DRESSING:
nearby_labels[label] = actor
missing_dressing = REQUIRED_NEARBY_DRESSING.difference(nearby_labels)
if missing_dressing:
failures.append("missing water dressing actors: " + ", ".join(sorted(missing_dressing)))
for label, actor in nearby_labels.items():
if distance_2d(water_location, actor.get_actor_location()) > MAX_DRESSING_DISTANCE_CM:
failures.append(f"{label} is too far from the freshwater source")
project_root = Path(unreal.Paths.convert_relative_path_to_full(unreal.Paths.project_dir()))
docs = project_root / "Docs" / "Terrain" / "GroundZeroWaterSourceVisualPass.md"
roadmap = project_root / "AGRARIAN_DEVELOPMENT_ROADMAP.md"
for path, snippets in {
docs: ["Ground Zero Water Source Visual Pass", "WaterSurfaceProxy", "stone bank/edge treatment"],
roadmap: ["[x] Add a real water-source visual pass", "surface material, edge treatment, scale, and placement"],
}.items():
text = path.read_text(encoding="utf-8")
for snippet in snippets:
if snippet not in text:
failures.append(f"{path} missing {snippet!r}")
if failures:
raise RuntimeError("Ground Zero water-source visual verification failed: " + "; ".join(failures))
unreal.log(
"Ground Zero water-source visual verification complete: "
f"{WATER_SOURCE_LABEL}, {len(REQUIRED_COMPONENTS)} native visual components, "
f"{len(REQUIRED_NEARBY_DRESSING)} nearby dressing actors."
)
main()