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:
2026-07-17 09:09:09 +02:00
co-authored by Claude Fable 5
parent ddc3d39d09
commit cffe760d36
10 changed files with 538 additions and 5 deletions
+10
View File
@@ -11,3 +11,13 @@ extends Resource
# KEY_M = 77, KEY_N = 78; -1 = no binding.
@export var default_key: int = -1
@export var preserves_state: bool = true
## D-254 §3 (companion app amendment, 2026-07-17): opt-out flag for the
## standalone Atlas companion (`make atlas`) — every registered app is hosted
## in the companion by default; set false only for apps that structurally
## require a playing character/gameplay context. The IN-GAME implant ignores
## this field entirely (zero behavior change there) — it exists solely for
## `atlas_standalone.gd`'s ImplantRegistry.instantiate_all(standalone=true)
## filter. Naming note: Jeroen's original suggested name was
## `availableInAtlasApp`; renamed to snake_case + "companion" (not "atlas")
## because the Atlas is itself one of the hosted apps, not the host.
@export var available_in_companion: bool = true
+8 -2
View File
@@ -27,9 +27,15 @@ func get_resolved_mode(app_path: String) -> int:
## Instantiate all registered apps whose manifest declares a scene_path and add
## them as children of parent. Manifests without scene_path are metadata-only
## and are silently skipped. Called from hud.gd._ready() — not from _ready() here.
func instantiate_all(parent: Node) -> void:
## and are silently skipped. Called from hud.gd._ready() in the normal game
## (standalone=false, default — every app is hosted, the flag below is
## irrelevant) and from atlas_standalone.gd (standalone=true, D-254 §3
## amendment) — in the companion, manifests with available_in_companion=false
## are skipped entirely (not instantiated, so never reachable by app-path).
func instantiate_all(parent: Node, standalone: bool = false) -> void:
for m in get_manifests():
if standalone and not m.available_in_companion:
continue
var scene_path: String = m.scene_path
if scene_path.is_empty():
continue