Phase 7: netfox + godot-jolt stack upgrade

Stack installed:
- netfox v1.35.3 (core + extras + noray + internals)
- godot-jolt v0.16.0-stable

Architecture:
- Server: ENet transport (works headless, no netfox deps)
- Client/Editor: netfox rollback (RollbackSynchronizer, TickInterpolator)

New/modified:
- docs/migration-netfox-plan.md — migration architecture
- scripts/network/network_manager.gd — netfox-aware ENet fallback
- scripts/network/player.gd — clean base player
- client/characters/player_netfox.gd — rollback player w/ WeaponManager
- client/characters/input/player_net_input.gd — BaseNetInput subclass
- client/characters/character/fps_character_controller.gd — netfox input feed
- client/weapons/ — weapon data, registry, TacticalWeaponHitscan, WeaponManager
- client/scripts/round_replicator.gd — client-side round state bridge
- server/scripts/round_manager.gd — improved state machine
- server/scripts/plugin_api/plugin_manager.gd — refined plugin system
- config: enemy_tag, ally_tag for meatball targeting

Removed: old C++ SimulationServer GDExtension (replaced by netfox rollback)
This commit is contained in:
2026-07-02 17:38:50 -04:00
parent e2dc429caa
commit e7299b17e9
3237 changed files with 523530 additions and 18 deletions
+153
View File
@@ -0,0 +1,153 @@
@tool
extends EditorScript
# Apply distance-based visibility ranges to all modular CSG pieces.
#
# This is the equivalent of LOD for CSG-based geometry — it hides
# entire pieces when they're beyond a useful visual distance.
#
# Ranges (defined per asset type):
# Walls: hide past 50 m
# Floors: hide past 60 m
# Pillars: hide past 30 m
# Beams: hide past 30 m
# Accent panels: hide past 20 m
# Door/Window: hide past 40 m
#
# Usage:
# Select a scene in the Godot editor, then:
# Scene > Run Script (select this file)
#
# Or from CLI on a specific scene:
# godot --script scripts/apply_visibility_ranges.gd \
# --scene res://assets/scenes/modular/kit_demo.tscn
const DEBUG := false # Enable for verbose logging
# Default ranges per type (begin = 0.0 means always visible up to end)
const RANGES := {
"wall": {"begin": 0.0, "end": 50.0, "margin": 5.0}, # fade out over 5m
"floor": {"begin": 0.0, "end": 60.0, "margin": 10.0},
"pillar": {"begin": 0.0, "end": 30.0, "margin": 3.0},
"beam": {"begin": 0.0, "end": 30.0, "margin": 3.0},
"accent": {"begin": 0.0, "end": 20.0, "margin": 2.0},
"doorway": {"begin": 0.0, "end": 40.0, "margin": 5.0},
"window": {"begin": 0.0, "end": 40.0, "margin": 5.0},
"endcap": {"begin": 0.0, "end": 50.0, "margin": 5.0},
"default": {"begin": 0.0, "end": 40.0, "margin": 5.0},
}
func _run():
var scene_root: Node
if Engine.is_editor_hint():
scene_root = get_scene() as Node
if not scene_root:
printerr("No open scene in editor. Open a scene first.")
return
else:
var args = OS.get_cmdline_args()
var scene_path := ""
for i in range(args.size()):
if args[i] == "--scene" and i + 1 < args.size():
scene_path = args[i + 1]
if scene_path.is_empty():
printerr("Usage: godot --script apply_visibility_ranges.gd --scene res://path/to/scene.tscn" +
"\nOr run from the Godot editor with a scene open.")
return
var packed = ResourceLoader.load(scene_path)
if not packed:
printerr("Failed to load scene: ", scene_path)
return
scene_root = packed.instantiate()
print("=== Visibility Range Applicator ===")
print("Scene: ", scene_root.name)
var counts := {} # type → number of nodes modified
var total := 0
_apply_to_node(scene_root, counts, total)
print("")
print("Modified nodes: ", total)
for type_name in counts:
print(" %s: %d" % [type_name, counts[type_name]])
print("")
print("=== Ranges applied ===")
func _apply_to_node(node: Node, counts: Dictionary, total: int) -> void:
# Only process CSG nodes (the modular pieces)
if node is CSGShape3D:
var range_config := _get_range_for_node(node)
if range_config != null:
node.visibility_range_begin = range_config.begin
node.visibility_range_end = range_config.end
node.visibility_range_begin_margin = range_config.margin
node.visibility_range_end_margin = range_config.margin
var type_name := _classify_node(node)
counts[type_name] = counts.get(type_name, 0) + 1
total += 1
if DEBUG:
print(" %s%.0f%.0f m (type: %s)" % [node.name, range_config.begin, range_config.end, type_name])
# Also apply to CSGCombiner3D parents (hold doorway/window subtractions)
if node is CSGCombiner3D:
var range_config := _get_range_for_combiner(node)
if range_config != null:
node.visibility_range_begin = range_config.begin
node.visibility_range_end = range_config.end
node.visibility_range_begin_margin = range_config.margin
node.visibility_range_end_margin = range_config.margin
var type_name := _classify_node(node)
counts[type_name] = counts.get(type_name, 0) + 1
total += 1
if DEBUG:
print(" %s%.0f%.0f m (type: %s)" % [node.name, range_config.begin, range_config.end, type_name])
# Recurse
for child in node.get_children():
_apply_to_node(child, counts, total)
func _classify_node(node: Node) -> String:
var name_lower := node.name.to_lower()
if name_lower.contains("wall"):
if name_lower.contains("door"): return "doorway"
if name_lower.contains("window"): return "window"
if name_lower.contains("corner"): return "wall"
if name_lower.contains("endcap"): return "endcap"
return "wall"
if name_lower.contains("floor"): return "floor"
if name_lower.contains("pillar"): return "pillar"
if name_lower.contains("beam"): return "beam"
if name_lower.contains("accent") or name_lower.contains("blue") or name_lower.contains("red"):
return "accent"
if name_lower.contains("panel"):
return "accent"
return "default"
func _get_range_for_node(node: CSGShape3D) -> Dictionary:
# Detect CSGBox3D size to infer type
if node is CSGBox3D:
var box := node as CSGBox3D
var size := box.size
# Floor slabs: thin, wide (Z > X on rotated ones, check min dimension)
if size.y <= 0.1 and (size.x >= 2.0 or size.z >= 2.0):
return RANGES.floor.duplicate()
return _get_range_by_name(node)
func _get_range_for_combiner(node: CSGCombiner3D) -> Dictionary:
return _get_range_by_name(node)
func _get_range_by_name(node: Node) -> Dictionary:
var type_name := _classify_node(node)
return RANGES.get(type_name, RANGES.default).duplicate()