#!/usr/bin/env python3 """Count reach-bound work from the saved v225 layout and L125 tile profiles.""" from __future__ import annotations import json import math import re import subprocess from pathlib import Path ROOT = Path(__file__).resolve().parents[4] HERE = Path(__file__).resolve().parent OUT = ROOT / "Saved/Linux/l129/precount" LAYOUT = ROOT / "Saved/Linux/l125/hashes/owner4-surface-world-w8-clang.layout.json" BASELINE_LOGS = [ROOT / f"Saved/Linux/l125/counts/s{s}-w8-profile1.log" for s in range(4)] CORE_VOXEL_METRES = 0.25 SUBVOXELS_PER_METRE = 1024.0 PROFILE = re.compile(r"([a-zA-Z0-9_]+)=([^ ]+)") TILE_KEY = re.compile(r"\((-?\d+),(-?\d+),(-?\d+),lod(\d+)\)") def knot_span(a: list[float], b: list[float], units_per_metre: float) -> float: dx, dy = (b[0] - a[0]) / units_per_metre, (b[1] - a[1]) / units_per_metre return max(1.0e-6, math.sqrt(math.sqrt(dx * dx + dy * dy))) def evaluate_raw(controls: list[list[float]], span: int, t: float, units_per_metre: float) -> list[float]: p1, p2 = controls[span], controls[span + 1] p0, p3 = controls[max(0, span - 1)], controls[min(span + 2, len(controls) - 1)] dt0, dt1, dt2 = (knot_span(p0, p1, units_per_metre), knot_span(p1, p2, units_per_metre), knot_span(p2, p3, units_per_metre)) t2, t3 = t * t, t * t * t result = [0.0, 0.0, 0.0, 0.0] for axis in (0, 1, 3): m1 = dt1 * ((p1[axis] - p0[axis]) / dt0 - (p2[axis] - p0[axis]) / (dt0 + dt1) + (p2[axis] - p1[axis]) / dt1) m2 = dt1 * ((p2[axis] - p1[axis]) / dt1 - (p3[axis] - p1[axis]) / (dt1 + dt2) + (p3[axis] - p2[axis]) / dt2) result[axis] = ((2.0 * t3 - 3.0 * t2 + 1.0) * p1[axis] + (-2.0 * t3 + 3.0 * t2) * p2[axis] + (t3 - 2.0 * t2 + t) * m1 + (t3 - t2) * m2) result[2] = p1[2] + (p2[2] - p1[2]) * t result[3] = min(max(result[3], min(p1[3], p2[3])), max(p1[3], p2[3])) return result def build_raw_segments(controls: list[list[float]], units_per_metre: float, spacing_metres: float = 8.0, chord_error_metres: float = 0.1) -> list[dict]: result: list[dict] = [] along_metres = 0.0 for span in range(len(controls) - 1): horizontal_arc = [0.0] * 33 previous = evaluate_raw(controls, span, 0.0, units_per_metre) for step in range(1, 33): current = evaluate_raw(controls, span, step / 32.0, units_per_metre) dx = (current[0] - previous[0]) / units_per_metre dy = (current[1] - previous[1]) / units_per_metre horizontal_arc[step] = horizontal_arc[step - 1] + math.hypot(dx, dy) previous = current span_length = horizontal_arc[-1] base_count = max(1, math.ceil(span_length / spacing_metres)) def parameter_at_arc(target: float) -> float: upper = 1 while upper < len(horizontal_arc) and horizontal_arc[upper] < target: upper += 1 upper = min(upper, 32) lower = upper - 1 arc_span = horizontal_arc[upper] - horizontal_arc[lower] arc_t = ((target - horizontal_arc[lower]) / arc_span if arc_span > 1.0e-12 else 0.0) return (lower + min(max(arc_t, 0.0), 1.0)) / 32.0 parameters = [0.0] def append_adaptive(t_a: float, t_b: float, a: list[float], b: list[float], depth: int) -> None: fractions = (0.25, 0.5, 0.75) maximum_error, polyline_length = 0.0, 0.0 previous_point = a for fraction in fractions: current = evaluate_raw(controls, span, t_a + (t_b - t_a) * fraction, units_per_metre) dx = (current[0] - (a[0] + (b[0] - a[0]) * fraction)) / units_per_metre dy = (current[1] - (a[1] + (b[1] - a[1]) * fraction)) / units_per_metre dr = (current[3] - (a[3] + (b[3] - a[3]) * fraction)) / units_per_metre maximum_error = max(maximum_error, math.sqrt(dx * dx + dy * dy + dr * dr)) sx = (current[0] - previous_point[0]) / units_per_metre sy = (current[1] - previous_point[1]) / units_per_metre polyline_length += math.hypot(sx, sy) previous_point = current sx = (b[0] - previous_point[0]) / units_per_metre sy = (b[1] - previous_point[1]) / units_per_metre polyline_length += math.hypot(sx, sy) split = ((polyline_length > spacing_metres * (1.0 + 1.0e-9) or maximum_error > chord_error_metres) and depth < 12) if split: t_mid = (t_a + t_b) * 0.5 middle = evaluate_raw(controls, span, t_mid, units_per_metre) append_adaptive(t_a, t_mid, a, middle, depth + 1) append_adaptive(t_mid, t_b, middle, b, depth + 1) else: parameters.append(t_b) for segment in range(1, base_count + 1): target_a = span_length * (segment - 1) / base_count target_b = span_length * segment / base_count t_a, t_b = parameter_at_arc(target_a), parameter_at_arc(target_b) append_adaptive(t_a, t_b, evaluate_raw(controls, span, t_a, units_per_metre), evaluate_raw(controls, span, t_b, units_per_metre), 0) points = [evaluate_raw(controls, span, t, units_per_metre) for t in parameters] sampled_arc = [0.0] * len(points) for index in range(1, len(points)): dx = (points[index][0] - points[index - 1][0]) / units_per_metre dy = (points[index][1] - points[index - 1][1]) / units_per_metre sampled_arc[index] = sampled_arc[index - 1] + math.hypot(dx, dy) sampled_span_length = sampled_arc[-1] for index, point in enumerate(points): progress = (sampled_arc[index] / sampled_span_length if sampled_span_length > 1.0e-12 else parameters[index]) point[2] = controls[span][2] + (controls[span + 1][2] - controls[span][2]) * progress for index in range(1, len(points)): dx = (points[index][0] - points[index - 1][0]) / units_per_metre dy = (points[index][1] - points[index - 1][1]) / units_per_metre dz = (points[index][2] - points[index - 1][2]) / units_per_metre length = math.sqrt(dx * dx + dy * dy + dz * dz) progress_a = (sampled_arc[index - 1] / sampled_span_length if sampled_span_length > 1.0e-12 else parameters[index - 1]) progress_b = (sampled_arc[index] / sampled_span_length if sampled_span_length > 1.0e-12 else parameters[index]) floor_a = controls[span][2] - controls[span][3] floor_b = controls[span + 1][2] - controls[span + 1][3] result.append({"a": points[index - 1], "b": points[index], "along": along_metres, "length": length, "span": span, "floor_a": floor_a + (floor_b - floor_a) * progress_a, "floor_b": floor_a + (floor_b - floor_a) * progress_b}) along_metres += length return result def closest_route_height(route: list[list[float]], x: float, y: float) -> tuple[float, float]: best_height, best_distance_sq = 0.0, float("inf") for a, b in zip(route, route[1:]): dx, dy = b[0] - a[0], b[1] - a[1] length_sq = dx * dx + dy * dy t = min(max(((x - a[0]) * dx + (y - a[1]) * dy) / length_sq, 0.0), 1.0) \ if length_sq > 1.0e-12 else 0.0 px, py = a[0] + t * dx, a[1] + t * dy distance_sq = (x - px) ** 2 + (y - py) ** 2 if distance_sq < best_distance_sq: best_distance_sq = distance_sq best_height = a[2] + (b[2] - a[2]) * t return best_height, best_distance_sq def prepared_segments(layout: dict) -> list[list[dict]]: qpm = SUBVOXELS_PER_METRE strata = {item["strate_index"]: item for item in layout["strates"]} all_passages: list[list[dict]] = [] for passage in layout["passages"]: controls_q = [[float(value) for value in point["position_q"]] + [float(point["radius_q"])] for point in passage["control_points"]] controls = [point.copy() for point in controls_q] upper_mouth = [value / qpm for value in passage["upper_mouth_q"]] upper_depth = max(0.0, (controls[0][2] / qpm) - upper_mouth[2] + 1.13) upper_stratum = strata.get(passage["upper_strate_index"], {}) if upper_stratum.get("generator_kind") == "FloatingIslands": route = [] for anchor in upper_stratum.get("anchors", []): if anchor.get("kind") == "route_point" and len(anchor.get("values", [])) >= 3: route.append([value / qpm for value in anchor["values"][:3]]) if route: route_height, distance_sq = closest_route_height( route, controls[0][0] / qpm, controls[0][1] / qpm) if distance_sq <= (controls[0][3] / qpm + 2.0) ** 2: upper_depth = max(upper_depth, controls[0][2] / qpm - route_height + 1.13) controls[0][3] = max(controls[0][3], upper_depth * qpm) lower_mouth = [value for value in passage["lower_mouth_q"]] player_center_q = round(1.13 * qpm) route_end = len(controls) - 1 for index in range(1, len(controls_q)): p = controls_q[index] if (int(p[0]) == lower_mouth[0] and int(p[1]) == lower_mouth[1] and int(p[2] - p[3] + player_center_q) == lower_mouth[2]): route_end = index raw = build_raw_segments(controls, qpm) total_path = 0.0 for segment in raw: if segment["span"] + 1 == route_end: total_path = segment["along"] + segment["length"] radii = [(0.0, controls[0][3] / qpm)] for segment in raw: if segment["span"] < route_end: radii.append((segment["along"] + segment["length"], segment["b"][3] / qpm)) body_radii = [radius for along, radius in radii if along >= 10.0 and total_path - along >= 10.0] minimum_body = min(body_radii) if body_radii else min(radius for _, radius in radii) floor_depth = max(0.5, minimum_body - 0.23) prepared = [] for segment in raw: route_segment = segment["span"] < route_end along_b = segment["along"] + segment["length"] def effective_z(raw_z: float, radius: float, planned_floor: float, along: float) -> float: raw_z /= qpm radius /= qpm planned_floor /= qpm local_depth = min(floor_depth, max(0.0, radius - 0.23)) if route_segment or abs(along - total_path) <= 1.0e-9: return planned_floor + local_depth route_depth = max(0.0, radius - 0.23) return raw_z - route_depth + local_depth a = [segment["a"][0] / qpm, segment["a"][1] / qpm, effective_z(segment["a"][2], segment["a"][3], segment["floor_a"], segment["along"])] b = [segment["b"][0] / qpm, segment["b"][1] / qpm, effective_z(segment["b"][2], segment["b"][3], segment["floor_b"], along_b)] prepared.append({"a": a, "b": b, "radius_a": segment["a"][3] / qpm, "radius_b": segment["b"][3] / qpm}) all_passages.append(prepared) return all_passages def profile_tiles() -> list[tuple[int, int, int, int, int, int, int, int]]: result = [] for strate, path in enumerate(BASELINE_LOGS): if not path.is_file(): raise SystemExit(f"missing baseline profile: {path}") seen = set() for line in path.read_text(encoding="utf-8", errors="replace").splitlines(): if not line.startswith("[PassageComposerTile] "): continue row = dict(PROFILE.findall(line)) match = TILE_KEY.search(row.get("key", "")) if not match: continue x, y, z, lod = map(int, match.groups()) if lod > 2: continue key = (x, y, z, lod) if key in seen: continue seen.add(key) result.append((strate, x, y, z, lod, int(row.get("features", 0)), int(row.get("route_candidates", 0)), int(row.get("sweep_pairs", 0)))) return result def main() -> int: if not LAYOUT.is_file(): raise SystemExit(f"missing resolved layout: {LAYOUT}") OUT.mkdir(parents=True, exist_ok=True) layout = json.loads(LAYOUT.read_text(encoding="utf-8")) passages = prepared_segments(layout) tiles = profile_tiles() input_path = OUT / "reach-input.tsv" with input_path.open("w", encoding="utf-8") as output: for passage_index, segments in enumerate(passages): for segment_index, segment in enumerate(segments): values = [*segment["a"], *segment["b"], segment["radius_a"], segment["radius_b"]] output.write("S {} {} {}\n".format(passage_index, segment_index, " ".join(f"{value:.15g}" for value in values))) for tile in tiles: output.write("T {}\n".format(" ".join(str(value) for value in tile))) binary = OUT / "CountReachCandidates" compile_log = OUT / "compile.log" with compile_log.open("w", encoding="utf-8") as log: built = subprocess.run(["g++", "-std=c++20", "-O3", "-DNDEBUG", str(HERE / "CountReachCandidates.cpp"), "-o", str(binary)], cwd=ROOT, stdout=log, stderr=subprocess.STDOUT, check=False) if built.returncode: raise SystemExit(f"counter compile failed: {compile_log}") result_path = OUT / "results.csv" run_log = OUT / "counter.log" command = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--", str(binary), str(input_path), str(result_path)] with run_log.open("w", encoding="utf-8") as log: result = subprocess.run(command, cwd=ROOT, stdout=log, stderr=subprocess.STDOUT, check=False, timeout=1740) if result.returncode: raise SystemExit(f"counter failed: {run_log}; exit={result.returncode}") print(f"[L129Precount] segments={[len(x) for x in passages]} " f"profile_tiles={len(tiles)} input={input_path.relative_to(ROOT)} " f"results={result_path.relative_to(ROOT)}") return 0 if __name__ == "__main__": raise SystemExit(main())