diff --git a/Standalone/Research/WorldAssemblerM3/Research3/REPORT.md b/Standalone/Research/WorldAssemblerM3/Research3/REPORT.md new file mode 100644 index 0000000..897420f --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Research3/REPORT.md @@ -0,0 +1,66 @@ +# Research R3 — floor ownership on an overlapping helical passage + +## Ranked recommendation and experiment + +**Rank 1: resolve the floor owner only from floor-near route candidates.** For a voxel within half a 25 cm voxel of a candidate floor (`abs(candidateFloorZ - qz) <= 0.125 m`), require that candidate to be within the bore radius in XY, then choose the nearest 3D floor point; break exact ties by lower stable segment index. If no such candidate exists, keep the existing floor/player fallback. Keep the tube owner separate: this rule chooses the floor cap, not the tube SDF. + +Do not use a strict `candidateFloorZ <= qz` test. At 12.5 cm sampling, a voxel can round just below its true floor; strict-below then excludes the correct segment and selects the previous segment. In the synthetic test this made a 23.8 cm peak-to-peak floor error. The half-voxel symmetric band retained the correct floor candidate. + +The 0.938 m ripple period in L38 is also the expected chord spacing of a 3 m radius, 20-segment turn: `2 R sin(pi/20) = 0.9386 m`. That ties the observed period to the route control-point interval. The full audit still needs to confirm whether a local floor-owner band removes its ripple. + +### Synthetic measurements + +The helix used the L27 FI Style 2 main-path measurements: 3.0 m mean centerline radius, 3.31 m vertical pitch, 12.95 turns, and 20 route controls per turn. The requested 4.5 m bore radius models the enlarged mouth; the same run was repeated at the 3.0 m mid-bore radius. Queries follow the analytic floor path at 0.125 m arc-length spacing; each position is rounded to a 0.125 m grid. There are 1,984 probes. The table reports peak-to-peak and maximum absolute error of selected floor height minus analytic floor height. + +| Ownership rule | 4.5 m bore p-p / max abs | 3.0 m bore p-p / max abs | +|---|---:|---:| +| Analytic closest helix centerline | 3.372 / 3.372 m | 3.329 / 3.329 m | +| Segment closest centerline | 3.387 / 3.383 m | 3.329 / 3.325 m | +| Nearest 3D floor point | 0.029 / 0.015 m | 0.029 / 0.015 m | +| Floor strictly below voxel, within bore radius | 0.238 / 0.224 m | 0.238 / 0.224 m | +| Floor within ±0.125 m and bore radius | 0.029 / 0.015 m | 0.029 / 0.015 m | +| Highest floor below voxel, within bore radius | 0.282 / 0.224 m | 0.282 / 0.224 m | +| Height-bin local segment ±1, nearest floor point | 0.029 / 0.015 m | 0.029 / 0.015 m | +| Helix angle plus height turn index | 0.030 / 0.015 m | 0.030 / 0.015 m | + +The exact-curve reference searches all local minima over the finite helix and refines them with golden-section minimization; the other segment rules reproduce the carver's XY chord projection and lateral Catmull–Rom smoothing. The analytic centerline rule is wrong for floor ownership: with `bore radius > pitch/2`, a floor sample is vertically closer to the centerline on a neighboring turn. The floor-point and height-window rules agree on this ideal route. The test does **not** reproduce L38's reported 8.6 cm floor ripple; it establishes the failure modes, but the proposed guard remains a candidate until the real FI Style 2 audit is rerun. + +Three additional monotone-grade style-like paths were sampled at 0.125 m: straight, a sinusoidal worm, and a scalloped cascade. Height-bin local ownership had p-p errors `0.005 / 0.030 / 0.031 m` (max absolute `0.003 / 0.016 / 0.016 m`). Nearest centerline ownership had `0.097 / 0.116 / 0.121 m` p-p. The literal strict-below-plus-radius rule had `0.203 / 0.231 / 11.567 m` p-p; the cascade's horizontal loops make a far-lower segment eligible. This disproves a one-sided “below and within radius” rule for shared use. + +Reproduction: `taskset -c 0-11 prlimit --as=6442450944 -- python3 Saved/Linux/research/r3_helix_owner.py`. + +## Math and code placement + +For route point `C(s)` and radius `r(s)`, the carve floor is `F(s) = C_z(s) - r(s) + 0.15 m`. The planner stores `C_z = playerZ + r - 1.38 m`, so `F = playerZ - 1.23 m`. The radius cancels and the floor remains monotone wherever authored `playerZ` is monotone. The straight, worm, spiral, and cascading styles change the XY route; the planner assigns their Z profile from cumulative horizontal route distance. Confirm monotonicity on a candidate range and use the existing nearest-floor fallback for flat or non-monotone mouth/landing spans. + +In `ApplyResolvedStratePassageCarves` (`Standalone/Core/src/ShapeWorldInternal.h`), add a `nearFloorCandidate` alongside `nearestFloorCandidate`. In the existing segment visitor, after computing `routeFloorQ` and horizontal distance squared, accept it into the new candidate only when `abs(routeFloorQ - qz) <= 0.125*qPerMetre` and horizontal distance squared is at most the interpolated radius squared. Select minimum floor-point distance, with stable segment-index tie-break. Apply that candidate only when valid; preserve the existing fallback otherwise. The current wide `carveBegin/carveEnd` range is around the `floorProfile`, so its monotone bounds already cover this band; alternatively, binary-search `F(s)` and visit the bracket segment plus its neighbors. + +This is style-independent: the profile comes from route control points, not helix angle, generator type, or RNG. The incremental version costs a few comparisons and one squared-distance comparison for segments already visited. A narrowed search costs one binary search plus at most three segment evaluations. Neither needs `atan2`, trigonometric roots, iteration to convergence, or mutable state. Fixed-point comparisons and the stable segment tie-break preserve worker-count and peer determinism. + +## Alternatives and distance-field literature + +For a circular helix `gamma(t) = (R cos t, R sin t, b t)` and query `(rho cos phi, rho sin phi, z)`, + +`D²(t) = rho² + R² + (b t - z)² - 2 R rho cos(t - phi)`. + +Stationary points solve `u + e sin(u) = M`, with `u = t - phi`, `e = R rho / b²`, and `M = z/b - phi`. There is no elementary closed form. For the finite, radius-varying route, a safe reference solver brackets stationary candidates over turns, evaluates those roots and both endpoints, and compares distances. A recent 2026 preprint proves a principal-branch minimizer for an ideal infinite circular helix, despite multiple stationary roots, but it is not peer reviewed and does not cover our finite Catmull-smoothed route. Even a perfect Euclidean closest point is the wrong floor owner when the floor cap is offset below the centerline. + +For floor probes on an ideal helix, an angle/height turn index can be written directly. With pitch `p = 2 pi b`, centerline height `z0 + b t`, and floor `z0 + b t - r + lift`, take `phi = atan2(qy-axisY, qx-axisX)` and + +`n = round((qz - z0 - b phi + r - lift) / p)`, `t = phi + 2 pi n`. + +Clamp or test the finite endpoints. This is cheap for a known helix and gives 0.030 m p-p error in the synthetic case, but it needs axis/pitch/radius metadata and has no direct equivalent for Straight, Worm, or Cascading. Use height only to disambiguate a local route interval in the shared path. + +A hard union of independently floor-limited coils is mathematically the union of their void sets (use `min` for negative-inside SDFs; this code's positive-air density reverses that to `max`). It does not preserve a single walkable tread. The 3 m mid-bore and 3.31 m pitch satisfy `2r > pitch`, so adjacent swept tubes intersect and the union opens a route through the intervening floor/support. At the 4.5 m mouth the overlap is larger. Smooth min/max adds a fillet in the band where the fields are close; the polynomial smooth-min can shift the zero surface by up to `k/4`, which can easily exceed the 1.5 cm floor budget. Keep smooth CSG away from the hard floor cap. + +CAD formulations for canal/pipe surfaces reduce distance queries to one-parameter root finding; they do not provide a cheap generic shortcut for this floor-owner query. Line-swept-sphere methods are closer to the current tube field, while high-curvature arbitrary cross-section sweeps may not have a globally smooth continuous distance field. Our control-point sweep is already a compact approximation, so changing the candidate ownership is lower cost than replacing it with a denser exact sweep representation. + +References: + +- [Closest Points on a Circular Helix and Kepler's Equation (2026 preprint)](https://www.preprints.org/manuscript/202608.1202) — stationary equation, multiple roots, and principal-branch result; the paper labels itself non-peer-reviewed. +- [Efficient point-projection to freeform curves and surfaces](https://doi.org/10.1016/j.cagd.2011.04.002) — geometric culling followed by numerical projection; treats monotone spiral curves specially. +- [Fast Distance Computation Between a Point and Cylinders, Cones, Line-Swept Spheres and Cone-Spheres](https://doi.org/10.1080/10867651.2004.10504892) and [Minimum distance between a canal surface and a simple surface](https://doi.org/10.1016/S0010-4485(02)00123-9) — swept-sphere and varying-radius canal-surface distance queries. +- [Iñigo Quílez, Smooth Minimum](https://archive.ph/9Z4dZ) and [distance-function modeling](https://vodacek.zvb.cz/archiv/101.html) — hard min/max operations, polynomial smooth-min formula, blend band, and distance-field caveats. +- [Synthesizing Geologically Coherent Cave Networks](https://diglib.eg.org/items/a4e7985c-af52-4b55-bd1a-eeeff6c47422) — uses compact sweep primitives for straighter conduits and sampled-sphere unions for tortuous conduits to keep a continuous 1-Lipschitz field; the latter costs more memory. +- [Feng and Crane, A Heat Method for Generalized Signed Distance](https://nzfeng.github.io/research/SignedHeatMethod/index.html) — robust generalized distance for broken/noisy/self-intersecting geometry; it solves global sparse systems, a dense offline option rather than a cheap independent per-voxel owner query. +- Local context: `Standalone/Research/WorldAssemblerM3/Linux27/REPORT.md`, `Standalone/Research/WorldAssemblerM3/Linux38/REPORT.md`, `Standalone/Core/src/ResolvedLayoutInternal.h`, and `Standalone/Core/src/ShapeWorldInternal.h`. diff --git a/Standalone/Research/WorldAssemblerM3/Research3/scripts/r3_helix_owner.py b/Standalone/Research/WorldAssemblerM3/Research3/scripts/r3_helix_owner.py new file mode 100644 index 0000000..2dcb3e7 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Research3/scripts/r3_helix_owner.py @@ -0,0 +1,212 @@ +import math +import bisect + +DS = 0.125 +PITCH = 3.31 # L27 measured FI Style 2 main helix pitch (m/turn) +HELIX_R = 3.0 # measured mean centerline radius (m) +BORE_R = 4.5 # conservative authored near-mouth bore radius (m) +LIFT = 0.15 +TURNS = 12.95 +SEGMENTS_PER_TURN = 20 +GRID = 0.125 + + +def snap(v): + return round(v / GRID) * GRID + + +def catmull(p0, p1, p2, p3, t): + t2, t3 = t*t, t*t*t + return 0.5*((2*p1)+(-p0+p2)*t+(2*p0-5*p1+4*p2-p3)*t2+(-p0+3*p1-3*p2+p3)*t3) + + +def helix_run(radius=HELIX_R, pitch=PITCH, turns=TURNS, bore=BORE_R): + n = math.ceil(turns * SEGMENTS_PER_TURN) + theta_end = turns * 2*math.pi + k = pitch/(2*math.pi) + # At control i, z is the centerline height; its floor is z - bore + LIFT. + pts = [] + for i in range(n+1): + th = theta_end*i/n + pts.append((radius*math.cos(th), radius*math.sin(th), k*th)) + length_per_rad = math.hypot(radius, k) + count = math.ceil(theta_end*length_per_rad/DS) + samples = [] + for j in range(count+1): + th = theta_end*j/count + true_floor = k*th-bore+LIFT + q = (snap(radius*math.cos(th)), snap(radius*math.sin(th)), snap(true_floor)) + samples.append((th, true_floor, q)) + return pts, samples, bore, (lambda th: th) + + +def other_run(kind, bore=1.5): + # Monotone-grade non-helix controls: straight, bounded worm, and scalloped cascade. + length = 80.0 + n = math.ceil(length / 0.75) + pts = [] + xy = [] + for i in range(n+1): + s = length*i/n + if kind == 'straight': + x, y = s, 0.0 + elif kind == 'worm': + x, y = s, 2.0*math.sin(s/5.0)+0.35*math.sin(s/1.7) + elif kind == 'cascading': + th = s/5.0 + r = 10.0-1.3*abs(math.sin(4.0*th)) + x, y = r*math.cos(th), r*math.sin(th) + else: + raise ValueError(kind) + xy.append((x,y)) + arc=[0.0] + for i in range(1,len(xy)): + arc.append(arc[-1]+math.hypot(xy[i][0]-xy[i-1][0],xy[i][1]-xy[i-1][1])) + for i,(x,y) in enumerate(xy): + pts.append((x,y,-0.19*arc[i])) + samples=[] + total=arc[-1] + count=math.ceil(total/DS) + for j in range(count+1): + s=total*j/count + i=0 + while i+1=len(arc): i=len(arc)-2 + t=(s-arc[i])/(arc[i+1]-arc[i]) + x=pts[i][0]*(1-t)+pts[i+1][0]*t + y=pts[i][1]*(1-t)+pts[i+1][1]*t + z=pts[i][2]*(1-t)+pts[i+1][2]*t + f=z-bore+LIFT + samples.append((s,f,(snap(x),snap(y),snap(f)))) + return pts,samples,bore,None + + +def project_candidates(pts, q, bore): + qx,qy,qz=q + out=[] + for i in range(len(pts)-1): + a,b=pts[i],pts[i+1] + dx,dy=b[0]-a[0],b[1]-a[1] + h2=dx*dx+dy*dy + if h2<1e-12: + dz=b[2]-a[2] + den=dx*dx+dy*dy+dz*dz + t=max(0.0,min(1.0,((qx-a[0])*dx+(qy-a[1])*dy+(qz-a[2])*dz)/den)) + else: + t=max(0.0,min(1.0,((qx-a[0])*dx+(qy-a[1])*dy)/h2)) + cx=a[0]+t*dx + cy=a[1]+t*dy + # Match ShapeWorldInternal.h: smooth only the lateral coordinates with Catmull-Rom. + if i>2 and i+12vals[i-1] or vals[i]>vals[i+1]: continue + lo=max(0.0,(i-1)*step); hi=min(theta_end,(i+1)*step) + x1=hi-gr*(hi-lo); x2=lo+gr*(hi-lo) + f1,f2=dist2(x1),dist2(x2) + for _ in range(30): + if f1<=f2: + hi,x2,f2=x2,x1,f1; x1=hi-gr*(hi-lo); f1=dist2(x1) + else: + lo,x1,f1=x1,x2,f2; x2=lo+gr*(hi-lo); f2=dist2(x2) + th=(lo+hi)*0.5; value=dist2(th) + if value implementer L38 continued), newest at bottom + +## R3 — ranked floor ownership and first synthetic results (2026-10-23) + +**Rank 1: keep the bore owner and floor owner separate. For floor samples, select the nearest floor point only from a local height band `abs(candidateFloorQ - qz) <= 0.125 m` (half the 25 cm voxel) and `horizontalDistance <= boreRadius`; break ties by stable lower segment index. If the band is empty, retain the existing floor/player fallback.** The shared route floor is monotone in control-point order: the carver's `point.z - point.radius + playerCenterAboveFloor` cancels the radius offset in the planner and equals authored `playerZ`, which is interpolated monotonically by cumulative route distance. A binary search on that profile can restrict the near-floor segment range to the crossing segment and its neighbors, with no style/generator branch. This is an ownership guard, not yet a claim that it passes the real L38 ripple audit. + +**Synthetic floor-error ripple (peak-to-peak / max absolute, metres; 1,984 floor probes, 0.125 m sampling, 3.0 m centerline radius, 3.31 m vertical pitch, 20 route controls/turn, 12.95 turns; bore radius 4.5 m, repeated at the 3.0 m mid-bore):** + +| Rule | FI-style2 p-p / max-abs | Read | +|---|---:|---| +| Exact analytic closest helix centerline | 3.372 / 3.372 | Wrong floor coil when distance to centerline is used for floor ownership. | +| Segment closest centerline | 3.387 / 3.383 | Same wrong-turn failure. | +| Nearest 3D floor point, unconstrained | 0.029 / 0.015 | Correct on this ideal route; max-abs is just under the 1.5 cm gate, but p-p is not. | +| Strict `floor <= qz`, within bore radius | 0.238 / 0.224 | Fails: voxel rounding excludes the true floor when it lies up to half a voxel above `qz`, so ownership drops to a lower segment. | +| Near-floor band `abs(floor-qz)<=0.125`, within radius | 0.029 / 0.015 | Matches nearest floor point here; prevents the strict-below one-segment drop. | +| Highest floor below `qz`, within radius | 0.282 / 0.224 | Fails; a one-sided “topmost below” rule is unstable at voxel boundaries. | +| Height-bin control segment ±1, then nearest floor point | 0.029 / 0.015 | Same as floor-point nearest on this ideal route; deterministic, style-independent guard. | +| Angle + height coil index | 0.030 / 0.015 | Works for this uniform helix after endpoint clamping; costs axis/angle/turn math and is style-specific. | + +On synthetic straight / worm / cascading paths, height-bin local ownership measured p-p `0.005 / 0.030 / 0.031 m` (max abs `0.003 / 0.016 / 0.016 m`), versus centerline ownership `0.097 / 0.116 / 0.121 m`. Strict below-plus-radius reached `11.57 m` p-p on the looping cascade. This rejects that literal rule for shared use. The smooth ideal test does **not** reproduce L38's reported 8.6 cm floor ripple; it establishes the wrong-turn and voxel-boundary failure modes. Re-run the actual FI Style 2 floor/wall audit before accepting the guard. + +The reported 0.938 m ripple period matches the expected control chord for the 3 m, 20-segment helix: `2*3*sin(pi/20) = 0.9386 m`; that points to per-control-segment ownership/smoothing as the repeat interval. + +**Rank 2:** analytic helix closest point is useful as a reference/control, not the runtime shared owner. For `gamma(t)=(R cos t,R sin t,b t)` and query cylindrical coordinates `(rho,phi,z)`, stationary points solve `u + e sin(u) = M`, where `u=t-phi`, `e=R rho/b^2`, `M=z/b-phi`. There is no elementary closed form; the equation can have multiple roots per turn when `e>1`, so robust finite-route search brackets candidates and includes endpoints. Here `b=3.31/(2 pi)=0.527 m/rad`; floor queries have large `e`, and exact closest centerline is the wrong semantic owner because the bore floor lies farther below the centerline than the turn pitch permits. + +For an ideal helix floor probe, angle/height gives `n=round((qz-z0-b*phi+r-lift)/pitch)`, `t=phi+2*pi*n`, where `phi=atan2(qy-axisY,qx-axisX)` and `lift=0.15m`. This avoids Euclidean coil ownership but needs helix axis/pitch metadata and a finite-end clamp; keep it as a reference, not the shared implementation. + +**Rank 3:** hard union of floor-limited coils is geometrically valid only if a merged void is intended. With 3 m mid-bore radius and 3.31 m pitch, neighboring swept tubes overlap (`2r > pitch`); a union opens the overlap and can erase the upper coil's tread/support. A smooth union expands/rounds that seam and shifts the zero surface by up to `k/4` for the polynomial smooth-min. Do not use it to decide floor ownership. + +Sources and details will be collected in `Standalone/Research/WorldAssemblerM3/Research3/REPORT.md`. +