Hoshe's finding: the H1 union-rejection mechanism correctly counts the
new browse discriminator, but ambiguous_union_frame_is_rejected was
carried over from PR #176 without a browse-inclusive case — a future
demux/ShapeProbe refactor could drop the fifth shape from the union
check with nothing to catch it. Adds browse+star_map and
browse+city_names union frames, both asserting rejection.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PathBuf::from("data/systems.db") was cwd-relative, and a spawned
server inherits GODOT's cwd — which is client/ (Godot's --path client
chdirs), so CultureResolver, CityContextReader, BodySourceResolver and
BrowseReader all silently failed in every make atlas / make game
spawn. Wave 1 masked it: the star map serves from star_map_data.json,
so 'renders 301 systems' never touched the DB; T-1133's browser was
the first DB-backed consumer to hit it live (Stig, /proc/PID/cwd).
resolve_systems_db_path(): exe-anchored <exe_dir>/../../data/systems.db
first (cwd-independent — the actual fix), then cwd-relative
data/systems.db (cd server && cargo run), then server/data/systems.db
(repo-root invocations); first existing candidate wins, info-logged.
Pure systems_db_candidates() split out as the testable seam (5 unit
tests — incl. pinning Path::parent() of a bare filename = Some(""),
not None). world_root now derives from the resolved ABSOLUTE path so
its ancestors().nth(3) arithmetic holds unconditionally instead of
silently falling back to ".." when canonicalize failed.
Verified all three launch styles; make atlas spawn shows 5 reader fds
open on server/data/systems.db via /proc/PID/fd.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New implant app (app_path implant/browser, key B, fullscreen), sibling
to the Atlas per Jeroen's IA ruling: kind picker -> generic filterable
index (live search-mode typing) -> generic detail, parameterized per
kind, composed entirely from D-169 components. available_in_companion
left unset (default true) — the app appears in the companion shell
automatically via the generic-host seam, zero companion-side wiring.
browser_adapter.gd is the sole home of literal wire field names: maps
Oscar's BrowseResponse contract ({id, primary, secondary} index rows;
BrowseDetail enum-as-single-key-map) to view models for all six kinds,
folding join partners (system economy/factions/culture, corporation
presence, commodity production chains with nested Leontief inputs).
browse_protocol.gd split out of protocol.gd (max-file-lines);
sim_bridge gains browse_response_received + request_browse_index/detail.
Live-data catch: a present-but-NULL key (unnamed asteroid belt
proper_name) bypasses Dictionary.get fallbacks and rendered '<null>' —
_display_or() null-vs-absent helper applied across all six detail
mappers, 4 regression tests distinct from the absent-key cases.
43 gdUnit adapter cases; full suite 3074 green. Live-verified against
a real server + real systems.db: all six kinds Ready with real row
counts (301/3240/466/165/36/28), detail drill-down, NotFound on bogus
ids. Spawn-mode DB resolution issue found during verification is
pre-existing (cwd-relative data/systems.db) — server-side fix follows
separately.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
browse_reader.rs: BrowseReader on the CityContextReader::open() pattern
— six index + six detail reads against systems.db (bodies filterable by
containing system; system/corporation/commodity details fold their join
partners). browse_proxy.rs: BrowseRequest{browse, kind, query} /
BrowseResponse{kind, status, index, detail} wire types + dispatcher;
BrowseIndexRow{id, primary, secondary} generic across kinds;
BrowseDetail a per-kind enum of field-exhaustive structs.
Demux: Inbound::BrowseRequest is the FIFTH map shape — deliberately the
last; the doc's four-shape ceiling is re-pinned at five with rationale
(six kinds x two forms folded into ONE envelope whose internal enums
pick sub-behavior, the AtlasLayerRequest.up_to precedent, instead of
twelve top-level shapes) and a hard rule that a sixth shape must
migrate to the D-225 tagged-envelope framing. Served for BOTH roles,
connection-tagged 1:1 in-order like atlas/starmap/citynames.
serve_browse_requests pub so integration tests drive the true
end-to-end pipeline. Wire-only per D-254 (T-949 precedent); v1
exclusions (cascade geometry, event logs) respected.
30 unit tests + 7 bridge_tcp integration tests (six-kind round-trip
over real TCP, reader-can-browse, no crossed responses between two
readers, unknown-id NotFound, empty-table Ready); 2 pre-existing tests
updated for the new receive_bridge_inputs parameter.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Tyre's finding: _on_hud_app_changed ran open_app synchronously from
INSIDE close_app's app_changed emit; close_app continues past the emit
and resets _active_app, clobbering the re-open — Atlas left visible
(z already raised) but is_app_active() false, so atlas_app's input
guard rejects every key: keyboard soft-lock after one M/Escape on the
reach screen. Fix: open_app.call_deferred, out of the signal frame.
Two regression tests pin the contract at the HudGroups level (no
server needed): the synchronous shape must keep getting clobbered and
the deferred shape must survive — if HudGroups emit semantics ever
change, both flag the contract shift for a deliberate look.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
ConnectionRole (Player|Reader, serde-default Player for wire back-compat;
shaped for a future TradingReader) on StartupMessage. BridgeResource
rewritten as 0-1 Player + 0-N Readers with ConnectionId; per-tick
accept_new_connections loop replaces the single blocking accept (the
listener is cloned non-blocking into ConnectionListener). First
connection installs per startup.role — spawn-mode Readers are often the
only connection a server gets.
Permitted-message matrix: readers may handshake and issue
Atlas/StarMap/CityNames requests (responses connection-tagged, own
requests only); PlayerInput from a reader is dropped with a strike
(disconnect at 3); ObserverSnapshot has no reader-facing path at all.
Shutdown scope: only a PLAYER send failure flips ServerRunning —
reader-only servers idle with the snapshot queued; reader disconnects
never kill the session.
Deliberately out of scope, documented at the call site: character spawn
stays in monolithic world-setup (reader-first servers carry an inert
unpiloted PlayerCharacter); no total-reader-count cap (per-reader frame
cap only, loopback-only scope).
6 new bridge_tcp integration tests; layer3 subprocess test exercises the
accept-loop end to end.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
D-227: records the two-tier driver model (body envelope from authored
params only, D-240 tilt exclusion upheld; position tier with fjord/
latitude/wetness/heterogeneity), the shared invent_primitives path,
the scatter floor-vs-ceiling reading, shoreline carving, and the
one-step-stale circularity ruling. D-239: Marine appended at
discriminant 6, morphology-derived verdict, airless precedence,
non-semantic Ord position.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Mechanical call-site updates (7) for the invent_primitives signature;
believability golden regenerated via UPDATE_GOLDEN=1. Deltas are
deliberate: slope_q/elev_q spreads widen, vegetation_classes +1
(Marine), water-districts-wet coherence reaches 46/46 and 19/19
(T-1082 class closed — classification and coastline are the same line).
Reductions (ocean_fraction distinct, morphology_zones on GJ244Ad)
are the eager pseudo-grid point-sampling coasts honestly instead of
area-averaging them into synthetic intermediates; believability
coast-sampling resolution is Q-123 calibration territory. All 37
derivation-harness law checks unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
aliveness_probe --render gains a 6th glaciation panel; glaciated
districts get an ice tint keyed on GlaciationGrade, and Marine
vegetation renders [0,96,172] — distinct from the morphology water
tones so district-carrier vs voxel-generator disagreement stays
visible in ladders.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Future implant apps/screens automatically extend the external app; per-app manifest bool (default true) locks out gameplay-dependent apps; in-game implant ignores the flag. Jeroen's suggested name availableInAtlasApp recorded with the rename rationale.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Renders true-2km district maps (derive_district per cell, NOT the coarse eager grid) as PNGs: morphology (17-zone palette transliterated from atlas_marker_overlay.gd, sync-commented), elev/temp/moisture/vegetation panels, anchor crosshair, km-honest filenames + manifest. believability.rs gains cascade_snapshot_for_body (cascade_for_body now a thin wrapper); probe re-derives the D-203-freed TerrainAnalysis via run_layer1 only under --render. Determinism proven: 5/5 panels byte-identical across passes AND cross-process (md5s reproduced in a fresh run). ~8us/district debug. First-ever look at the district layer produced the T-1125/T-1126/T-1127 findings (D-227 seam ends at ~500km; ocean-blind vegetation; unrendered glaciation).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The _comment_atlas_goldens key inside scenarios{} hit _print_list's .get() on a bare String (reproduced: Invalid call on String at visual_capture.gd:699). Moved to a top-level key alongside resolution/golden_dir; _print_list now also skips _-prefixed and non-Dictionary entries so future comment keys cannot crash it. Verified: --list runs clean, JSON valid.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Root-caused with server trace logs: Walk stance throttles 1 move per 2 ticks and SILENTLY discards early moves (movement.rs apply_move -> try_move, TRACE-only) — under load the test's MoveEast landed exactly 1 tick after MoveNorth and was consumed. Server behavior is correct, deliberate D-053, pinned by Rust tests; production clients re-send while keys are held. Fix: COOLDOWN_TICKS=3 post-ack spacing, same as test_sprint2_proof.gd (T-1068) whose fix never reached this file due to helper copy-paste. Pre-fix repro at run 17/30 under load; post-fix 30/30 stress green + full suite 2956/2956.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Second distinct flake in this suite today (gate run: MoveEast observed mid-move at the 5s window). The old accounting accrued elapsed only on empty polls — load-dependent effective window. Wall-clock deadline via Time.get_ticks_msec; ceiling 5s->10s (exit-on-arrival, costs nothing when healthy). Targeted suite 6/6.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three of the four new tests skipped scene-tree entry, so _ready() never resolved _canvas and set_view crashed _apply_transform. Mirrors the fourth (passing) test. Targeted suite green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Curated from the 42-shot matrix, one per body spanning all zoom levels and overlay sets; self-consistency verified 12/12 at exact pixel match on this box. .import sidecars force-added per the existing golden convention. Bodies: GJ380c, GJ144e, GJ338Bd, GJ820Bc (Metropolis + Cygni yard), GJ251c (T-1116 water/no-roads exhibit), GJ244Ad (frozen), GJ445c-m1 (moon).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AtlasViewer gains public set_view(zoom, offset)/get_view_offset (clamped, review-flagged API gap) + gdUnit suite. visual_capture.gd gains an atlas_matrix scenario (one boot, tests/atlas_shots.json-driven: 7 verified bodies x zoom x overlay sets, signal-gated waits on atlas_layers_received/city_names_received with timeout fallback). 12 individually-addressable golden scenarios registered in tests/visual.json. Root-level settings.db* gitignored (capture-spawned servers write their settings store to cwd). Fixes found live: GDScript lambdas capture value-type locals BY VALUE (wait-flag moved to Array carrier); RegionalScreen.enter() no-ops when the screen id is unchanged (direct enter() per body after the first).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>