wip(assembler): M2 round 1 -- sharp-mesher spike: hybrid MC+DC keeps edges, borders and LOD seams not solved yet

Standalone only. Hybrid/DC cut sharp residual 16.7 -> 6.25 cm and split creases ~100%, organic wall
unchanged under hybrid, +4.4% mesh cost, W1/W8/W16 identical. Gate failed: hybrid open edges on 500/625
tagged tile borders; every method fails LOD0/LOD1 (no transition templates yet). Binaries and OBJ meshes not committed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-10 01:32:27 +02:00
1 parent 61633cb492
commit 6953254349
31 files changed
+5627

No files matched your search

+12
View File
@@ -805,3 +805,15 @@ New source files: none. Modified source files:
`Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp`, and `Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp`, and
`Standalone/Source/VoxelForge/Public/VoxelMarchingCubesMesher.h`. Modified BuildHost test tool: `Standalone/Source/VoxelForge/Public/VoxelMarchingCubesMesher.h`. Modified BuildHost test tool:
`Tools/TestVoxelForgeCoreWorldHost.ps1`. `Tools/TestVoxelForgeCoreWorldHost.ps1`.
## M2 round 1 — sharp-mesher decision spike
The standalone MC / hybrid / DC comparison found a sharp-quality advantage for hybrid, but no
candidate passes the complete seam gate: MC closes all same-LOD tile borders, hybrid has open
edges on all 500 tagged-shape cases, DC fails all 625 same-LOD cases, and all three fail the
LOD0/LOD1 transition audit. No production mesher is selected; hybrid is the candidate to carry
into the in-game round 2 after border and transition handling is implemented.
Full methods, phase/error/normal/cost/hash tables, seam results, captures, run ledger, and the
complete new-file list are in [the M2 report](Research/WorldAssemblerM2/REPORT.md). The matched
contact sheet is [here](Research/WorldAssemblerM2/results/captures/contact_sheet.jpg).
@@ -878,3 +878,33 @@ confirm their material directions. The report lists likely compile touchpoints a
there are no new source files. there are no new source files.
— Luna — Luna
## Luna, M2 round 1 — sharp-mesher decision spike
Hi Claude,
I built the standalone 25 cm comparison for MC, feature-aware hybrid MC/DC, and DC everywhere.
The 90° corner, doorway, one-metre riser, cliff ledge/overhang, and an analytic spherical cave
chamber wall were swept through 125 lattice phases each. Hybrid/DC reduced sharp-feature field
residual and split about 99.9–100% of tagged creases; MC split about 11.8–24.3%. Hybrid kept the
chamber wall on MC and matched its 0.39 cm mean residual. DC everywhere had a rougher chamber
surface at 0.50 cm mean and 4.75 cm maximum residual.
No method passes the seam gate. Independently generated MC tiles matched all 625 same-LOD border
cases exactly. Hybrid had open edges in all 500 sharp cases, and DC failed all 625 cases. Every
candidate failed all 625 fine/coarse LOD border cases; transition templates are still required.
Hybrid costs 14.907 ms per 32³ tile at W8 versus 14.275 ms for MC. All 45 W1/W8/W16 hash rows
matched their W1 reference. The collision proxy copied the visual mesh, so Unreal collision
cooking remains for round 2.
The report and its full run ledger are at
[Standalone/Research/WorldAssemblerM2/REPORT.md](../../WorldAssemblerM2/REPORT.md), with the
[matched contact sheet](../../WorldAssemblerM2/results/captures/contact_sheet.jpg). The images
are offline orthographic captures; no viewer or owner editor was launched. No Source file,
owner project file, or .uasset changed.
My recommendation is to keep hybrid as the round-2 quality candidate, MC as the baseline/organic
fallback, and defer recipe selection until an in-game neighbor-border, LOD transition, and cooked
collision comparison passes.
— Luna
@@ -0,0 +1,115 @@
[CmdletBinding()]
param()
$ErrorActionPreference = 'Stop'
$WorkDir = [IO.Path]::GetFullPath($PSScriptRoot)
$Standalone = [IO.Path]::GetFullPath((Join-Path $WorkDir '..\..'))
$PluginRoot = [IO.Path]::GetFullPath((Join-Path $Standalone '..'))
$BuildDir = Join-Path $WorkDir 'build'
$OutDir = Join-Path $WorkDir 'bin'
[IO.Directory]::CreateDirectory($BuildDir) | Out-Null
[IO.Directory]::CreateDirectory($OutDir) | Out-Null
$Runner = Get-Process -Id $PID
$Runner.PriorityClass = 'BelowNormal'
$Runner.ProcessorAffinity = [IntPtr]0xFF00
$Affinity = [IntPtr]0xFF00
$VcVars = 'E:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat'
if (-not (Test-Path -LiteralPath $VcVars -PathType Leaf)) { throw "MSVC environment script missing: $VcVars" }
# Import the exact MSVC environment without running the compiler through an unrestricted shell.
$EnvironmentLines = & $env:ComSpec /d /s /c "call `"$VcVars`" >nul 2>nul && set"
if ($LASTEXITCODE -ne 0) { throw "vcvars64 environment setup failed: $LASTEXITCODE" }
foreach ($Line in $EnvironmentLines) {
$Equals = $Line.IndexOf('=')
if ($Equals -le 0) { continue }
$Name = $Line.Substring(0, $Equals)
if ($Name.StartsWith('=')) { continue }
[Environment]::SetEnvironmentVariable($Name, $Line.Substring($Equals + 1), 'Process')
}
$Compiler = Join-Path $env:VCToolsInstallDir 'bin\Hostx64\x64\cl.exe'
if (-not (Test-Path -LiteralPath $Compiler -PathType Leaf)) { throw "MSVC compiler missing: $Compiler" }
$Source = Join-Path $WorkDir 'SharpMesherSpike.cpp'
$Exe = Join-Path $OutDir 'SharpMesherSpike.exe'
$Obj = Join-Path $BuildDir 'SharpMesherSpike.obj'
$Stdout = Join-Path $WorkDir 'build.stdout.log'
$Stderr = Join-Path $WorkDir 'build.stderr.log'
$CompilerArguments = '/nologo /std:c++20 /O2 /fp:precise /EHsc /MP8 /D_CRT_SECURE_NO_WARNINGS ' +
('/I"{0}" /I"{1}" /Fo"{2}" "{3}" /Fe:"{4}"' -f `
(Join-Path $Standalone 'Compat'), `
(Join-Path $Standalone 'Source\VoxelForge\Public'), $Obj, $Source, $Exe)
$Attempt = 0
do {
$Attempt++
Remove-Item -LiteralPath $Stdout, $Stderr -Force -ErrorAction SilentlyContinue
$Info = [Diagnostics.ProcessStartInfo]::new()
$Info.FileName = $Compiler
$Info.Arguments = $CompilerArguments
$Info.WorkingDirectory = $PluginRoot
$Info.UseShellExecute = $false
$Info.CreateNoWindow = $true
$Info.RedirectStandardOutput = $true
$Info.RedirectStandardError = $true
$Process = [Diagnostics.Process]::new()
$Process.StartInfo = $Info
if (-not $Process.Start()) { throw 'Could not start cl.exe.' }
$ProcessId = $Process.Id
$Process.PriorityClass = [Diagnostics.ProcessPriorityClass]::BelowNormal
$Process.ProcessorAffinity = $Affinity
$OwnedPids = [Collections.Generic.HashSet[int]]::new()
[void]$OwnedPids.Add($ProcessId)
$StartAt = [DateTime]::UtcNow
$OutputTask = $Process.StandardOutput.ReadToEndAsync()
$ErrorTask = $Process.StandardError.ReadToEndAsync()
$TimedOut = $false
$MemoryStop = $false
while (-not $Process.HasExited) {
$Process.Refresh()
$Processes = @(Get-CimInstance -ClassName Win32_Process -ErrorAction SilentlyContinue)
$FoundChild = $true
while ($FoundChild) {
$FoundChild = $false
foreach ($Candidate in $Processes) {
if ($OwnedPids.Contains([int]$Candidate.ParentProcessId) -and
$OwnedPids.Add([int]$Candidate.ProcessId)) { $FoundChild = $true }
}
}
foreach ($OwnedPid in $OwnedPids) {
try {
$Child = Get-Process -Id $OwnedPid -ErrorAction Stop
$Child.PriorityClass = 'BelowNormal'
$Child.ProcessorAffinity = $Affinity
if ($Child.PrivateMemorySize64 -ge 8GB) { $MemoryStop = $true; Stop-Process -Id $OwnedPid -Force }
} catch {}
}
if ($MemoryStop) { break }
if (([DateTime]::UtcNow - $StartAt).TotalSeconds -ge 900) { $TimedOut = $true; break }
Start-Sleep -Milliseconds 200
}
if ($TimedOut -or $MemoryStop) {
foreach ($OwnedPid in $OwnedPids) { try { Stop-Process -Id $OwnedPid -Force -ErrorAction Stop } catch {} }
try { $Process.Kill() } catch {}
}
$Process.WaitForExit()
$CompilerStdout = $OutputTask.GetAwaiter().GetResult()
$CompilerStderr = $ErrorTask.GetAwaiter().GetResult()
[IO.File]::WriteAllText($Stdout, $CompilerStdout)
[IO.File]::WriteAllText($Stderr, $CompilerStderr)
if ($TimedOut) { throw 'Build exceeded 15 minutes and was stopped.' }
if ($MemoryStop) { throw 'Build process reached the 8 GB private-memory cap and was stopped.' }
if ($Process.ExitCode -eq -1073741502 -and $Attempt -eq 1) {
Write-Output 'Build exited 0xC0000142; waiting 60 seconds before the one allowed retry.'
Start-Sleep -Seconds 60
continue
}
if ($Process.ExitCode -ne 0) {
Write-Output "Build failed exit_code=$($Process.ExitCode) stdout=$Stdout stderr=$Stderr"
if ($CompilerStdout) { Write-Output $CompilerStdout }
if ($CompilerStderr) { Write-Output $CompilerStderr }
throw 'Sharp mesher spike build failed.'
}
} while ($Process.ExitCode -eq -1073741502 -and $Attempt -lt 2)
Write-Output "Build passed exe=$Exe elapsed_seconds=$([Math]::Round(([DateTime]::UtcNow - $StartAt).TotalSeconds,2)) priority=BelowNormal affinity=0xFF00"
@@ -0,0 +1,125 @@
# World assembler M2 round 1: sharp-mesher decision spike
Date: 2026-10-10
Status: **Gate failed; no production mesher selected.** Hybrid is the leading candidate for an in-game follow-up, but it does not pass independent tile seams. Every candidate fails the tested LOD0/LOD1 seam.
## Decision
At 25 cm, feature-aware DC substantially reduces sharp-feature field residual and preserves hard normal splits. The hybrid also leaves the smooth cave-chamber wall on the MC path, where its measured residual matches MC. Its 8-worker mesh cost is about 4.4% above MC.
Those gains are not enough to choose it: hybrid fails all 500 tagged-shape independent tile-border cases, and DC everywhere fails all 625. MC passes all 625 same-LOD tile-border cases but retains large sharp-feature bevel residual and also fails every LOD transition case. No candidate passes both border gates. Hold architectural recipes until a real border/transition implementation passes.
Round 2 should take MC and hybrid into Unreal as opt-in, versioned recipe candidates. Hybrid is the primary quality candidate; MC is the baseline and organic fallback. Bring neighbor halo/ownership rules, a 2:1 transition template or equivalent, and actual collision cooking. Compare the five fixtures in-game with matched cameras, including same-LOD and LOD0/LOD1 borders. Keep DC-everywhere as a reference unless its organic quality and border results improve.
## Test setup
The standalone C++20 harness uses the plugin’s canonical marching-cubes lookup table and 25 cm cells. It builds a 32³-cell, 8 m tile for each fixture. The lattice phase sweep translates each analytic field by 0, 5, 10, 15, and 20 cm on each axis: 125 phases per fixture and method, 1,875 phase meshes total.
The fixtures are a union-of-boxes 90° corner, a box doorway with an analytic void, a one-metre riser, a cliff ledge/overhang, and a smooth spherical cave-chamber cavity. The chamber has negative density in solid rock and positive density in its air cavity, following the plugin sign convention. It is an analytic curved proxy, not a sampled native cave or SurfaceWorld patch.
The candidates are:
- **MC:** table-driven marching cubes with linear edge roots and interpolated central-difference field normals. This preserves MC geometry while giving it a fair smooth-normal path.
- **Hybrid:** untagged connected surface components use MC. Components connected to analytic sharp-plane feature tags use dual contouring.
- **DC:** dual contouring on every surface component, including the cave-chamber wall.
DC uses Hermite roots, a fixed-order 3×3 QEF solve, a small center penalty for rank-deficient constraints, cell-bound clamping, deterministic feature selection, and position quantization at 1/65,536 voxel. All candidates share a 0.01 mm positive bias for exact-zero lattice samples. No LOD transition template is implemented in this spike; independent fine/coarse half meshes are compared directly to expose that gate.
Phase and same-resolution seam sweeps use the five phase offsets above. Same-LOD borders compare independently generated 8 m tiles on either side of x=0. LOD borders compare an independently generated 25 cm half tile against an independently generated 50 cm half tile. Border contours are compared in both directions at five points per segment. A missing contour is recorded as unmatched; the topological audit counts geometric boundary and nonmanifold edges after merging the independent meshes.
## Surface residual and crease normals
The residual is the absolute analytic field value, in metres, sampled at each triangle’s three vertices, three edge midpoints, and centroid. The table shows the equal-weight mean of the 125 phase means and the maximum sample over all phases. Repeated vertex samples count repeatedly. This is a useful analytic-field residual, not a symmetric Hausdorff distance.
| Fixture | MC mean / max (cm) | Hybrid mean / max (cm) | DC mean / max (cm) |
|---|---:|---:|---:|
| 90° corner | 0.51 / 16.67 | 0.17 / 6.25 | 0.17 / 6.25 |
| Doorway | 0.99 / 16.67 | 0.41 / 12.50 | 0.41 / 12.50 |
| One-metre riser | 0.46 / 16.67 | 0.15 / 6.25 | 0.15 / 6.25 |
| Cliff ledge / overhang | 0.49 / 16.67 | 0.16 / 6.25 | 0.16 / 6.25 |
| Cave-chamber wall | 0.39 / 1.17 | 0.39 / 1.17 | 0.50 / 4.75 |
MC’s worst sharp residual is 16.67 cm at this phase sample, close to the expected 17.68 cm half-diagonal bevel bound. Hybrid/DC reduce the sharp residual while DC-everywhere is rougher on the smooth chamber than either MC path.
Crease split rate counts adjacent triangles with distinct analytic feature labels and face-normal angle above 30°. A split is counted when their average interpolated/vertex normals also differ by more than 30°.
| Fixture | MC split rate | Hybrid split rate | DC split rate |
|---|---:|---:|---:|
| 90° corner | 11.83% | 99.93% | 99.93% |
| Doorway | 24.31% | 100.00% | 100.00% |
| One-metre riser | 12.98% | 99.99% | 99.99% |
| Cliff ledge / overhang | 15.18% | 99.89% | 99.89% |
| Cave-chamber wall | no tagged crease | no tagged crease | no tagged crease |
## Topology and independent borders
The phase meshes themselves have no nonmanifold edges. MC has 0 open-edge meshes in 625 fixture/phase cases. Hybrid and DC each have 36 open-edge meshes in 625, summing to 116 open edges across those cases.
Independent-border results aggregate 625 fixture/phase cases per method. “Bad” means unmatched seam probes, boundary edges, or nonmanifold edges. No nonmanifold edges were found.
| Method | Same-LOD bad cases / 625 | Same-LOD open edges | Same-LOD unmatched probes | LOD0/LOD1 bad cases / 625 | LOD0/LOD1 open edges | LOD0/LOD1 unmatched probes |
|---|---:|---:|---:|---:|---:|---:|
| MC | 0 | 0 | 0 | 625 | 75,494 | 0 |
| Hybrid | 500 | 85,632 | 3,484 | 625 | 75,794 | 5,819 |
| DC | 625 | 101,440 | 3,609 | 625 | 75,790 | 6,128 |
MC’s same-LOD contour match is exact in all cases: zero measured contour distance and zero merged open edges. Hybrid passes only the 125 smooth chamber cases; its 500 tagged-shape cases have open edges. DC also fails the chamber border.
Every LOD0/LOD1 case has open boundary edges. For MC, the per-case maximum sampled contour distance has an 18.57 cm median, 26.41 cm 95th percentile, and 31.82 cm worst case; 39 of 625 cases exceed 25 cm. Hybrid has complete contour comparisons in only 130 cases; five of those exceed 25 cm, with a 560.47 cm worst finite comparison. DC has complete contour comparisons in only five cases, with a 560.47 cm worst finite comparison. The large finite outliers and unmatched probes are direct failures, not a pass hidden by averages. The spike has no transition templates, so these results are a transition gate, not a claim that any candidate’s final transition design has been tuned.
## Cost, determinism, and collision proxy
Each timing uses an already sampled 32³-cell field grid, three mesh builds, and the median build time. Field sampling, Unreal scheduling, upload, collision cooking, and rendering are excluded. All values below average the five fixtures.
| Worker count | MC mean ms/tile | Hybrid mean ms/tile | DC mean ms/tile |
|---:|---:|---:|---:|
| 1 | 16.308 | 17.375 | 16.269 |
| 8 | 14.275 | 14.907 | 13.996 |
| 16 | 14.555 | 15.291 | 14.129 |
At W8, hybrid is 4.4% slower and DC is 2.0% faster than MC. Mean triangle counts are 3,454.4 / 3,456.8 / 3,457.6 for MC / Hybrid / DC. Mean vertex counts are 1,728.8 / 3,469.2 / 1,985.6. Hybrid’s feature-split path therefore roughly doubles output vertices for negligible triangle change.
All 45 determinism rows (five fixtures × three methods × three worker counts) match the W1 reference for that fixture and method: zero cross-worker hash mismatches. W16 is a 16-worker software pool running with process affinity limited to eight logical CPUs.
The standalone collision proxy is the same emitted vertex/triangle mesh. All 45 proxy hashes match the visual mesh hash and report 0 m geometry delta. This verifies the candidate mesh handoff only; there is no independent collision simplification, Unreal physics cook, or contact test in round 1.
## Captures
The [contact sheet](results/captures/contact_sheet.jpg) is an offline orthographic rasterization of matched MC, Hybrid, and DC meshes for all five fixtures, plus one LOD seam view per method. It is 864 × 1,476 pixels. The 18 individual captures are 288 × 246 pixels. There are 19 JPEGs including the sheet; all are under 300 KB, and the largest is the 128,260-byte contact sheet. No viewer was launched.
Capture files:
- [90° corner — MC](results/captures/corner90_MC.jpg), [Hybrid](results/captures/corner90_Hybrid.jpg), [DC](results/captures/corner90_DC.jpg)
- [Doorway — MC](results/captures/doorway_MC.jpg), [Hybrid](results/captures/doorway_Hybrid.jpg), [DC](results/captures/doorway_DC.jpg)
- [One-metre riser — MC](results/captures/riser1m_MC.jpg), [Hybrid](results/captures/riser1m_Hybrid.jpg), [DC](results/captures/riser1m_DC.jpg)
- [Cliff ledge — MC](results/captures/cliff_overhang_MC.jpg), [Hybrid](results/captures/cliff_overhang_Hybrid.jpg), [DC](results/captures/cliff_overhang_DC.jpg)
- [Cave chamber — MC](results/captures/organic_cave_chamber_MC.jpg), [Hybrid](results/captures/organic_cave_chamber_Hybrid.jpg), [DC](results/captures/organic_cave_chamber_DC.jpg)
- [LOD seam — MC](results/captures/lodseam_MC.jpg), [Hybrid](results/captures/lodseam_Hybrid.jpg), [DC](results/captures/lodseam_DC.jpg)
## Run ledger and constraints
| Run/check | Result |
|---|---|
| Final MSVC C++20 build | Passed in 4.42 s, BelowNormal, affinity 0xFF00. |
| Final phase/cost/determinism/seam run | Passed in 114.208 s; 1,875 phase meshes and 1,875 independent seam cases, BelowNormal, affinity 0xFF00. |
| Final capture renderer | Passed in 2.61 s, BelowNormal, affinity 0xFF00; 19 files under the 300 KB cap. |
| Earlier phase run 1 | Exited normally in 31.14 s; exact-zero lattice degeneracies exposed topology defects. |
| Earlier phase run 2 | Exited normally in 31.11 s after adding the deterministic exact-zero bias; DC still had boundary defects. |
| Earlier phase run 3 | Exited normally in 35.07 s after splitting disconnected cell patches; remaining boundary defects kept it from acceptance. |
| Earlier phase run 4 | Exited normally in 36.55 s after making MC endpoint-gradient interpolation explicit; used for normal-split comparison. |
| Earlier seam run | Exited normally in 74.21 s, but clipped a shared mesh and did not test independently generated borders. It was superseded by the final independent-neighbor seam run. |
| Tool validation | One early PowerShell parser check used an invalid reference argument. Two later analysis-only PowerShell aggregation commands had syntax errors. All were corrected; none launched a mesher process. One early MSVC environment setup printed a nonfatal vswhere helper warning; compilation succeeded, and the final build emitted no warning. |
Every final build/test/render process used BelowNormal priority and affinity 0xFF00, limiting it to eight of the 16 logical processors. The build/test/render scripts enforce an 8 GB private-memory guard and 15-minute timeout. No process hit either limit, crashed, or needed a 0xC0000142 retry. The earlier phase runs above exited normally; their topology/seam measurements failed or were superseded as noted. No viewer or owner editor was opened. No owner project was built and no .uasset was touched.
## Files added for the spike
New implementation and launch files:
- [SharpMesherSpike.cpp](SharpMesherSpike.cpp)
- [BuildSharpMesherSpike.ps1](BuildSharpMesherSpike.ps1)
- [RunSharpMesherSpike.ps1](RunSharpMesherSpike.ps1)
- [RenderSharpMesherCaptures.ps1](RenderSharpMesherCaptures.ps1)
- [RenderSharpMesherCaptures.py](RenderSharpMesherCaptures.py)
The report, CSV measurements, exported OBJ meshes, offline captures, executable/object build outputs, and run logs are also in this WorldAssemblerM2 directory. No files under Source were changed.
@@ -0,0 +1,53 @@
[CmdletBinding()]
param()
$ErrorActionPreference = 'Stop'
$WorkDir = [IO.Path]::GetFullPath($PSScriptRoot)
$Python = (Get-Command python.exe -ErrorAction Stop).Source
$Script = Join-Path $WorkDir 'RenderSharpMesherCaptures.py'
$Stdout = Join-Path $WorkDir 'render.stdout.log'
$Stderr = Join-Path $WorkDir 'render.stderr.log'
$Runner = Get-Process -Id $PID
$Runner.PriorityClass = 'BelowNormal'
$Runner.ProcessorAffinity = [IntPtr]0xFF00
$Info = [Diagnostics.ProcessStartInfo]::new()
$Info.FileName = $Python
$Info.Arguments = ('"{0}"' -f $Script)
$Info.WorkingDirectory = $WorkDir
$Info.UseShellExecute = $false
$Info.CreateNoWindow = $true
$Info.RedirectStandardOutput = $true
$Info.RedirectStandardError = $true
$Process = [Diagnostics.Process]::new()
$Process.StartInfo = $Info
if (-not $Process.Start()) { throw 'Could not start capture renderer.' }
$Process.PriorityClass = [Diagnostics.ProcessPriorityClass]::BelowNormal
$Process.ProcessorAffinity = [IntPtr]0xFF00
$StartAt = [DateTime]::UtcNow
$OutputTask = $Process.StandardOutput.ReadToEndAsync()
$ErrorTask = $Process.StandardError.ReadToEndAsync()
$TimedOut = $false
$OverMemory = $false
$EightGB = 8L * 1024L * 1024L * 1024L
while (-not $Process.HasExited) {
$Process.Refresh()
if ($Process.PrivateMemorySize64 -ge $EightGB) { $OverMemory = $true; break }
if (([DateTime]::UtcNow - $StartAt).TotalSeconds -ge 900) { $TimedOut = $true; break }
Start-Sleep -Milliseconds 250
}
if ($TimedOut -or $OverMemory) { try { $Process.Kill() } catch {} }
$Process.WaitForExit()
$RenderStdout = $OutputTask.GetAwaiter().GetResult()
$RenderStderr = $ErrorTask.GetAwaiter().GetResult()
[IO.File]::WriteAllText($Stdout, $RenderStdout)
[IO.File]::WriteAllText($Stderr, $RenderStderr)
if ($TimedOut) { throw "Capture rendering exceeded 15 minutes. Logs: $Stdout / $Stderr" }
if ($OverMemory) { throw "Capture renderer reached the 8 GB private-memory cap. Logs: $Stdout / $Stderr" }
if ($Process.ExitCode -ne 0) {
Write-Output "Capture rendering failed exit_code=$($Process.ExitCode) stdout=$Stdout stderr=$Stderr"
if ($RenderStdout) { Write-Output $RenderStdout }
if ($RenderStderr) { Write-Output $RenderStderr }
throw 'Matched capture rendering failed.'
}
Write-Output "Capture render passed elapsed_seconds=$([Math]::Round(([DateTime]::UtcNow - $StartAt).TotalSeconds,2)) priority=BelowNormal affinity=0xFF00 stdout=$Stdout stderr=$Stderr"
@@ -0,0 +1,205 @@
#!/usr/bin/env python3
"""Render the standalone spike's exported meshes from matched orthographic views."""
from __future__ import annotations
import math
from pathlib import Path
from PIL import Image, ImageDraw
ROOT = Path(__file__).resolve().parent
RESULTS = ROOT / "results"
CAPTURES = RESULTS / "captures"
CAPTURES.mkdir(parents=True, exist_ok=True)
METHODS = ("MC", "Hybrid", "DC")
PIECES = (
("corner90", "90 degree corner"),
("doorway", "doorway"),
("riser1m", "one metre riser"),
("cliff_overhang", "cliff ledge / overhang"),
("organic_cave_chamber", "organic cave chamber wall"),
)
WIDTH, HEIGHT = 288, 216
BG = (239, 242, 244)
INK = (36, 44, 51)
SEAM_FINE = (73, 133, 174)
SEAM_COARSE = (188, 112, 68)
def normalize(v):
n = math.sqrt(sum(c * c for c in v))
return tuple(c / n for c in v) if n > 1.0e-12 else (0.0, 0.0, 1.0)
def dot(a, b):
return sum(x * y for x, y in zip(a, b))
def cross(a, b):
return (a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0])
def read_obj(path: Path):
vertices, normals, faces = [], [], []
for line in path.read_text(encoding="ascii").splitlines():
if line.startswith("v "):
vertices.append(tuple(map(float, line.split()[1:4])))
elif line.startswith("vn "):
normals.append(tuple(map(float, line.split()[1:4])))
elif line.startswith("f "):
face = []
for item in line.split()[1:4]:
fields = item.split("//")
face.append((int(fields[0]) - 1, int(fields[1]) - 1 if len(fields) > 1 else int(fields[0]) - 1))
faces.append(tuple(face))
return vertices, normals, faces
def project_mesh(vertices, faces, seam=False):
# One view is reused for every candidate in a row: azimuth 38°, elevation 27°.
target = (0.0, 0.0, 0.0)
view_dir = normalize((8.0, -10.0, 7.0))
forward = tuple(-c for c in view_dir)
right = normalize(cross(forward, (0.0, 0.0, 1.0)))
up = normalize(cross(right, forward))
scale = 20.2
projected = []
for p in vertices:
rel = tuple(p[i] - target[i] for i in range(3))
projected.append((WIDTH * 0.5 + dot(rel, right) * scale,
HEIGHT * 0.51 - dot(rel, up) * scale,
dot(rel, forward)))
return projected
def raster(path: Path, color_by_x=False):
vertices, normals, faces = read_obj(path)
if color_by_x:
clipped_vertices, clipped_normals, clipped_faces = [], [], []
for face in faces:
center_x = sum(vertices[i][0] for i, _ in face) / 3.0
left = center_x < 0.0
polygon = [(vertices[vi], normals[ni] if ni < len(normals) else (0.0, 0.0, 1.0)) for vi, ni in face]
clipped = []
for i, (pa, na) in enumerate(polygon):
pb, nb = polygon[(i + 1) % len(polygon)]
inside_a = pa[0] <= 0.0 if left else pa[0] >= 0.0
inside_b = pb[0] <= 0.0 if left else pb[0] >= 0.0
if inside_a:
clipped.append((pa, na))
if inside_a != inside_b:
t = (0.0 - pa[0]) / (pb[0] - pa[0])
point = tuple(pa[k] + t * (pb[k] - pa[k]) for k in range(3))
normal = normalize(tuple(na[k] + t * (nb[k] - na[k]) for k in range(3)))
clipped.append((point, normal))
if len(clipped) < 3:
continue
first = len(clipped_vertices)
for point, normal in clipped:
clipped_vertices.append(point)
clipped_normals.append(normal)
for i in range(1, len(clipped) - 1):
clipped_faces.append(((first, first), (first + i, first + i), (first + i + 1, first + i + 1)))
vertices, normals, faces = clipped_vertices, clipped_normals, clipped_faces
projected = project_mesh(vertices, faces, color_by_x)
pixels = Image.new("RGB", (WIDTH, HEIGHT), BG)
zbuf = [float("inf")] * (WIDTH * HEIGHT)
px = pixels.load()
light = normalize((-0.45, -0.55, 0.75))
for face in faces:
ids = [v for v, _ in face]
points = [projected[i] for i in ids]
x0 = max(0, int(math.floor(min(p[0] for p in points))))
x1 = min(WIDTH - 1, int(math.ceil(max(p[0] for p in points))))
y0 = max(0, int(math.floor(min(p[1] for p in points))))
y1 = min(HEIGHT - 1, int(math.ceil(max(p[1] for p in points))))
if x0 > x1 or y0 > y1:
continue
(ax, ay, az), (bx, by, bz), (cx, cy, cz) = points
denom = (by - cy) * (ax - cx) + (cx - bx) * (ay - cy)
if abs(denom) < 1.0e-9:
continue
face_normals = [normals[ni] if ni < len(normals) else (0.0, 0.0, 1.0) for _, ni in face]
for y in range(y0, y1 + 1):
for x in range(x0, x1 + 1):
sx, sy = x + 0.5, y + 0.5
u = ((by - cy) * (sx - cx) + (cx - bx) * (sy - cy)) / denom
v = ((cy - ay) * (sx - cx) + (ax - cx) * (sy - cy)) / denom
w = 1.0 - u - v
if u < -1.0e-8 or v < -1.0e-8 or w < -1.0e-8:
continue
depth = u * az + v * bz + w * cz
idx = y * WIDTH + x
if depth >= zbuf[idx]:
continue
zbuf[idx] = depth
n = normalize(tuple(u * face_normals[0][k] + v * face_normals[1][k] + w * face_normals[2][k] for k in range(3)))
shade = 0.36 + 0.64 * max(0.0, dot(n, light))
if color_by_x:
center_x = sum(vertices[i][0] for i in ids) / 3.0
base = SEAM_FINE if center_x < 0.0 else SEAM_COARSE
else:
base = (130, 145, 151)
px[x, y] = tuple(max(0, min(255, int(c * shade))) for c in base)
if color_by_x:
draw = ImageDraw.Draw(pixels)
# Project a visible segment on the LOD boundary plane, x=0.
seam_world = [(-0.0, y, z) for y, z in ((-4, -4), (4, -4), (4, 4), (-4, 4), (-4, -4))]
seam_points = project_mesh(seam_world, [], True)
draw.line([(p[0], p[1]) for p in seam_points], fill=(181, 55, 49), width=2)
return pixels
def capture_title(image: Image.Image, title: str, subtitle: str):
canvas = Image.new("RGB", (WIDTH, HEIGHT + 30), BG)
canvas.paste(image, (0, 30))
draw = ImageDraw.Draw(canvas)
draw.rectangle((0, 0, WIDTH, 29), fill=(31, 42, 50))
draw.text((8, 4), title, fill=(255, 255, 255))
draw.text((8, 16), subtitle, fill=(196, 210, 218))
return canvas
def main():
captures = []
for key, label in PIECES:
row = []
for method in METHODS:
obj = RESULTS / "meshes" / f"{key}_{method}.obj"
image = capture_title(raster(obj), label, f"{method} | matched orthographic view")
path = CAPTURES / f"{key}_{method}.jpg"
image.save(path, quality=86, optimize=True)
row.append(image)
captures.append(row)
seam_row = []
for method in METHODS:
obj = RESULTS / "meshes" / f"lodseam_cliff_overhang_{method}.obj"
image = capture_title(raster(obj, color_by_x=True), "LOD0 to LOD1 cliff seam", f"{method} | fine left, coarse right, x=0 seam")
path = CAPTURES / f"lodseam_{method}.jpg"
image.save(path, quality=86, optimize=True)
seam_row.append(image)
captures.append(seam_row)
sheet = Image.new("RGB", (WIDTH * 3, (HEIGHT + 30) * 6), (221, 227, 230))
draw = ImageDraw.Draw(sheet)
for row_index, row in enumerate(captures):
for col_index, image in enumerate(row):
x, y = col_index * WIDTH, row_index * (HEIGHT + 30)
sheet.paste(image, (x, y))
draw.rectangle((x, y, x + WIDTH - 1, y + HEIGHT + 29), outline=(73, 83, 89), width=1)
sheet_path = CAPTURES / "contact_sheet.jpg"
sheet.save(sheet_path, quality=86, optimize=True)
oversized = []
for path in sorted(CAPTURES.glob("*.jpg")):
size = path.stat().st_size
print(f"capture file={path.name} bytes={size}")
if size >= 300_000:
oversized.append((path.name, size))
if oversized:
raise RuntimeError(f"captures exceed 300 KB: {oversized}")
print(f"contact_sheet={sheet_path} bytes={sheet_path.stat().st_size} image_count={len(list(CAPTURES.glob('*.jpg')))}")
if __name__ == "__main__":
main()
@@ -0,0 +1,69 @@
[CmdletBinding()]
param(
[ValidateRange(2,5)][int]$PhaseCount = 5
)
$ErrorActionPreference = 'Stop'
$WorkDir = [IO.Path]::GetFullPath($PSScriptRoot)
$Exe = Join-Path $WorkDir 'bin\SharpMesherSpike.exe'
$OutDir = Join-Path $WorkDir 'results'
if (-not (Test-Path -LiteralPath $Exe -PathType Leaf)) { throw "Spike executable missing: $Exe" }
[IO.Directory]::CreateDirectory($OutDir) | Out-Null
$Runner = Get-Process -Id $PID
$Runner.PriorityClass = 'BelowNormal'
$Runner.ProcessorAffinity = [IntPtr]0xFF00
$Affinity = [IntPtr]0xFF00
$Stdout = Join-Path $WorkDir 'run.stdout.log'
$Stderr = Join-Path $WorkDir 'run.stderr.log'
$EightGB = 8L * 1024L * 1024L * 1024L
$Attempt = 0
do {
$Attempt++
Remove-Item -LiteralPath $Stdout, $Stderr -Force -ErrorAction SilentlyContinue
$Info = [Diagnostics.ProcessStartInfo]::new()
$Info.FileName = $Exe
$Info.Arguments = ('--out="{0}" --phase-count={1}' -f $OutDir, $PhaseCount)
$Info.WorkingDirectory = [IO.Path]::GetFullPath((Join-Path $WorkDir '..\..'))
$Info.UseShellExecute = $false
$Info.CreateNoWindow = $true
$Info.RedirectStandardOutput = $true
$Info.RedirectStandardError = $true
$Process = [Diagnostics.Process]::new()
$Process.StartInfo = $Info
if (-not $Process.Start()) { throw 'Could not start SharpMesherSpike.exe.' }
$Process.PriorityClass = [Diagnostics.ProcessPriorityClass]::BelowNormal
$Process.ProcessorAffinity = $Affinity
$StartAt = [DateTime]::UtcNow
$OutputTask = $Process.StandardOutput.ReadToEndAsync()
$ErrorTask = $Process.StandardError.ReadToEndAsync()
$TimedOut = $false
$OverMemory = $false
while (-not $Process.HasExited) {
$Process.Refresh()
if ($Process.PrivateMemorySize64 -ge $EightGB) { $OverMemory = $true; break }
if (([DateTime]::UtcNow - $StartAt).TotalSeconds -ge 900) { $TimedOut = $true; break }
Start-Sleep -Milliseconds 250
}
if ($TimedOut -or $OverMemory) { try { $Process.Kill() } catch {} }
$Process.WaitForExit()
$RunStdout = $OutputTask.GetAwaiter().GetResult()
$RunStderr = $ErrorTask.GetAwaiter().GetResult()
[IO.File]::WriteAllText($Stdout, $RunStdout)
[IO.File]::WriteAllText($Stderr, $RunStderr)
if ($TimedOut) { throw "Spike exceeded 15 minutes and was stopped. Logs: $Stdout / $Stderr" }
if ($OverMemory) { throw "Spike reached the 8 GB private-memory cap and was stopped. Logs: $Stdout / $Stderr" }
if ($Process.ExitCode -eq -1073741502 -and $Attempt -eq 1) {
Write-Output 'Spike exited 0xC0000142; waiting 60 seconds before the one allowed retry.'
Start-Sleep -Seconds 60
continue
}
if ($Process.ExitCode -ne 0) {
Write-Output "Spike failed exit_code=$($Process.ExitCode) stdout=$Stdout stderr=$Stderr"
if ($RunStdout) { Write-Output $RunStdout }
if ($RunStderr) { Write-Output $RunStderr }
throw 'Sharp mesher spike failed.'
}
} while ($Process.ExitCode -eq -1073741502 -and $Attempt -lt 2)
Write-Output "Spike passed elapsed_seconds=$([Math]::Round(([DateTime]::UtcNow - $StartAt).TotalSeconds,2)) phase_count=$PhaseCount priority=BelowNormal affinity=0xFF00 stdout=$Stdout stderr=$Stderr"
File diff suppressed because it is too large. Load diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

@@ -0,0 +1,46 @@
piece,method,workers,cell_count,triangles,vertices,mesh_ms_median_3,mesh_hash,collision_proxy_hash,collision_geometry_delta_m
corner90,MC,1,32768,3192,1598,16.5302,0x3e35202e741326da,0x3e35202e741326da,0
corner90,MC,8,32768,3192,1598,14.1565,0x3e35202e741326da,0x3e35202e741326da,0
corner90,MC,16,32768,3192,1598,14.5744,0x3e35202e741326da,0x3e35202e741326da,0
corner90,Hybrid,1,32768,3196,3473,17.3756,0x1c68ed55a97a2b70,0x1c68ed55a97a2b70,0
corner90,Hybrid,8,32768,3196,3473,14.7298,0x1c68ed55a97a2b70,0x1c68ed55a97a2b70,0
corner90,Hybrid,16,32768,3196,3473,15.1832,0x1c68ed55a97a2b70,0x1c68ed55a97a2b70,0
corner90,DC,1,32768,3196,1875,16.346,0x27eb83e4bcb5d532,0x27eb83e4bcb5d532,0
corner90,DC,8,32768,3196,1875,13.5541,0x27eb83e4bcb5d532,0x27eb83e4bcb5d532,0
corner90,DC,16,32768,3196,1875,13.9794,0x27eb83e4bcb5d532,0x27eb83e4bcb5d532,0
doorway,MC,1,32768,2172,1086,15.0354,0x5091c48df9516232,0x5091c48df9516232,0
doorway,MC,8,32768,2172,1086,13.7949,0x5091c48df9516232,0x5091c48df9516232,0
doorway,MC,16,32768,2172,1086,13.9519,0x5091c48df9516232,0x5091c48df9516232,0
doorway,Hybrid,1,32768,2172,2462,15.8758,0xc70ca49841d4ad46,0xc70ca49841d4ad46,0
doorway,Hybrid,8,32768,2172,2462,14.137,0xc70ca49841d4ad46,0xc70ca49841d4ad46,0
doorway,Hybrid,16,32768,2172,2462,14.5053,0xc70ca49841d4ad46,0xc70ca49841d4ad46,0
doorway,DC,1,32768,2172,1376,15.0068,0x7ec18c54d9af2487,0x7ec18c54d9af2487,0
doorway,DC,8,32768,2172,1376,13.2934,0x7ec18c54d9af2487,0x7ec18c54d9af2487,0
doorway,DC,16,32768,2172,1376,13.2488,0x7ec18c54d9af2487,0x7ec18c54d9af2487,0
riser1m,MC,1,32768,4428,2216,17.6744,0x930603791a29a779,0x930603791a29a779,0
riser1m,MC,8,32768,4428,2216,15.0862,0x930603791a29a779,0x930603791a29a779,0
riser1m,MC,16,32768,4428,2216,15.501,0x930603791a29a779,0x930603791a29a779,0
riser1m,Hybrid,1,32768,4432,4775,19.222,0x7a80c8099ed5e2df,0x7a80c8099ed5e2df,0
riser1m,Hybrid,8,32768,4432,4775,15.9639,0x7a80c8099ed5e2df,0x7a80c8099ed5e2df,0
riser1m,Hybrid,16,32768,4432,4775,16.2443,0x7a80c8099ed5e2df,0x7a80c8099ed5e2df,0
riser1m,DC,1,32768,4432,2559,17.6231,0x7a58ef3dd552662f,0x7a58ef3dd552662f,0
riser1m,DC,8,32768,4432,2559,15.5117,0x7a58ef3dd552662f,0x7a58ef3dd552662f,0
riser1m,DC,16,32768,4432,2559,15.5445,0x7a58ef3dd552662f,0x7a58ef3dd552662f,0
cliff_overhang,MC,1,32768,5036,2520,18.5265,0x1881bd75ddc7e7de,0x1881bd75ddc7e7de,0
cliff_overhang,MC,8,32768,5036,2520,15.2582,0x1881bd75ddc7e7de,0x1881bd75ddc7e7de,0
cliff_overhang,MC,16,32768,5036,2520,15.3717,0x1881bd75ddc7e7de,0x1881bd75ddc7e7de,0
cliff_overhang,Hybrid,1,32768,5040,5412,19.8127,0x985b8497adfe9dec,0x985b8497adfe9dec,0
cliff_overhang,Hybrid,8,32768,5040,5412,15.9717,0x985b8497adfe9dec,0x985b8497adfe9dec,0
cliff_overhang,Hybrid,16,32768,5040,5412,16.4467,0x985b8497adfe9dec,0x985b8497adfe9dec,0
cliff_overhang,DC,1,32768,5040,2892,18.232,0x4723adced541eab2,0x4723adced541eab2,0
cliff_overhang,DC,8,32768,5040,2892,14.6485,0x4723adced541eab2,0x4723adced541eab2,0
cliff_overhang,DC,16,32768,5040,2892,14.7275,0x4723adced541eab2,0x4723adced541eab2,0
organic_cave_chamber,MC,1,32768,2444,1224,13.7729,0x50031a0a691ec6f4,0x50031a0a691ec6f4,0
organic_cave_chamber,MC,8,32768,2444,1224,13.0782,0x50031a0a691ec6f4,0x50031a0a691ec6f4,0
organic_cave_chamber,MC,16,32768,2444,1224,13.3739,0x50031a0a691ec6f4,0x50031a0a691ec6f4,0
organic_cave_chamber,Hybrid,1,32768,2444,1224,14.5881,0x50031a0a691ec6f4,0x50031a0a691ec6f4,0
organic_cave_chamber,Hybrid,8,32768,2444,1224,13.732,0x50031a0a691ec6f4,0x50031a0a691ec6f4,0
organic_cave_chamber,Hybrid,16,32768,2444,1224,14.0734,0x50031a0a691ec6f4,0x50031a0a691ec6f4,0
organic_cave_chamber,DC,1,32768,2448,1226,14.1388,0x317a60749cc26f6a,0x317a60749cc26f6a,0
organic_cave_chamber,DC,8,32768,2448,1226,12.971,0x317a60749cc26f6a,0x317a60749cc26f6a,0
organic_cave_chamber,DC,16,32768,2448,1226,13.1444,0x317a60749cc26f6a,0x317a60749cc26f6a,0
1 piece method workers cell_count triangles vertices mesh_ms_median_3 mesh_hash collision_proxy_hash collision_geometry_delta_m
2 corner90 MC 1 32768 3192 1598 16.5302 0x3e35202e741326da 0x3e35202e741326da 0
3 corner90 MC 8 32768 3192 1598 14.1565 0x3e35202e741326da 0x3e35202e741326da 0
4 corner90 MC 16 32768 3192 1598 14.5744 0x3e35202e741326da 0x3e35202e741326da 0
5 corner90 Hybrid 1 32768 3196 3473 17.3756 0x1c68ed55a97a2b70 0x1c68ed55a97a2b70 0
6 corner90 Hybrid 8 32768 3196 3473 14.7298 0x1c68ed55a97a2b70 0x1c68ed55a97a2b70 0
7 corner90 Hybrid 16 32768 3196 3473 15.1832 0x1c68ed55a97a2b70 0x1c68ed55a97a2b70 0
8 corner90 DC 1 32768 3196 1875 16.346 0x27eb83e4bcb5d532 0x27eb83e4bcb5d532 0
9 corner90 DC 8 32768 3196 1875 13.5541 0x27eb83e4bcb5d532 0x27eb83e4bcb5d532 0
10 corner90 DC 16 32768 3196 1875 13.9794 0x27eb83e4bcb5d532 0x27eb83e4bcb5d532 0
11 doorway MC 1 32768 2172 1086 15.0354 0x5091c48df9516232 0x5091c48df9516232 0
12 doorway MC 8 32768 2172 1086 13.7949 0x5091c48df9516232 0x5091c48df9516232 0
13 doorway MC 16 32768 2172 1086 13.9519 0x5091c48df9516232 0x5091c48df9516232 0
14 doorway Hybrid 1 32768 2172 2462 15.8758 0xc70ca49841d4ad46 0xc70ca49841d4ad46 0
15 doorway Hybrid 8 32768 2172 2462 14.137 0xc70ca49841d4ad46 0xc70ca49841d4ad46 0
16 doorway Hybrid 16 32768 2172 2462 14.5053 0xc70ca49841d4ad46 0xc70ca49841d4ad46 0
17 doorway DC 1 32768 2172 1376 15.0068 0x7ec18c54d9af2487 0x7ec18c54d9af2487 0
18 doorway DC 8 32768 2172 1376 13.2934 0x7ec18c54d9af2487 0x7ec18c54d9af2487 0
19 doorway DC 16 32768 2172 1376 13.2488 0x7ec18c54d9af2487 0x7ec18c54d9af2487 0
20 riser1m MC 1 32768 4428 2216 17.6744 0x930603791a29a779 0x930603791a29a779 0
21 riser1m MC 8 32768 4428 2216 15.0862 0x930603791a29a779 0x930603791a29a779 0
22 riser1m MC 16 32768 4428 2216 15.501 0x930603791a29a779 0x930603791a29a779 0
23 riser1m Hybrid 1 32768 4432 4775 19.222 0x7a80c8099ed5e2df 0x7a80c8099ed5e2df 0
24 riser1m Hybrid 8 32768 4432 4775 15.9639 0x7a80c8099ed5e2df 0x7a80c8099ed5e2df 0
25 riser1m Hybrid 16 32768 4432 4775 16.2443 0x7a80c8099ed5e2df 0x7a80c8099ed5e2df 0
26 riser1m DC 1 32768 4432 2559 17.6231 0x7a58ef3dd552662f 0x7a58ef3dd552662f 0
27 riser1m DC 8 32768 4432 2559 15.5117 0x7a58ef3dd552662f 0x7a58ef3dd552662f 0
28 riser1m DC 16 32768 4432 2559 15.5445 0x7a58ef3dd552662f 0x7a58ef3dd552662f 0
29 cliff_overhang MC 1 32768 5036 2520 18.5265 0x1881bd75ddc7e7de 0x1881bd75ddc7e7de 0
30 cliff_overhang MC 8 32768 5036 2520 15.2582 0x1881bd75ddc7e7de 0x1881bd75ddc7e7de 0
31 cliff_overhang MC 16 32768 5036 2520 15.3717 0x1881bd75ddc7e7de 0x1881bd75ddc7e7de 0
32 cliff_overhang Hybrid 1 32768 5040 5412 19.8127 0x985b8497adfe9dec 0x985b8497adfe9dec 0
33 cliff_overhang Hybrid 8 32768 5040 5412 15.9717 0x985b8497adfe9dec 0x985b8497adfe9dec 0
34 cliff_overhang Hybrid 16 32768 5040 5412 16.4467 0x985b8497adfe9dec 0x985b8497adfe9dec 0
35 cliff_overhang DC 1 32768 5040 2892 18.232 0x4723adced541eab2 0x4723adced541eab2 0
36 cliff_overhang DC 8 32768 5040 2892 14.6485 0x4723adced541eab2 0x4723adced541eab2 0
37 cliff_overhang DC 16 32768 5040 2892 14.7275 0x4723adced541eab2 0x4723adced541eab2 0
38 organic_cave_chamber MC 1 32768 2444 1224 13.7729 0x50031a0a691ec6f4 0x50031a0a691ec6f4 0
39 organic_cave_chamber MC 8 32768 2444 1224 13.0782 0x50031a0a691ec6f4 0x50031a0a691ec6f4 0
40 organic_cave_chamber MC 16 32768 2444 1224 13.3739 0x50031a0a691ec6f4 0x50031a0a691ec6f4 0
41 organic_cave_chamber Hybrid 1 32768 2444 1224 14.5881 0x50031a0a691ec6f4 0x50031a0a691ec6f4 0
42 organic_cave_chamber Hybrid 8 32768 2444 1224 13.732 0x50031a0a691ec6f4 0x50031a0a691ec6f4 0
43 organic_cave_chamber Hybrid 16 32768 2444 1224 14.0734 0x50031a0a691ec6f4 0x50031a0a691ec6f4 0
44 organic_cave_chamber DC 1 32768 2448 1226 14.1388 0x317a60749cc26f6a 0x317a60749cc26f6a 0
45 organic_cave_chamber DC 8 32768 2448 1226 12.971 0x317a60749cc26f6a 0x317a60749cc26f6a 0
46 organic_cave_chamber DC 16 32768 2448 1226 13.1444 0x317a60749cc26f6a 0x317a60749cc26f6a 0
@@ -0,0 +1,46 @@
piece,method,workers,hash,identical_to_w1
corner90,MC,1,0x3e35202e741326da,true
corner90,MC,8,0x3e35202e741326da,true
corner90,MC,16,0x3e35202e741326da,true
corner90,Hybrid,1,0x1c68ed55a97a2b70,true
corner90,Hybrid,8,0x1c68ed55a97a2b70,true
corner90,Hybrid,16,0x1c68ed55a97a2b70,true
corner90,DC,1,0x27eb83e4bcb5d532,true
corner90,DC,8,0x27eb83e4bcb5d532,true
corner90,DC,16,0x27eb83e4bcb5d532,true
doorway,MC,1,0x5091c48df9516232,true
doorway,MC,8,0x5091c48df9516232,true
doorway,MC,16,0x5091c48df9516232,true
doorway,Hybrid,1,0xc70ca49841d4ad46,true
doorway,Hybrid,8,0xc70ca49841d4ad46,true
doorway,Hybrid,16,0xc70ca49841d4ad46,true
doorway,DC,1,0x7ec18c54d9af2487,true
doorway,DC,8,0x7ec18c54d9af2487,true
doorway,DC,16,0x7ec18c54d9af2487,true
riser1m,MC,1,0x930603791a29a779,true
riser1m,MC,8,0x930603791a29a779,true
riser1m,MC,16,0x930603791a29a779,true
riser1m,Hybrid,1,0x7a80c8099ed5e2df,true
riser1m,Hybrid,8,0x7a80c8099ed5e2df,true
riser1m,Hybrid,16,0x7a80c8099ed5e2df,true
riser1m,DC,1,0x7a58ef3dd552662f,true
riser1m,DC,8,0x7a58ef3dd552662f,true
riser1m,DC,16,0x7a58ef3dd552662f,true
cliff_overhang,MC,1,0x1881bd75ddc7e7de,true
cliff_overhang,MC,8,0x1881bd75ddc7e7de,true
cliff_overhang,MC,16,0x1881bd75ddc7e7de,true
cliff_overhang,Hybrid,1,0x985b8497adfe9dec,true
cliff_overhang,Hybrid,8,0x985b8497adfe9dec,true
cliff_overhang,Hybrid,16,0x985b8497adfe9dec,true
cliff_overhang,DC,1,0x4723adced541eab2,true
cliff_overhang,DC,8,0x4723adced541eab2,true
cliff_overhang,DC,16,0x4723adced541eab2,true
organic_cave_chamber,MC,1,0x50031a0a691ec6f4,true
organic_cave_chamber,MC,8,0x50031a0a691ec6f4,true
organic_cave_chamber,MC,16,0x50031a0a691ec6f4,true
organic_cave_chamber,Hybrid,1,0x50031a0a691ec6f4,true
organic_cave_chamber,Hybrid,8,0x50031a0a691ec6f4,true
organic_cave_chamber,Hybrid,16,0x50031a0a691ec6f4,true
organic_cave_chamber,DC,1,0x317a60749cc26f6a,true
organic_cave_chamber,DC,8,0x317a60749cc26f6a,true
organic_cave_chamber,DC,16,0x317a60749cc26f6a,true
1 piece method workers hash identical_to_w1
2 corner90 MC 1 0x3e35202e741326da true
3 corner90 MC 8 0x3e35202e741326da true
4 corner90 MC 16 0x3e35202e741326da true
5 corner90 Hybrid 1 0x1c68ed55a97a2b70 true
6 corner90 Hybrid 8 0x1c68ed55a97a2b70 true
7 corner90 Hybrid 16 0x1c68ed55a97a2b70 true
8 corner90 DC 1 0x27eb83e4bcb5d532 true
9 corner90 DC 8 0x27eb83e4bcb5d532 true
10 corner90 DC 16 0x27eb83e4bcb5d532 true
11 doorway MC 1 0x5091c48df9516232 true
12 doorway MC 8 0x5091c48df9516232 true
13 doorway MC 16 0x5091c48df9516232 true
14 doorway Hybrid 1 0xc70ca49841d4ad46 true
15 doorway Hybrid 8 0xc70ca49841d4ad46 true
16 doorway Hybrid 16 0xc70ca49841d4ad46 true
17 doorway DC 1 0x7ec18c54d9af2487 true
18 doorway DC 8 0x7ec18c54d9af2487 true
19 doorway DC 16 0x7ec18c54d9af2487 true
20 riser1m MC 1 0x930603791a29a779 true
21 riser1m MC 8 0x930603791a29a779 true
22 riser1m MC 16 0x930603791a29a779 true
23 riser1m Hybrid 1 0x7a80c8099ed5e2df true
24 riser1m Hybrid 8 0x7a80c8099ed5e2df true
25 riser1m Hybrid 16 0x7a80c8099ed5e2df true
26 riser1m DC 1 0x7a58ef3dd552662f true
27 riser1m DC 8 0x7a58ef3dd552662f true
28 riser1m DC 16 0x7a58ef3dd552662f true
29 cliff_overhang MC 1 0x1881bd75ddc7e7de true
30 cliff_overhang MC 8 0x1881bd75ddc7e7de true
31 cliff_overhang MC 16 0x1881bd75ddc7e7de true
32 cliff_overhang Hybrid 1 0x985b8497adfe9dec true
33 cliff_overhang Hybrid 8 0x985b8497adfe9dec true
34 cliff_overhang Hybrid 16 0x985b8497adfe9dec true
35 cliff_overhang DC 1 0x4723adced541eab2 true
36 cliff_overhang DC 8 0x4723adced541eab2 true
37 cliff_overhang DC 16 0x4723adced541eab2 true
38 organic_cave_chamber MC 1 0x50031a0a691ec6f4 true
39 organic_cave_chamber MC 8 0x50031a0a691ec6f4 true
40 organic_cave_chamber MC 16 0x50031a0a691ec6f4 true
41 organic_cave_chamber Hybrid 1 0x50031a0a691ec6f4 true
42 organic_cave_chamber Hybrid 8 0x50031a0a691ec6f4 true
43 organic_cave_chamber Hybrid 16 0x50031a0a691ec6f4 true
44 organic_cave_chamber DC 1 0x317a60749cc26f6a true
45 organic_cave_chamber DC 8 0x317a60749cc26f6a true
46 organic_cave_chamber DC 16 0x317a60749cc26f6a true
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff