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
@@ -0,0 +1,157 @@
@tool
extends RefCounted
# Validator: Texture Size
#
# Checks:
# - All textures referenced by materials in the scene are ≤ 1024×1024
# - Checks StandardMaterial3D, ORMMaterial3D, and ShaderMaterial textures
# - Warns on textures > 512×512 (suggested for secondary surfaces)
# - Skips LightmapGI textures (baked lightmaps are handled separately)
const MAX_TEXTURE_SIZE := 1024
const WARN_TEXTURE_SIZE := 512
func validate(scene_root: Node, scene_path: String) -> Dictionary:
var errors: Array[String] = []
var warnings: Array[String] = []
var inspected := 0
var oversized: Array[String] = []
# Collect all MeshInstance3D nodes and their materials
var mesh_instances: Array[MeshInstance3D] = []
_collect_mesh_instances(scene_root, mesh_instances)
# Track visited textures to avoid duplicate reports
var visited_textures: Dictionary = {}
for mi in mesh_instances:
var mesh: Mesh = mi.mesh
if mesh == null:
continue
for surf_idx in mesh.get_surface_count():
var mat := mesh.surface_get_material(surf_idx)
if mat == null:
# Check override material on MeshInstance3D
mat = mi.get_surface_override_material(surf_idx)
if mat == null:
# Check material_override on MeshInstance3D
mat = mi.material_override
if mat == null:
continue
_check_material_textures(mat, visited_textures, errors, warnings, inspected)
# Check material_override if no per-surface materials
if mesh_instances.is_empty():
_find_materials_on_node(scene_root, visited_textures, errors, warnings, inspected)
# Check LightmapGI light_data texture sizes separately
var lightmaps: Array[LightmapGI] = []
_find_nodes_by_type(scene_root, "LightmapGI", lightmaps)
for lm in lightmaps:
if lm.light_data != null:
var lightmap_tex = lm.light_data.get_texture()
if lightmap_tex != null and not visited_textures.has(lightmap_tex.resource_path):
visited_textures[lightmap_tex.resource_path] = true
var size := _get_texture_size(lightmap_tex)
if size > 0:
print(" Lightmap texture: ", lightmap_tex.resource_path, "", size, "×")
if size > MAX_TEXTURE_SIZE:
warnings.append("Lightmap texture exceeds %d: %s (%d×)" % [MAX_TEXTURE_SIZE, lightmap_tex.resource_path, size])
print("")
print(" Textures inspected: ", inspected)
if oversized.is_empty():
print(" ✓ All textures within ", MAX_TEXTURE_SIZE, "×", MAX_TEXTURE_SIZE, " limit")
else:
for o in oversized:
print("", o)
return {
"pass": errors.is_empty(),
"errors": errors,
"warnings": warnings,
}
func _check_material_textures(mat: Material, visited: Dictionary, errors: Array, warnings: Array, inspected: int) -> void:
if mat is StandardMaterial3D:
var smat := mat as StandardMaterial3D
_check_single_texture(smat.albedo_texture, "albedo", smat.resource_path, visited, errors, warnings, inspected)
_check_single_texture(smat.normal_texture, "normal", smat.resource_path, visited, errors, warnings, inspected)
_check_single_texture(smat.roughness_texture, "roughness", smat.resource_path, visited, errors, warnings, inspected)
_check_single_texture(smat.metallic_texture, "metallic", smat.resource_path, visited, errors, warnings, inspected)
_check_single_texture(smat.emission_texture, "emission", smat.resource_path, visited, errors, warnings, inspected)
_check_single_texture(smat.ao_texture, "ambient_occlusion", smat.resource_path, visited, errors, warnings, inspected)
elif mat is ORMMaterial3D:
var orm := mat as ORMMaterial3D
_check_single_texture(orm.albedo_texture, "albedo", orm.resource_path, visited, errors, warnings, inspected)
_check_single_texture(orm.normal_texture, "normal", orm.resource_path, visited, errors, warnings, inspected)
_check_single_texture(orm.orm_texture, "ORM", orm.resource_path, visited, errors, warnings, inspected)
_check_single_texture(orm.emission_texture, "emission", orm.resource_path, visited, errors, warnings, inspected)
elif mat is ShaderMaterial:
var sm := mat as ShaderMaterial
# ShaderMaterial textures are set via shader params — check all Texture2D params
var param_list = sm.get_shader_parameter_list() if sm.shader else []
for param in param_list:
var val = sm.get_shader_parameter(param)
if val is Texture2D:
_check_single_texture(val as Texture2D, param, sm.resource_path, visited, errors, warnings, inspected)
func _check_single_texture(tex: Texture2D, slot_name: String, material_path: String, visited: Dictionary, errors: Array, warnings: Array, inspected: int) -> void:
if tex == null:
return
var path := tex.resource_path
if path.is_empty():
path = tex.name # built-in/placeholder texture
if visited.has(path):
return
visited[path] = true
inspected += 1
var size := _get_texture_size(tex)
if size <= 0:
return
var label := "%s/%s" % [material_path.get_file(), slot_name]
if size > MAX_TEXTURE_SIZE:
errors.append("Texture exceeds %d×%d: %s (%s) — %d× (path: %s)" % [MAX_TEXTURE_SIZE, MAX_TEXTURE_SIZE, label, path, size, path])
elif size > WARN_TEXTURE_SIZE:
warnings.append("Texture > %d×%d: %s (%s) — %d× (consider downscaling)" % [WARN_TEXTURE_SIZE, WARN_TEXTURE_SIZE, label, path, size])
func _get_texture_size(tex: Texture2D) -> int:
# Return the larger dimension
return max(tex.get_width(), tex.get_height())
func _collect_mesh_instances(node: Node, result: Array) -> void:
if node is MeshInstance3D:
result.append(node)
for child in node.get_children():
_collect_mesh_instances(child, result)
func _find_nodes_by_type(root: Node, expected_type: String, output) -> void:
if root.get_class() == expected_type:
output.append(root)
for child in root.get_children():
_find_nodes_by_type(child, expected_type, output)
func _find_materials_on_node(node: Node, visited: Dictionary, errors: Array, warnings: Array, inspected: int) -> void:
# Fallback: walk all nodes and check their material_override
for child in node.get_children():
if child.has_method("get_material_override"):
var mat = child.get("material_override")
if mat != null and mat is Material:
_check_material_textures(mat, visited, errors, warnings, inspected)
_find_materials_on_node(child, visited, errors, warnings, inspected)