chore(bench): add client bench harness and Phase 2 handoff
The client now boots cleanly on the bench machine (the CAC spawn-key
crash was fixed in 8e8483d). Add the harness used to drive bench scenes
and capture frametimes, the bench-world generation workflow, and a
self-contained handoff brief. All files live under analysis/ (packwizignored),
so nothing player-facing changes.
- harness/rcon.py: minimal RCON client for the temp worldgen server
- harness/run_scene.py: apply variant -> launch Prism under MangoHud ->
xdotool scene commands -> p50/p95/p99/1%-low from the CSV
- HANDOFF.md: state, gotchas, next steps
- build.py: disable DH autoUpdater for bench variants (suppress modal)
This commit is contained in:
1 parent
8e8483d072
commit
e5e369a693
7 files changed
+611
-1
No files matched your search
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Minimal Minecraft RCON client (stdlib only).
|
||||
|
||||
Usage: python3 rcon.py <host> <port> <password> <command> [command ...]
|
||||
Exit non-zero if the server reports a failure.
|
||||
"""
|
||||
import socket
|
||||
import struct
|
||||
import sys
|
||||
|
||||
|
||||
class Rcon:
|
||||
def __init__(self, host: str, port: int, password: str, timeout: float = 10.0):
|
||||
self.host, self.port, self.password = host, port, password
|
||||
self.timeout = timeout
|
||||
self._id = 0
|
||||
|
||||
def _send(self, sock: socket.socket, ptype: int, body: str) -> int:
|
||||
self._id += 1
|
||||
rid = self._id
|
||||
payload = struct.pack("<ii", rid, ptype) + body.encode("utf-8") + b"\x00\x00"
|
||||
sock.sendall(struct.pack("<i", len(payload)) + payload)
|
||||
return rid
|
||||
|
||||
def _recv(self, sock: socket.socket):
|
||||
raw = b""
|
||||
while len(raw) < 4:
|
||||
chunk = sock.recv(4 - len(raw))
|
||||
if not chunk:
|
||||
raise ConnectionError("RCON connection closed")
|
||||
raw += chunk
|
||||
(length,) = struct.unpack("<i", raw)
|
||||
body = b""
|
||||
while len(body) < length:
|
||||
chunk = sock.recv(length - len(body))
|
||||
if not chunk:
|
||||
raise ConnectionError("RCON connection closed")
|
||||
body += chunk
|
||||
rid, ptype = struct.unpack("<ii", body[:8])
|
||||
return rid, ptype, body[8:-2].decode("utf-8", "replace")
|
||||
|
||||
def command(self, cmd: str) -> str:
|
||||
with socket.create_connection((self.host, self.port), timeout=self.timeout) as sock:
|
||||
sock.settimeout(self.timeout)
|
||||
rid = self._send(sock, 3, self.password)
|
||||
rid2, _, _ = self._recv(sock)
|
||||
if rid2 == -1:
|
||||
raise PermissionError("RCON auth failed")
|
||||
self._send(sock, 2, cmd)
|
||||
_, _, text = self._recv(sock)
|
||||
return text
|
||||
|
||||
|
||||
def main(argv):
|
||||
if len(argv) < 5:
|
||||
print(__doc__)
|
||||
return 2
|
||||
host, port, password = argv[1], int(argv[2]), argv[3]
|
||||
rc = 0
|
||||
for cmd in argv[4:]:
|
||||
try:
|
||||
out = Rcon(host, port, password).command(cmd)
|
||||
except Exception as exc: # noqa: BLE001 - CLI surface
|
||||
print(f"! {cmd}: {exc}", file=sys.stderr)
|
||||
rc = 1
|
||||
continue
|
||||
if out.strip():
|
||||
print(out.strip())
|
||||
lowered = out.lower()
|
||||
if "unknown" in lowered or "failed" in lowered or "error" in lowered:
|
||||
rc = 1
|
||||
return rc
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(sys.argv))
|
||||
Reference in new issue
Block a user