feat(client): T-1132 standalone Atlas companion shell + make atlas (D-254 SS3)
New atlas_standalone scene/script: attach-or-spawn boot (one REAL
connect_to_sim attempt at SR_PORT-or-9876 — a separate throwaway TCP
probe was proven by live run to kill a pre-accept-loop server via
broken handshake pipe; never abandon a connected socket), else spawn
--port 0 via new ServerProcess.start_with_pipe + LISTENING:{port}
stdout parse, retry against the resolved port. Reader role wired end
to end: protocol.encode_startup_message optional role param (empty
omits the wire key — byte-identical for all existing callers),
sim_bridge.connection_role suppresses the post-handshake
RequestAllSettings auto-send, hud_groups skips AutoPause/AutoResume
sends for readers (all three would otherwise burn Reader violation
strikes per the T-1130 matrix — endorsed by Oscar).
Generic implant host per D-254 SS3: the shell instantiates ALL
registered implant apps; implant_app_manifest gains
available_in_companion (opt-out, default true) and
implant_registry.instantiate_all a standalone filter param (default
preserves hud.gd behavior byte-identically). Boot order is
instantiate_all THEN open_app (reverse renders a permanently black
window — app_changed fires with no listener; matches hud.gd's order).
Owned-server lifecycle: _exit_tree stops a spawned child, attached
servers survive companion close. Known engine limitation documented:
raw SIGTERM bypasses all Godot notifications and orphans a spawned
server; WM close paths verified clean.
Live-verified: spawn-mode (301 systems rendered from systems.db over
the wire), attach-mode, two simultaneous readers, clean shutdown with
zero orphan processes. 14 new gdUnit tests (port/LISTENING parsing).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -639,14 +639,22 @@ static func _decode_enum_variant(raw) -> Dictionary:
|
||||
## Sent by the client immediately after handshake validation.
|
||||
## Server reads this to initialize SimRng (D-010, D-029).
|
||||
## character_visual: optional CharacterVisualDescriptor — included as "character_visual_descriptor" dict.
|
||||
## role: D-254 §2 ConnectionRole wire value — "" (default, every caller before
|
||||
## D-254) omits the "role" key entirely, matching server StartupMessage::role's
|
||||
## #[serde(default)] and decoding as ConnectionRole::Player — byte-identical to
|
||||
## pre-D-254 output. atlas_standalone.gd is the one caller that passes "Reader"
|
||||
## (unit enum variant, bare string per this file's header wire-format note —
|
||||
## same encoding as PlayerAction unit variants like "MoveNorth").
|
||||
static func encode_startup_message(
|
||||
world_seed: int, character_visual: Variant = null
|
||||
world_seed: int, character_visual: Variant = null, role: String = ""
|
||||
) -> PackedByteArray:
|
||||
var msg := {
|
||||
"world_seed": world_seed,
|
||||
}
|
||||
if character_visual != null and character_visual.has_method("to_dict"):
|
||||
msg["character_visual_descriptor"] = character_visual.to_dict()
|
||||
if not role.is_empty():
|
||||
msg["role"] = role
|
||||
var result = _mp().encode(msg)
|
||||
if result.status != null:
|
||||
push_error("Protocol: startup message encode failed: %s" % result.status)
|
||||
|
||||
Reference in New Issue
Block a user