Final allowed fix round for S4-c2 on cc8e5677a. This lands every item in the campaign packet section 12 without adding a flush site, shader, distance, overflow recovery draw, or graphical-client run. R2-1 particle row 5: ParticleRenderer now constructs and owns the actual particle-mesh-opaque pipeline using the existing particle_mesh shaders and layout, Blend=None, depth test/write enabled with WorldCompare, dynamic per-batch cull, clockwise front face, and no alpha-to-coverage. The production dispatch selects it for an opaque-classified mesh particle whose clamped material alpha is 1.0. Nonopaque mesh pipelines remain depth-write-off. The production route keeps cached reserve/immediate delegates and the warmed append/immediate paths allocate 0 B. R2-2 EnvCell exact per-subset routing: ObjectMeshManager carries Content's TextureBatchData.RetailSurfaceMask onto ObjectRenderBatch at the real upload boundary. EnvCellRenderer scans the active prepared cell snapshot and feeds each real transparent batch's exact mask through RetailAlphaMeshRouter. Pure 0x08 Base1ClipMap reaches CLIP, 0x02 alpha-family reaches ALPHA, and table Immediate subsets draw at the cell turn. Separate fixed-route draw sources coalesce to at most one token per (cell,list) and filtered replay draws only that list's subsets; a mixed cell contributes to both lists without duplicate replay. Detail-on routes eligible subsets immediately with the detail pass. The warmed dispatch/source allocation pins measure 0 B; the production scan/filter is covered behaviorally and uses only retained scratch/enumerators (static allocation audit). R2-3 capacity cleanup: RetailAlphaQueue registers a source before the 3,000 entry capacity return. A source whose first append is rejected is therefore reset by flush, EndFrame, or abort, but its rejected payload is never prepared or drawn. R2-4 production proof and prose: both actual Wb submit sites are exercised; the particle tests call the production dispatcher and inspect the constructed owner's production pipeline/selector; EnvCell tests upload real Content batch masks through ObjectMeshManager and drain real filtered MDI calls. The A1 positive proof executes WorldSceneRenderer's real outdoor frame owner through RetailPViewRenderer.DrawInside and RetailAlphaQueue.EndFrame and observes [DrawBuilding x N, RenderNormalMode] with no LandscapeFlush. The packet and register now state the varying retail first-for-list truth and the exact per-subset EnvCell/AP-238, visible AP-239 compositing, and AP-240 feeder scope. Physical active register counts remain AP=159 and AD=92. Final clean-state gates (actual output): - Release solution build: Build succeeded; 0 Warning(s); 0 Error(s). - Hermetic solution filter: every project green, 16,728 passed / 0 failed / 0 skipped total; AcDream.App.Tests 6,875/6,875. - InstalledDat: 255 passed / 10 failed / 1 skipped / 266 total, exactly the allowed identities: TowerAscent_StaircaseStaysConeVisible_EveryStep; MainGameUiAndChatInput_MediaBearingChildrenNowBuildAsRealWidgets (#383); EveryAuthoredInvisibleWidget_StartsHiddenAcrossAllLayouts (#383); Oh_doorway_still_first_frame_diff (#458); and the six AlphaFlushCounts_{CathedralArrival,CathedralLeak,CathedralStairArch, FoundryDeep,HoltburgDoorwayStill,TerraceEdge}_MatchesRetailFrame2. All six AlphaFlushSites_* pass in the same lane. - VulkanShaderDescriptorContractTests + VulkanShaderManifestTests + RenderPackSpirvValidatorTests: 32 passed / 0 failed / 0 skipped. - Corrected queue/router/walk/driver/particle/Wb/EnvCell/PView production filter: 211 passed / 0 failed / 0 skipped. - Explicit warmed production allocation pins: 2 passed; both measure 0 B. - Register: physical AP-238/AP-239/AP-240/AD-120/~~AP-34~~ rows each count exactly 1; active physical rows AP=159 and AD=92. - git diff --check: PASS. Production mutation checks (each applied, run to the named first failure, and exactly reversed before the final gates): 1. Restoring the particle row-5 throw fails OpaqueClassifiedMeshBatch_WithNoMaterialAlpha_DrawsImmediateOnOpaqueDepthState first with InvalidOperationException: mutation: row 5 unreachable. 2. Deleting row 5's immediate callback fails that same production-dispatch test's first collection assertion: expected [(Mesh, 11, True)], actual []. 3. Constructing the actual owner pipeline with depthWrite:false fails ImmediateOpaqueMesh_UsesProductionParticleMeshOpaquePipelineDescription first at Assert.True(description.Depth.Write): expected true, actual false. 4. Mapping the production selector back to _meshAlphaPipeline fails that same test first at Assert.Same: expected particle-mesh-opaque, actual particle-mesh-alpha. 5. Dropping RetailSurfaceMask at the real ObjectMeshManager upload boundary fails the mixed-cell production scan first: expected Clip | Alpha, actual Immediate. 6. Hardcoding the uploaded EnvCell scan to MaskAlphaFamily fails the mixed-cell production scan first: expected Clip | Alpha, actual Alpha; the pure-mask pin also reports expected Clip, actual Alpha. 7. Inverting the production detail predicate fails the detail-on production pin first: expected Immediate, actual Clip. 8. Removing both EnvCell replay filters fails the mixed production drain's real MDI assertions: each call expected DrawCount 1, actual DrawCount 2. 9. Moving RegisterSource below the full-capacity return fails every RejectedFirstUseSource_IsCleanedWithoutPrepareOrDraw row (flush, EndFrame, abort) at the first ResetCount assertion: expected 1, actual 0. 10. Hardcoding detailSurfaceActive=false at the real Wb dispatch fails both production submit-site tests at their first queue-count assertion: expected 0, actual 1. 11. Restoring RetailPViewRenderer's removed outdoor LandscapeFlush call fails OutdoorProductionPView_DrainsBuildingThenRenderNormalModeWithoutLandscapeFlush first at the real drain sequence: expected [DrawBuilding, RenderNormalMode], actual [DrawBuilding, LandscapeFlush, RenderNormalMode]. 12. Deleting WorldSceneRenderer's final EndFrame owner drain fails that same full-path A1 test first: expected [DrawBuilding, RenderNormalMode], actual [DrawBuilding]. Not done/deferred: none. No retail conflict or infeasible contract item was found. No graphical client was launched. Co-Authored-By: Codex <noreply@openai.com>
414 lines
16 KiB
C#
414 lines
16 KiB
C#
using System.Numerics;
|
|
using AcDream.App.Rendering;
|
|
using AcDream.App.Rendering.Gpu;
|
|
using AcDream.App.Rendering.Gpu.Vk;
|
|
using AcDream.App.Rendering.Wb;
|
|
using AcDream.App.Tests.Rendering.Gpu;
|
|
using AcDream.Content;
|
|
using Chorizite.Core.Render.Enums;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using CullMode = DatReaderWriter.Enums.CullMode;
|
|
|
|
namespace AcDream.App.Tests.Rendering;
|
|
|
|
/// <summary>
|
|
/// S4-c2 final EnvCell production-dispatch pins: exact retained per-batch mask,
|
|
/// one token per (cell,list), separate list-filtered replay sources, immediate
|
|
/// detail turns, interleaving, and warmed allocation behavior.
|
|
/// </summary>
|
|
public sealed class EnvCellAlphaDrawSourceTests
|
|
{
|
|
/// <summary>
|
|
/// Canonical F4180104 surface 08000BFF's pure Base1ClipMap mask 0x08
|
|
/// reaches CLIP while alpha-family 0x02 reaches ALPHA. Mutation check:
|
|
/// hardcoding MaskAlphaFamily in the production batch classifier makes
|
|
/// the first assertion report Alpha instead of Clip.</summary>
|
|
[Fact]
|
|
public void RetainedPerBatchMask_RoutesClipAndAlphaExactly()
|
|
{
|
|
var clip = new ObjectRenderBatch { IsTransparent = true, RetailSurfaceMask = 0x08 };
|
|
var alpha = new ObjectRenderBatch { IsTransparent = true, RetailSurfaceMask = 0x02 };
|
|
|
|
Assert.Equal(
|
|
EnvCellTransparentRoute.Clip,
|
|
EnvCellRenderer.RouteTransparentBatch(clip, detailSurfaceActive: false));
|
|
Assert.Equal(
|
|
EnvCellTransparentRoute.Alpha,
|
|
EnvCellRenderer.RouteTransparentBatch(alpha, detailSurfaceActive: false));
|
|
}
|
|
|
|
/// <summary>
|
|
/// A mixed cell creates one token in each list and neither set draws before
|
|
/// the real queue drain. CLIP drains first and receives only the CLIP
|
|
/// replay filter; ALPHA receives only ALPHA. Mutation checks: inverting the
|
|
/// detail-off immediate predicate makes <c>Assert.Empty(log)</c> fail;
|
|
/// removing the replay filter makes either route assertion fail.</summary>
|
|
[Fact]
|
|
public void MixedCell_DefersOneTokenPerList_AndReplayIsListFiltered()
|
|
{
|
|
using var fixture = new ProductionEnvCellFixture(
|
|
detailSurfaceActive: false,
|
|
0x08,
|
|
0x02);
|
|
EnvCellTransparentRoute routes = fixture.Renderer.GetTransparentRoutes(
|
|
ProductionEnvCellFixture.CellId,
|
|
detailSurfaceActive: false);
|
|
|
|
fixture.Queue.BeginFrame();
|
|
RetailPViewPassExecutor.DispatchTransparentCellShell(
|
|
ProductionEnvCellFixture.CellId,
|
|
routes,
|
|
detailSurfaceActive: false,
|
|
fixture.Queue,
|
|
fixture.ClipSource,
|
|
fixture.AlphaSource,
|
|
fixture.ImmediateSink);
|
|
|
|
Assert.Equal(EnvCellTransparentRoute.Clip | EnvCellTransparentRoute.Alpha, routes);
|
|
Assert.Empty(fixture.Device.Calls.OfType<GpuRecordedMultiDrawIndirect>());
|
|
Assert.Equal(1, fixture.Queue.ClipCount);
|
|
Assert.Equal(1, fixture.Queue.AlphaCount);
|
|
|
|
fixture.Queue.Flush(RetailAlphaFlushSite.DrawBuilding, 0f);
|
|
|
|
GpuRecordedMultiDrawIndirect[] draws =
|
|
[.. fixture.Device.Calls.OfType<GpuRecordedMultiDrawIndirect>()];
|
|
Assert.Equal(2, draws.Length);
|
|
Assert.All(draws, static draw => Assert.Equal(1u, draw.DrawCount));
|
|
fixture.Queue.EndFrame();
|
|
}
|
|
|
|
[Fact]
|
|
public void DetailOn_DrawsAtCellTurnWithDetail_AndQueuesNothing()
|
|
{
|
|
using var fixture = new ProductionEnvCellFixture(
|
|
detailSurfaceActive: true,
|
|
0x08);
|
|
EnvCellTransparentRoute routes = fixture.Renderer.GetTransparentRoutes(
|
|
ProductionEnvCellFixture.CellId,
|
|
detailSurfaceActive: true);
|
|
fixture.Queue.BeginFrame();
|
|
|
|
RetailPViewPassExecutor.DispatchTransparentCellShell(
|
|
ProductionEnvCellFixture.CellId,
|
|
routes,
|
|
detailSurfaceActive: true,
|
|
fixture.Queue,
|
|
fixture.ClipSource,
|
|
fixture.AlphaSource,
|
|
fixture.ImmediateSink);
|
|
|
|
Assert.Equal(EnvCellTransparentRoute.Immediate, routes);
|
|
Assert.Equal(1, fixture.ImmediateSink.DrawCount);
|
|
Assert.True(fixture.ImmediateSink.LastDetailSurfaceActive);
|
|
Assert.Equal(2, fixture.Device.Calls.OfType<GpuRecordedMultiDrawIndirect>().Count());
|
|
Assert.Equal(0, fixture.Queue.PendingCount);
|
|
fixture.Queue.EndFrame();
|
|
Assert.Equal(1, fixture.ImmediateSink.DrawCount);
|
|
}
|
|
|
|
/// <summary>Two cell tokens from the SAME <see cref="RetailPViewPassExecutor.EnvCellAlphaDrawSource"/>,
|
|
/// with an unrelated source's entry appended between them, must still
|
|
/// produce TWO separate single-cell draw calls around the interposed
|
|
/// entry — <see cref="IRetailAlphaDrawSource"/>'s "only adjacent
|
|
/// same-source entries batch" invariant (the queue never groups across
|
|
/// another entry, which is what keeps compositing order exact). Mutation
|
|
/// check: an implementation that draws every prepared cell id in one
|
|
/// call regardless of the requested <c>(first, count)</c> range would
|
|
/// print both cell ids together on EACH of the two
|
|
/// <c>DrawPreparedAlphaBatch</c> invocations instead of once each — the
|
|
/// exact sequence assertion below fails against that mutation.</summary>
|
|
[Fact]
|
|
public void ParticleAppendedBetweenTwoCellTokens_KeepsItsPositionInTheCombinedDrain()
|
|
{
|
|
var log = new List<string>();
|
|
var clipSource = Source(EnvCellTransparentRoute.Clip, log);
|
|
var cellSource = Source(EnvCellTransparentRoute.Alpha, log);
|
|
var particleSource = new RecordingSource("particle", log);
|
|
var queue = new RetailAlphaQueue();
|
|
|
|
queue.BeginFrame();
|
|
RetailPViewPassExecutor.DispatchTransparentCellShell(
|
|
0x100u, EnvCellTransparentRoute.Alpha, false, queue,
|
|
clipSource, cellSource, (_, _, _) => throw new InvalidOperationException());
|
|
Assert.True(queue.TryAppend(RetailAlphaList.Alpha, particleSource, 7, false));
|
|
RetailPViewPassExecutor.DispatchTransparentCellShell(
|
|
0x200u, EnvCellTransparentRoute.Alpha, false, queue,
|
|
clipSource, cellSource, (_, _, _) => throw new InvalidOperationException());
|
|
queue.EndFrame();
|
|
|
|
Assert.Equal(new[] { "Alpha:00000100", "particle:7", "Alpha:00000200" }, log);
|
|
}
|
|
|
|
[Fact]
|
|
public void ProductionDispatch_WarmedImmediateAndFilteredSourcesDoNotAllocate()
|
|
{
|
|
var sink = new NoAllocRouteSink();
|
|
var clipSource = new RetailPViewPassExecutor.EnvCellAlphaDrawSource(
|
|
sink.Render,
|
|
EnvCellTransparentRoute.Clip);
|
|
var alphaSource = new RetailPViewPassExecutor.EnvCellAlphaDrawSource(
|
|
sink.Render,
|
|
EnvCellTransparentRoute.Alpha);
|
|
RetailPViewPassExecutor.RenderImmediateEnvCellRoute immediate = sink.DrawImmediate;
|
|
var queue = new RetailAlphaQueue();
|
|
|
|
for (int i = 0; i < 64; i++)
|
|
RunMixed(queue, clipSource, alphaSource, immediate);
|
|
|
|
long before = GC.GetAllocatedBytesForCurrentThread();
|
|
for (int i = 0; i < 1_000; i++)
|
|
RunMixed(queue, clipSource, alphaSource, immediate);
|
|
long allocated = GC.GetAllocatedBytesForCurrentThread() - before;
|
|
|
|
Assert.Equal(0, allocated);
|
|
}
|
|
|
|
private static void RunMixed(
|
|
RetailAlphaQueue queue,
|
|
RetailPViewPassExecutor.EnvCellAlphaDrawSource clipSource,
|
|
RetailPViewPassExecutor.EnvCellAlphaDrawSource alphaSource,
|
|
RetailPViewPassExecutor.RenderImmediateEnvCellRoute immediate)
|
|
{
|
|
queue.BeginFrame();
|
|
RetailPViewPassExecutor.DispatchTransparentCellShell(
|
|
0xF4180104u,
|
|
EnvCellTransparentRoute.Immediate
|
|
| EnvCellTransparentRoute.Clip
|
|
| EnvCellTransparentRoute.Alpha,
|
|
detailSurfaceActive: false,
|
|
queue,
|
|
clipSource,
|
|
alphaSource,
|
|
immediate);
|
|
queue.EndFrame();
|
|
}
|
|
|
|
private static RetailPViewPassExecutor.EnvCellAlphaDrawSource Source(
|
|
EnvCellTransparentRoute expectedRoute,
|
|
List<string> log) =>
|
|
new(
|
|
(cells, route, detail) =>
|
|
{
|
|
Assert.Equal(expectedRoute, route);
|
|
Assert.False(detail);
|
|
for (int i = 0; i < cells.Count; i++)
|
|
log.Add($"{route}:{cells[i]:X8}");
|
|
},
|
|
expectedRoute);
|
|
|
|
/// <summary>
|
|
/// Recording-GPU fixture for the production EnvCell scan, dispatch and
|
|
/// filtered replay path. The two retained masks enter through the real
|
|
/// Content upload boundary, not through hand-built App batches.
|
|
/// </summary>
|
|
private sealed class ProductionEnvCellFixture : IDisposable
|
|
{
|
|
public const uint CellId = 0xF4180104u;
|
|
private const ulong MeshId = 0x2_F4180104UL;
|
|
|
|
private readonly GpuDeviceFrameLifetime _frames;
|
|
private readonly VulkanWorldPassScope _scope;
|
|
private readonly ObjectMeshManager _meshManager;
|
|
private readonly IGpuPassEncoder _pass;
|
|
private readonly IDisposable _publication;
|
|
|
|
public ProductionEnvCellFixture(
|
|
bool detailSurfaceActive,
|
|
params byte[] retainedMasks)
|
|
{
|
|
Device = new RecordingGpuDevice();
|
|
_frames = new GpuDeviceFrameLifetime(Device);
|
|
_scope = new VulkanWorldPassScope(sampleCount: 1);
|
|
_meshManager = new ObjectMeshManager(
|
|
new VulkanMeshPipelineDevice(Device.Retirement),
|
|
Device,
|
|
new NullPreparedAssetSource(),
|
|
NullLogger<ObjectMeshManager>.Instance);
|
|
|
|
var mesh = new ObjectMeshData
|
|
{
|
|
ObjectId = MeshId,
|
|
Vertices =
|
|
[
|
|
new VertexPositionNormalTexture { Position = new Vector3(0, 0, 0) },
|
|
new VertexPositionNormalTexture { Position = new Vector3(1, 0, 0) },
|
|
new VertexPositionNormalTexture { Position = new Vector3(0, 1, 0) },
|
|
],
|
|
};
|
|
var batches = new List<TextureBatchData>(retainedMasks.Length);
|
|
for (int i = 0; i < retainedMasks.Length; i++)
|
|
{
|
|
batches.Add(new TextureBatchData
|
|
{
|
|
Key = new TextureKey { SurfaceId = 0x08000BFFu + (uint)i },
|
|
TextureData = new byte[8 * 8 * 4],
|
|
Indices = [0, 1, 2],
|
|
IsTransparent = true,
|
|
Translucency = AcDream.Core.Meshing.TranslucencyKind.AlphaBlend,
|
|
RetailSurfaceMask = retainedMasks[i],
|
|
CullMode = CullMode.Clockwise,
|
|
IsCellShell = true,
|
|
SourceSurfaceIndex = i,
|
|
});
|
|
}
|
|
mesh.TextureBatches[(8, 8, TextureFormat.RGBA8)] = batches;
|
|
Assert.NotNull(_meshManager.UploadMeshData(mesh));
|
|
|
|
Renderer = new EnvCellRenderer(
|
|
Device,
|
|
_frames,
|
|
_scope,
|
|
_meshManager,
|
|
new WbFrustum(),
|
|
detailSurfaceActive
|
|
? new TerrainAtlas.RetailDetailTextureBinding(
|
|
new GpuTextureSlot(99),
|
|
Tiling: 2f,
|
|
SurfaceTextureId: 1,
|
|
RenderSurfaceId: 2,
|
|
Width: 4,
|
|
Height: 4)
|
|
: default,
|
|
detailSurfaceActive ? DetailOn : DetailOff);
|
|
typeof(EnvCellRenderer)
|
|
.GetField("_activeSnapshot", System.Reflection.BindingFlags.NonPublic
|
|
| System.Reflection.BindingFlags.Instance)!
|
|
.SetValue(Renderer, new EnvCellVisibilitySnapshot
|
|
{
|
|
BatchedByCell = new Dictionary<uint, Dictionary<ulong, List<InstanceData>>>
|
|
{
|
|
[CellId] = new Dictionary<ulong, List<InstanceData>>
|
|
{
|
|
[MeshId] =
|
|
[
|
|
new InstanceData
|
|
{
|
|
Transform = Matrix4x4.Identity,
|
|
CellId = CellId,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
});
|
|
|
|
Queue = new RetailAlphaQueue();
|
|
ClipSource = new RetailPViewPassExecutor.EnvCellAlphaDrawSource(
|
|
Renderer.RenderTransparentOrdered,
|
|
EnvCellTransparentRoute.Clip);
|
|
AlphaSource = new RetailPViewPassExecutor.EnvCellAlphaDrawSource(
|
|
Renderer.RenderTransparentOrdered,
|
|
EnvCellTransparentRoute.Alpha);
|
|
ImmediateSink = new ProductionImmediateSink(Renderer);
|
|
|
|
_frames.BeginFrame();
|
|
IGpuFrame frame = _frames.CurrentFrame!;
|
|
_pass = frame.BeginPass(
|
|
GpuPassDescription.BackbufferClear(
|
|
"s4-c2-envcell-production",
|
|
Vector4.Zero,
|
|
sampleCount: 1));
|
|
_publication = _scope.Publish(_pass);
|
|
Renderer.BeginFrame(frameSlot: 0);
|
|
Device.Clear();
|
|
}
|
|
|
|
public RecordingGpuDevice Device { get; }
|
|
|
|
public EnvCellRenderer Renderer { get; }
|
|
|
|
public RetailAlphaQueue Queue { get; }
|
|
|
|
public RetailPViewPassExecutor.EnvCellAlphaDrawSource ClipSource { get; }
|
|
|
|
public RetailPViewPassExecutor.EnvCellAlphaDrawSource AlphaSource { get; }
|
|
|
|
public ProductionImmediateSink ImmediateSink { get; }
|
|
|
|
private static bool DetailOn() => true;
|
|
|
|
private static bool DetailOff() => false;
|
|
|
|
public void Dispose()
|
|
{
|
|
_publication.Dispose();
|
|
_pass.Dispose();
|
|
Renderer.Dispose();
|
|
_meshManager.Dispose();
|
|
Device.Dispose();
|
|
}
|
|
}
|
|
|
|
private sealed class ProductionImmediateSink(EnvCellRenderer renderer) :
|
|
IEnvCellImmediateDrawSink
|
|
{
|
|
public int DrawCount { get; private set; }
|
|
|
|
public bool LastDetailSurfaceActive { get; private set; }
|
|
|
|
public void DrawImmediate(
|
|
uint cellId,
|
|
EnvCellTransparentRoute route,
|
|
bool detailSurfaceActive)
|
|
{
|
|
DrawCount++;
|
|
LastDetailSurfaceActive = detailSurfaceActive;
|
|
renderer.RenderTransparentOrdered([cellId], route, detailSurfaceActive);
|
|
}
|
|
}
|
|
|
|
private sealed class NullPreparedAssetSource : IPreparedAssetSource
|
|
{
|
|
public PreparedAssetSourceStats Stats => default;
|
|
|
|
public CacheStats DecodedTextureCacheStats => default;
|
|
|
|
public PreparedAssetPresence Probe(
|
|
AcDream.Content.Pak.PakAssetType type,
|
|
uint sourceFileId) =>
|
|
PreparedAssetPresence.Missing;
|
|
|
|
public PreparedAssetReadResult Read(
|
|
in PreparedAssetRequest request,
|
|
CancellationToken cancellationToken = default) =>
|
|
PreparedAssetReadResult.Missing;
|
|
|
|
public void Dispose()
|
|
{
|
|
}
|
|
}
|
|
|
|
private sealed class RecordingSource(string name, List<string> log) : IRetailAlphaDrawSource
|
|
{
|
|
private int[] _prepared = [];
|
|
|
|
public void PrepareAlphaDraws(ReadOnlySpan<int> tokens) => _prepared = tokens.ToArray();
|
|
|
|
public void DrawPreparedAlphaBatch(int firstPreparedDraw, int drawCount)
|
|
{
|
|
for (int i = 0; i < drawCount; i++)
|
|
log.Add($"{name}:{_prepared[firstPreparedDraw + i]}");
|
|
}
|
|
|
|
public void ResetAlphaSubmissions()
|
|
{
|
|
}
|
|
}
|
|
|
|
private sealed class NoAllocRouteSink
|
|
{
|
|
public void Render(
|
|
IReadOnlyList<uint> cells,
|
|
EnvCellTransparentRoute route,
|
|
bool detailSurfaceActive)
|
|
{
|
|
}
|
|
|
|
public void DrawImmediate(
|
|
uint cellId,
|
|
EnvCellTransparentRoute route,
|
|
bool detailSurfaceActive)
|
|
{
|
|
}
|
|
}
|
|
}
|