port 2V2-b: GamePhase dispatch consumes the Entergame burst
PythonNetworkStreamPhaseGame/PhaseGameActor/PhaseGameItem/Command, NetworkActorManager and PythonChat/PythonChatModule are ported verbatim and dispatched at runtime; the 2V0 chat stub module is removed and initChat() runs before initnet() as in 40250. InstanceBase, PythonCharacterManager and GameLib ActorInstance/RaceData/RaceManager compile but stay TODO until 2V2-c/d wire them. Units not ported yet get their singletons from PythonBoot's GameSingletons (CPythonApplication member order) and platform/pending stand-ins generated by platform_stub.py pending; const char* stand-ins return "". port.login_flow's fake server now sends the CInputLogin::Entergame order (actor insert, NPC positions, PHASE_GAME, land list, time, channel, greet notice, ping) and a TraceError observer asserts no unknown packet header. port.login_live passes against the real server for 10 s of game phase. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
d16fabf61c
commit
62b6e23bfc
@@ -5,6 +5,12 @@
|
||||
platform_stub.py gen [<header>...] [--force] write extension/src/platform/<Lib>/<File>.cpp
|
||||
(no args: every platform header; never overwrites
|
||||
without --force, so hand-written implementations stay)
|
||||
platform_stub.py pending <header> <symbol>... [--ctor]
|
||||
append stubs of just those members (the linker's
|
||||
undefined-symbol names, demangled as ld prints them) to the stand-in of a not-yet-ported
|
||||
logic unit, extension/src/platform/pending/<Lib>/<File>.cpp;
|
||||
--ctor adds the class constructor/destructor so the
|
||||
singleton can exist (its members still default-construct)
|
||||
|
||||
`<header>` is a mirror path such as `EterLib/GrpBase.h`. A header is platform-layer when its same-stem
|
||||
40250 .cpp unit is (port_map.py LAYER_PATTERNS), or, without one, when its own path matches.
|
||||
@@ -262,11 +268,13 @@ class Collector:
|
||||
and not node.get("inline"):
|
||||
self.add("fn", f"{scope}{node['name']}", node)
|
||||
|
||||
def render(self) -> list[str]:
|
||||
def render(self, only=None) -> list[str]:
|
||||
out = []
|
||||
for kind, qname, node in self.decls:
|
||||
if node.get("mangledName") in self.defined:
|
||||
continue
|
||||
if only is not None and not only(qname, node):
|
||||
continue
|
||||
if kind == "var":
|
||||
out.append(f"decltype({qname}) {qname}{{}};\n")
|
||||
else:
|
||||
@@ -288,7 +296,8 @@ def namespaces(header: Path) -> dict[str, str]:
|
||||
return result
|
||||
|
||||
|
||||
def generate(rel: str) -> str:
|
||||
def generate(rel: str, only=None) -> str:
|
||||
"""The skeleton of `rel`; with `only(qname, node)`, just the declarations it accepts (the bodies alone)."""
|
||||
header = PORT / rel
|
||||
records, functions = scan(header)
|
||||
args, tu = gate_command(rel)
|
||||
@@ -304,9 +313,9 @@ def generate(rel: str) -> str:
|
||||
for n in nodes:
|
||||
if n.get("name") == name:
|
||||
col.root(n, ns.get(name, ""))
|
||||
body = col.render()
|
||||
if not body:
|
||||
return ""
|
||||
body = col.render(only)
|
||||
if not body or only is not None:
|
||||
return "\n".join(body)
|
||||
lib = rel.split("/")[0]
|
||||
stdafx = next((p.name for p in (PORT / lib).glob("*") if p.name.lower() == "stdafx.h"), None)
|
||||
unit = unit_of(rel)
|
||||
@@ -382,6 +391,44 @@ def cmd_gen(args) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_pending(args) -> int:
|
||||
rel = next((h.relative_to(PORT).as_posix() for h in PORT.rglob("*.h")
|
||||
if h.relative_to(PORT).as_posix().lower() == args.header.lower()), None)
|
||||
if not rel:
|
||||
raise SystemExit(f"{args.header}: not in the mirror")
|
||||
wanted = {s.strip().strip('"') for s in args.symbols}
|
||||
demangled: dict[str, str] = {}
|
||||
|
||||
def only(qname: str, node: dict) -> bool:
|
||||
mangled = node.get("mangledName", "")
|
||||
if mangled not in demangled:
|
||||
demangled[mangled] = subprocess.run(["c++filt", mangled], capture_output=True, text=True).stdout.strip()
|
||||
if demangled[mangled] in wanted:
|
||||
return True
|
||||
return args.ctor and node.get("kind") in ("CXXConstructorDecl", "CXXDestructorDecl") \
|
||||
and qname.split("::")[-2:-1] == [node.get("name", "").lstrip("~")] and qname.count("::") == 1
|
||||
body = generate(rel, only)
|
||||
if not body:
|
||||
print(f"none {rel} (no such undefined member)")
|
||||
return 0
|
||||
path = PLATFORM / "pending" / (rel.rsplit(".", 1)[0] + ".cpp")
|
||||
if path.exists():
|
||||
text = path.read_text(encoding="utf-8").rstrip("\n") + "\n\n" + body
|
||||
else:
|
||||
lib = rel.split("/")[0]
|
||||
stdafx = next((p.name for p in (PORT / lib).glob("*") if p.name.lower() == "stdafx.h"), None)
|
||||
unit = unit_of(rel) or rel
|
||||
text = (f"// Stand-in for the 40250 logic unit {unit}, not ported yet: the members the ported units\n"
|
||||
f"// reference, as {MARK} stubs. Delete this file in the commit that ports the unit into port/\n"
|
||||
f"// (docs/PORT-PLAN.md).\n"
|
||||
+ (f'#include "{lib}/{stdafx}"\n' if stdafx else "")
|
||||
+ f'#include "{rel}"\n\n#include "{"../" * (rel.count("/") + 1)}PlatformStub.h"\n\n' + body)
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(text, encoding="utf-8")
|
||||
print(f"wrote {path.relative_to(REPO)} (+{body.count('MT_PLATFORM_STUB();')} stubs)")
|
||||
return 0
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
sub = ap.add_subparsers(dest="cmd", required=True)
|
||||
@@ -390,6 +437,11 @@ def main() -> int:
|
||||
g.add_argument("headers", nargs="*")
|
||||
g.add_argument("--force", action="store_true")
|
||||
g.set_defaults(fn=cmd_gen)
|
||||
p = sub.add_parser("pending")
|
||||
p.add_argument("header")
|
||||
p.add_argument("symbols", nargs="*")
|
||||
p.add_argument("--ctor", action="store_true")
|
||||
p.set_defaults(fn=cmd_pending)
|
||||
args = ap.parse_args()
|
||||
return args.fn(args)
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
|
||||
Mechanical edits, and nothing else:
|
||||
- CP949 is decoded and written as UTF-8 (comments are Korean). A non-ASCII string or character
|
||||
literal would change bytes at runtime, so such a file is refused and must be handled by hand.
|
||||
literal would change bytes at runtime, so its non-ASCII characters are written as their CP949
|
||||
bytes in 3-digit octal escapes (`"\301\326..."`): the compiled bytes stay those of 40250.
|
||||
- CRLF becomes LF.
|
||||
- `#include "..."` paths get the on-disk case of the file they name (40250 builds on a
|
||||
case-insensitive file system and writes `../eterLib/StdAfx.h`, `Parser.h` for `parser.h`, ...),
|
||||
@@ -77,9 +78,12 @@ def mechanical(ref: Path, index: dict[str, Path], src: Path) -> str:
|
||||
raise SystemExit(f"{src.relative_to(ref)}: not CP949 ({e})")
|
||||
text = text.replace("\r\n", "\n").lstrip("")
|
||||
code = COMMENT.sub(lambda m: " " * len(m.group(0)), text)
|
||||
bad = [m.group(0) for m in LITERAL.finditer(code) if not m.group(0).isascii()]
|
||||
if bad:
|
||||
raise SystemExit(f"{src.relative_to(ref)}: non-ASCII literal(s), copy by hand: {bad[:3]}")
|
||||
# Literals are found with comments blanked out; offsets are the same in `text`.
|
||||
for m in reversed(list(LITERAL.finditer(code))):
|
||||
if not m.group(0).isascii():
|
||||
escaped = "".join(c if c.isascii() else "".join(f"\\{b:03o}" for b in c.encode("cp949"))
|
||||
for c in m.group(0))
|
||||
text = text[:m.start()] + escaped + text[m.end():]
|
||||
return INCLUDE.sub(lambda m: m.group(1) + fix_include(ref, index, src, m.group(2)) + m.group(3), text)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user