rnd(M03): extract streaming target planner

Work-Package: M03-RND-STREAMING-PLANNER-001
Agent: sindo-main-codex
Tests: planner/facade/focus/coordinate/manifest/shutdown contracts, seven-checkpoint dry-run, repository gates
Fidelity: preserves wanted/retained radii, boundary prefetch, editor preview, queue priority and cache/render behavior
This commit is contained in:
2026-07-16 00:08:02 +04:00
parent 0c24f96ae1
commit e887c3bad5
16 changed files with 793 additions and 84 deletions
+42 -75
View File
@@ -17,6 +17,8 @@ const GODOT_WORLD_POSITION_SCRIPT := preload("res://src/domain/coordinates/godot
const COORDINATE_MAPPER_SCRIPT := preload("res://src/domain/coordinates/coordinate_mapper.gd")
const ADT_TILE_COORDINATE_SCRIPT := preload("res://src/domain/coordinates/adt_tile_coordinate.gd")
const ADT_TILE_LOCAL_POSITION_SCRIPT := preload("res://src/domain/coordinates/adt_tile_local_position.gd")
const STREAMING_TARGET_PLANNER_SCRIPT := preload("res://src/render/streaming/streaming_target_planner.gd")
const STREAMING_TARGET_POLICY_SCRIPT := preload("res://src/render/streaming/streaming_target_policy.gd")
const REQUIRED_BAKED_TILE_FORMAT_VERSION := 5
const REQUIRED_SPLAT_TILE_FORMAT_VERSION := 1
const REQUIRED_CONTROL_SPLAT_TILE_FORMAT_VERSION := 3
@@ -174,6 +176,7 @@ const QUALITY_HIGH := "High"
@export var hitch_profiler_threshold_ms: float = 20.0
var _builder
var _streaming_target_planner := STREAMING_TARGET_PLANNER_SCRIPT.new()
var _map_dir := ""
var _abs_map_dir := ""
var _wdt_info: Dictionary = {}
@@ -720,57 +723,47 @@ func _refresh_editor_streaming_targets_at(focus_pos: Vector3, force: bool) -> vo
if not force and not _should_refresh_focus(focus_pos):
return
var focus_tile := _world_to_tile(focus_pos)
var total_tile_radius: int = maxi(0, editor_preview_tile_radius)
var typed_focus_position = GODOT_WORLD_POSITION_SCRIPT.new(focus_pos.x, focus_pos.y, focus_pos.z)
var target_plan = _streaming_target_planner.plan_editor(
_available_tiles,
typed_focus_position,
editor_preview_tile_radius
)
var wanted_tiles: Dictionary = target_plan.wanted_tile_keys()
var wanted_tiles: Dictionary = {}
for ty in range(focus_tile.y - total_tile_radius, focus_tile.y + total_tile_radius + 1):
for tx in range(focus_tile.x - total_tile_radius, focus_tile.x + total_tile_radius + 1):
var key := _tile_key(tx, ty)
if _available_tiles.has(key):
wanted_tiles[key] = true
_apply_streaming_target(wanted_tiles, wanted_tiles, focus_pos)
_apply_streaming_target(wanted_tiles, target_plan.retained_tile_keys(), focus_pos)
_note_refresh_focus(focus_pos)
if force or debug_streaming:
print("StreamingWorld(editor): focus_tile=%s wanted=%d queued=%d" % [
focus_tile, wanted_tiles.size(), _tile_load_queue.size()])
Vector2i(target_plan.focus_tile.tile_x, target_plan.focus_tile.tile_y),
wanted_tiles.size(),
_tile_load_queue.size(),
])
func _refresh_streaming_targets_at(focus_pos: Vector3, force: bool) -> void:
if not force and not _should_refresh_focus(focus_pos):
return
var focus_tile := _world_to_tile(focus_pos)
# Chunk-LOD radius → tile radius (ceiling), plus margin for pre-warming.
var focus_tiles := _predictive_focus_tiles(focus_pos)
var total_tile_radius: int = _runtime_load_tile_radius()
var retain_tile_radius: int = _runtime_retain_tile_radius()
var retained_tiles: Dictionary = {}
for center in focus_tiles:
for ty in range(center.y - retain_tile_radius, center.y + retain_tile_radius + 1):
for tx in range(center.x - retain_tile_radius, center.x + retain_tile_radius + 1):
var retained_key := _tile_key(tx, ty)
if _available_tiles.has(retained_key):
retained_tiles[retained_key] = true
var wanted_tiles: Dictionary = {}
for center in focus_tiles:
for ty in range(center.y - total_tile_radius, center.y + total_tile_radius + 1):
for tx in range(center.x - total_tile_radius, center.x + total_tile_radius + 1):
var key := _tile_key(tx, ty)
if _available_tiles.has(key):
wanted_tiles[key] = true
var typed_focus_position = GODOT_WORLD_POSITION_SCRIPT.new(focus_pos.x, focus_pos.y, focus_pos.z)
var target_plan = _streaming_target_planner.plan_runtime(
_available_tiles,
typed_focus_position,
_streaming_target_policy()
)
var wanted_tiles: Dictionary = target_plan.wanted_tile_keys()
var retained_tiles: Dictionary = target_plan.retained_tile_keys()
_apply_streaming_target(wanted_tiles, retained_tiles, focus_pos)
_note_refresh_focus(focus_pos)
if force or debug_streaming:
print("StreamingWorld: focus_tile=%s wanted=%d queued=%d" % [
focus_tile, wanted_tiles.size(), _tile_load_queue.size()])
Vector2i(target_plan.focus_tile.tile_x, target_plan.focus_tile.tile_y),
wanted_tiles.size(),
_tile_load_queue.size(),
])
@@ -2891,50 +2884,24 @@ func _compute_desired_lods(state: Dictionary, cam_pos: Vector3) -> Dictionary:
return desired
func _runtime_visible_tile_radius() -> int:
var chunk_tile_radius: int = int(ceil(float(lod2_radius_chunks) / 16.0)) + warm_tile_margin
return maxi(chunk_tile_radius, lod2_tile_radius)
func _runtime_load_tile_radius() -> int:
return _runtime_visible_tile_radius() + maxi(0, prewarm_tile_margin)
return STREAMING_TARGET_POLICY_SCRIPT.calculate_load_tile_radius(
lod2_radius_chunks,
lod2_tile_radius,
warm_tile_margin,
prewarm_tile_margin
)
func _runtime_retain_tile_radius() -> int:
return _runtime_load_tile_radius() + maxi(0, retain_tile_margin)
func _predictive_focus_tiles(focus_pos: Vector3) -> Array[Vector2i]:
var base := _world_to_tile(focus_pos)
var result: Array[Vector2i] = [base]
var threshold: float = clampf(boundary_prefetch_threshold, 0.0, 0.49)
if threshold <= 0.0:
return result
var typed_focus_position = GODOT_WORLD_POSITION_SCRIPT.new(focus_pos.x, focus_pos.y, focus_pos.z)
var tile_local_position = COORDINATE_MAPPER_SCRIPT.godot_to_adt_tile_local(typed_focus_position)
var local_x: float = tile_local_position.east_yards / TILE_SIZE
var local_z: float = tile_local_position.south_yards / TILE_SIZE
var x_offsets: Array[int] = [0]
var z_offsets: Array[int] = [0]
if local_x <= threshold:
x_offsets.append(-1)
elif local_x >= 1.0 - threshold:
x_offsets.append(1)
if local_z <= threshold:
z_offsets.append(-1)
elif local_z >= 1.0 - threshold:
z_offsets.append(1)
for ox in x_offsets:
for oz in z_offsets:
if ox == 0 and oz == 0:
continue
result.append(Vector2i(base.x + ox, base.y + oz))
return result
func _streaming_target_policy() -> StreamingTargetPolicy:
return STREAMING_TARGET_POLICY_SCRIPT.new(
lod2_radius_chunks,
lod2_tile_radius,
warm_tile_margin,
prewarm_tile_margin,
retain_tile_margin,
boundary_prefetch_threshold
)
func _compute_desired_tile_lod(state: Dictionary, cam_pos: Vector3) -> int: