Original curved-blade alpine verge
15 September 2026. Source integration and CPU verification; native import, gameplay rendering, appearance and frame cost remain to be checked by the serialized Windows build. This is not an AAA acceptance.
Source and delivery
Poly Haven Grass Medium 01, CC0-1.0, modeled by Rico Cilliers and photographed by Rob Tuytel. The official Blender source and five dependencies, publisher MD5 and project SHA-256 remain in ArtSource/Alps/GrassMedium01Review/source-manifest.json. The original source and the two CPU review images were inspected before integration. Docs/UNDERGROWTH-ASSETS.json remains a correct historical texture-only record.
Tools/Art/prepare_alpine_grass.py exports only mid_b, mid_c, tall_a, tall_b, each at three original LODs. Original curved blade geometry, mesh normals and UVs are retained. A tuft's footprint is centered and its foot placed at the origin; no giant crossed atlas rectangles, new decimation or per-blade objects are introduced. The twelve FBXs total 442,960 bytes / 5,985 source triangles. Their conservative source-corner mesh payload is about 0.625 MiB, before engine overhead. Full per-file hashes are in integration-manifest.json.
Matching 1k photographic maps are provided as Textures/AlpineGrass_{color,normal,mask}. Color has original RGB plus the source alpha; no tint or artistic recoloring. The normal EXR has slight component overshoots, up to 1.0283: directions are decoded, normalized and re-encoded to unsigned 8-bit PNG, avoiding component clipping. The HDRP mask uses R=0, G=1, B=1 and A=1−source roughness. White material color, .38 alpha cutoff, coverage-preserving mipmaps, source tangent normal and double-sided normal flipping are explicit. This technical normal normalization is not represented as a byte-identical EXR copy.
Runtime contract
Root ChapterScenery.GroundDetails calls AlpineGrassVerge.Create(track), then Add(localFoot, heightMetres, yawDegrees, seed) for each existing ground candidate, and Seal(). One renderer child belongs to the generated track root. Positions still come from the actual apron-aware SceneryGroundHeight; no route, physics, collider, AI or terrain coordinate changes. Winter creates no summer grass. Following the native rejection below, the requested height now selects a 0.85–1.45 original-source scale, preserving each family's real size rather than forcing small tufts to 0.60 m.
The renderer keeps original Resources meshes shared. It retains the full FBX root transform, checks imported height against the measured source height within 2.5%, one readable submesh, original triangle count, UV and normal arrays, and rejects a missing or invalid asset explicitly. No geometry failure silently creates an atlas fallback. Imported mesh allocation is bounded to twelve sources, with a conservative 25 MiB guard. Imported assets remain in the existing shared Resources cache; per-stage bins/matrices die with their single owner. This is not a claim that the project's wider active-chunk architecture is finished.
Anchors are collected into 24 m spatial bins. Each frame visits only nearby bins, then applies frustum and 105 m distance culling. It does not scan the entire stage's anchor list. The final 12 m shrink to the planted foot. The near/far boundary is 32 m. Budgets are shared across the two variants of each group, not independently multiplied per mesh:
| Group | LOD | Instance cap | Worst triangles |
|---|---|---|---|
| Near mid | 1 | 192 | 127,104 |
| Near tall | 0 | 96 | 29,760 |
| Far mid | 2 | 640 | 222,720 |
| Far tall | 2 | 480 | 78,720 |
| Total | 1,408 | 458,304 |
Graphics.RenderMeshInstanced submits at most 256 instances per draw, below Unity's default 511 limit. A cached actual MainCamera is passed explicitly in RenderParams. Near grass casts shadows; far grass receives shadows but does not cast them. RepresentativeMaterial is included in the build's shader references, with instancing enabled. Null-graphics gameplay QA still constructs and checks sources but skips only rendering. Unsupported real-device instancing emits a clear error and disables the renderer. Native log markers are RAMPAGE_GRASS_IMPORT, RAMPAGE_GRASS_IMPORT_COMPLETE, RAMPAGE_GRASS_INSTANCING and the first RAMPAGE_GRASS_DRAW with visited/total anchors and actual draw counts.
Evidence and remaining check
ArtSource/Alps/GrassMedium01Review/verify_integration.py re-imported all twelve exported FBXs in Blender CPU: all source triangle counts and UV-layer counts match, with sub-micrometer height differences. integration-cpu-verification.json records this pass. Packed RGB equals the decoded source exactly; alpha, normalized tangent normal and roughness differ only by at most half of one 8-bit quantization step. This cannot prove Unity's imported axis, HDRP shader execution, shadow balance or appearance.
Offline Runtime + Editor compilation passed 94 + 22 source files, zero warnings/errors. The next native check must confirm twelve import records, actual instanced draws with the intended camera, planted source blades without black blocks, stable LOD change at road speed, and measurable image/frame cost. CPU previews show a feasible geometric improvement, not the final game's lighting or acceptance.
Native import succeeded; scale and material correction
The 22:13 Alps01 and 22:25 Alps02 player runs import all twelve original meshes with 502,860 conservative runtime mesh bytes, matching triangle counts, UV counts and metric heights. The first Alps01 draw has 586 instances / 172,740 triangles, visiting 1,109 of 22,467 anchors. The first Alps02 draw has 496 / 155,591, visiting 974 of 24,500 anchors. Import and instancing therefore execute; the reviewed images still show excessively large, dark islands and are not accepted visually.
Two concrete mismatches were identified. Original mid_c LOD1/2 is only 0.131 m high and about 0.21 m wide. The previous common 0.60 m target scaled it 4.58× to a nearly metre-wide island; the accepted CPU review used only 0.85–1.45×. Runtime scale now follows that reviewed interval, independently of family/LOD. Original Blender shading also blends in a Translucent BSDF at source Translucency 0.4; the initial HDRP Standard material omitted that transmission entirely.
The corrected material uses native HDRP LitTranslucent, TransmissionMask 0.4 and an explicitly bound ThinObject diffusion profile. AlpineGrassDiffusion.asset derives its optical settings from the installed HDRP Runtime/RenderPipelineResources/FoliageDiffusionProfile.asset: white transmission tint, 0–0.2873 mm thickness range, world scale 1, IOR 1.4. Zero geometric thickness corresponds to these single-surface blades. This is a bounded HDRP translation of the source's thin-leaf behavior, not a claim that different renderers' BSDFs match exactly. A stage-owned DiffusionProfileList volume registers the profile; its independently allocated component/profile are released on destruction. Only the imported profile remains shared. No global brightness, source tint or emission compensation was added.
The missing-probe hypothesis remains unproven. Unity's official RenderMeshInstanced documentation specifies automatic per-instance probe setup with BlendProbes; its probe interpolation API specifies ambient fallback without baked probes. LightProbeUsage.Off also supplies ambient lighting. No native SH coefficients have yet been measured, so BlendProbes is retained instead of asserting that an absent probe group proves black lighting. The next actual same-pose image should check the now-small blades and backlighting before any further change.
Dense near-verge revision after the 23:32 native images
Both actual Docs/QA/20260915-233200-campaign/alps-01-pass0-view0.png and view1.png were reviewed. Source scale is now plausible, but the verge is scattered flecks over empty ground. The source mid tufts occupy roughly 0.19 × 0.20 m; tall tufts roughly 0.15 × 0.15 m, before the retained 0.85–1.45 scale. The preceding main band attempted only seven tufts per side per 2.6 m over a 4.75 m width: at most 0.57/m² before rejection. The first native draw was 579 instances / 165,747 triangles over the 105 m range; simply increasing the rendering budget would not create missing anchors.
High quality now adds a deterministic jittered 0.25 m longitudinal/transverse grid only in 7.05–9.05 m lateral bands on each side. The 9.05–12 m bands receive five transverse rows at 0.55 m spacing and a 0.55 m longitudinal step. Existing sparse transition growth and sapling positions remain. Source scale, the 6.65 m asphalt exclusion, height/slope/patch rejections, and actual SceneryGroundHeight seating remain; dense rows use their own deterministic RNG. No colliders, individual grass GameObjects, blade edits, UV changes, tint or transmission changes were introduced. High favours mid tufts 70:30 over tall ones. Low keeps its original anchor generation, family distribution and budgets; winter still has no summer grass.
High renders only within 35 m, with a smooth source-size shrink from 28–35 m, and uses the near LOD only inside 10 m. Eight-metre spatial bins restrict candidate gathering. Actual squared camera distance sorts the frustum-visible candidates before budget allocation, rather than approximate bin-centre order. The following shared group limits also have explicit global instance/triangle guards:
| High group | LOD | Instance cap | Worst triangles |
|---|---|---|---|
| Near mid | 1 | 512 | 338,944 |
| Near tall | 0 | 128 | 39,680 |
| Far mid | 2 | 2,048 | 712,704 |
| Far tall | 2 | 768 | 125,952 |
| Total | 3,456 | 1,217,280 |
Selection compacts into the reused candidate-list prefix. A single packed matrix array contains exactly 3,456 slots / 221,184 bytes for High, or 1,408 / 90,112 bytes for Low. Per-mesh ranges are computed before submission; meshes do not each allocate the global maximum. Anchors store position, yaw, original scale and a family byte (24-byte struct layout), and sealed bins own exact-length arrays. Shared source meshes remain unchanged; there is no whole-stage scan each frame. Near grass alone casts shadows.
For the preceding native 3,625.65 m Alps01 route, the new grid attempts 232,048 main-band and 65,930 secondary-band positions. Including the preceding 22,462 sparse anchors gives an upper reference of 320,440 anchors / approximately 7.69 MB of raw anchor structs before the dense terrain/road/patch rejection. Alps02's equivalent upper reference is 355,195 / 8.52 MB. Bin/list overhead, temporary construction lists, visible-candidate scratch and Unity overhead are additional; these are not total process-memory measurements. Dense anchor construction still performs exact ground/clearance queries once per accepted candidate, so the next native run must measure startup duration as well as frames and repeated-stage memory.
Budget/source arithmetic is recorded in ArtSource/Alps/GrassMedium01Review/near-verge-budget.json. Managed compile: 97 Runtime + 22 Editor, zero warnings/errors. No Unity or Player process was started by this work. The RTX4060 target informed this bounded budget; actual visual coverage, frame time and memory plateau remain unverified until the parent-owned native run.
Configure-time profile binding correction
Tools/Logs/alpine-landcover-guardrail-build-retry.log records a real failed native build: HDRP's Editor implementation of SetDiffusionProfile attempted AssetDatabase.AddObjectToAsset on the not-yet-persisted representative grass material. The Player implementation only writes the profile hash. This was an integration error, not a GPU failure, and the failed build is not treated as delivered.
The binding now distinguishes these paths. Player code keeps the official HDMaterial call. Under UNITY_EDITOR, the current imported profile.hash is read through SerializedObject and bitcast to the shader float; the persistent profile GUID is packed byte-for-byte into its Vector4 in the same order as installed HDUtils.ConvertGUIDToVector4. No subasset write is attempted on the transient material. Its eventual saved copy retains the hash/GUID, while the Resources asset and stage-owned diffusion-profile volume explicitly retain/register the actual Player resource. Missing import/hash/GUID remains an error; no exception is caught or hidden.
The corrected source passed both the normal 97 Runtime + 22 Editor compile and a separate compile of all Runtime sources with UNITY_EDITOR defined, zero warnings/errors. The latter was necessary because the ordinary offline Runtime compile exercises only the Player branch. Native Configure/build confirmation remains the root-owned next step. Renderer SHA-256 at freeze: 9c44856bc5f7ff6ae405c8b0967dd5e7e32fda64eaf090d70485566e24a53f7c.