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
No files matched your search
@@ -878,3 +878,33 @@ confirm their material directions. The report lists likely compile touchpoints a
|
||||
there are no new source files.
|
||||
|
||||
— 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"
|
||||
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 125 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 9.5 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
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
|
||||
|
@@ -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
|
||||
|