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,106 @@
import itertools
import os
from .compat import py312
from distutils import log
flatten = itertools.chain.from_iterable
class Installer:
nspkg_ext = '-nspkg.pth'
def install_namespaces(self) -> None:
nsp = self._get_all_ns_packages()
if not nsp:
return
filename = self._get_nspkg_file()
self.outputs.append(filename)
log.info("Installing %s", filename)
lines = map(self._gen_nspkg_line, nsp)
if self.dry_run:
# always generate the lines, even in dry run
list(lines)
return
with open(filename, 'wt', encoding=py312.PTH_ENCODING) as f:
# Python<3.13 requires encoding="locale" instead of "utf-8"
# See: python/cpython#77102
f.writelines(lines)
def uninstall_namespaces(self) -> None:
filename = self._get_nspkg_file()
if not os.path.exists(filename):
return
log.info("Removing %s", filename)
os.remove(filename)
def _get_nspkg_file(self):
filename, _ = os.path.splitext(self._get_target())
return filename + self.nspkg_ext
def _get_target(self):
return self.target
_nspkg_tmpl = (
"import sys, types, os",
"p = os.path.join(%(root)s, *%(pth)r)",
"importlib = __import__('importlib.util')",
"__import__('importlib.machinery')",
(
"m = "
"sys.modules.setdefault(%(pkg)r, "
"importlib.util.module_from_spec("
"importlib.machinery.PathFinder.find_spec(%(pkg)r, "
"[os.path.dirname(p)])))"
),
("m = m or sys.modules.setdefault(%(pkg)r, types.ModuleType(%(pkg)r))"),
"mp = (m or []) and m.__dict__.setdefault('__path__',[])",
"(p not in mp) and mp.append(p)",
)
"lines for the namespace installer"
_nspkg_tmpl_multi = ('m and setattr(sys.modules[%(parent)r], %(child)r, m)',)
"additional line(s) when a parent package is indicated"
def _get_root(self):
return "sys._getframe(1).f_locals['sitedir']"
def _gen_nspkg_line(self, pkg):
pth = tuple(pkg.split('.'))
root = self._get_root()
tmpl_lines = self._nspkg_tmpl
parent, sep, child = pkg.rpartition('.')
if parent:
tmpl_lines += self._nspkg_tmpl_multi
return ';'.join(tmpl_lines) % locals() + '\n'
def _get_all_ns_packages(self):
"""Return sorted list of all package namespaces"""
pkgs = self.distribution.namespace_packages or []
return sorted(set(flatten(map(self._pkg_names, pkgs))))
@staticmethod
def _pkg_names(pkg):
"""
Given a namespace package, yield the components of that
package.
>>> names = Installer._pkg_names('a.b.c')
>>> set(names) == set(['a', 'a.b', 'a.b.c'])
True
"""
parts = pkg.split('.')
while parts:
yield '.'.join(parts)
parts.pop()
class DevelopInstaller(Installer):
def _get_root(self):
return repr(str(self.egg_path))
def _get_target(self):
return self.egg_link