Commit Graph
193 Commits
Author SHA1 Message Date
jpmschweitzerandClaude Opus 4.6 702fed5922 fix(client): address PR #141 review — parse-order, tween guard, test fixes
- Fix autoload parse-order violations: sim_bridge.gd, input_mapper.gd,
  audio_manager.gd now use load() for class_name types instead of
  direct references (LocalBridge, ServerProcess, Constants)
- Collapse redundant tween validity guard in dialogue_box.gd to
  is_instance_valid(panel) only
- Add clarifying comments to fog test resize assertions (8-tile
  padding trigger, 32x32 fixture assumption)
- Fix test_examine_display_sprint18 case 2: GameState.has() →
  "field" in GameState (Node vs Dictionary API)
- Fix test_game_state_sprint20: rename before_each → before_test
  (GdUnit4 lifecycle hook)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-02 22:50:17 +02:00
jpmschweitzerandClaude Opus 4.6 e002f811f0 fix(client): free camera review fixes — action toggle, zoom reset, discrete guard, tests (#898)
- Replace raw KEY_F4 check with Input.is_action_just_pressed("free_camera") to
  consume the registered project action (matches F3/F12 dev toggle pattern)
- Reset camera.zoom to Vector2.ONE when toggling free camera off so zoom does
  not bleed into normal gameplay
- Add free_camera_mode guard to InputMapper._unhandled_input() so discrete
  actions (INTERACT, stance, pause) are suppressed alongside movement
- Add client/tests/test_free_camera.gd: flag default, movement suppression,
  discrete action suppression, zoom constant contracts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-02 18:03:06 +02:00
jpmschweitzerandClaude Opus 4.6 3f5b4258ba feat(client): sprint 38 — free camera viewer, archetype strip, test fixes
- Add free camera mode (F4 toggle): WASD pan, scroll zoom, decoupled
  from player position (#898)
- Strip archetype-driven code: remove character_archetype, lattice_profile,
  and lattice color palettes from client (#882)
- Fix confrontation_monologue signal not firing in headless test mode (#867)
- Revive fog state behavioral tests: EXP_EXPLORED persistence, grow-only
  bounds, texture-resize copy, BoundaryWall handling (#879)
- Triage pre-existing test failures: fix examine_display dismiss timing,
  fog test position fragility, rendering snapshot assertions,
  time_display format (#871)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-02 10:19:11 +02:00
jpmschweitzerandClaude Opus 4.7 60733738f2 fix(client): PR #135 review — T1/T3/H4-H7 blocking + nits
Code changes addressing PR #135 review (Tyre + Hoshe):

- **T3 (blocking):** test_merge_path_flows_sprint37.gd `_load_main_menu`
  and `_load_char_create` now assert the scene loaded instead of silently
  returning. Missing .tscn → red test, not falsely green.
- **T1:** sim_bridge.gd signal `handshake_complete(protocol_version: int)`
  was D-192 residue with no listeners. Drop the int parameter entirely
  and the literal-0 emit.
- **H4:** test_new_game_catalog_snapshot_resolves_loading_state now
  asserts SimBridge.state == CONNECTED terminus, not just the loading
  flag — guarantees full flow completion, not merely flag-clear.
- **H5:** test_protocol_bridge.gd file-level comment refreshed; drops
  reference to removed protocol-version check tests.
- **H6:** test_p0_regressions.gd `_make_snapshot_bytes` comment refreshed
  and version field removed from fixture dict (D-192: not required).
- **H7:** test_merge_path_flows_sprint37.gd `_make_catalog_snapshot`
  drops version field from fixture dict (D-192).

Follow-up tickets filed for reviewer suggestions:
- **T2:** #889 — revive EntityRenderer sprite constants coverage
  (D-044 ENTITY_WIDTH/HEIGHT, asserted by deleted test_sprite_integration).
- **T4:** #890 — UI timeout fallback for bookmark catalog wait in
  main_menu (systemic 'catalog never arrives' class beyond #872's
  TCP-batch race).
- **T5/T6:** #891 — scene-flow test tier docs + test-only reset
  helpers (SimBridge.reset_for_test, MetaStack.reset_for_test) +
  minimal public API on scenes so UI refactors don't break all four
  flow tests simultaneously.

Verification:
- `make lint-client` — no script errors
- `gdlint client/scripts/ client/ui/` — no problems
- `make test-client` — 2428/2488 passing. 60 remaining failures are
  pre-existing, unrelated to sprint 37 (test_dialogue_sprint20 #558
  signals, test_input_roundtrip integration-sans-server, etc.).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 10:06:39 +02:00
jpmschweitzerandClaude Sonnet 4.6 d72fcc7847 fix(protocol): drop PROTOCOL_VERSION lockstep — D-192 (#875)
Removes the version-mismatch guard from Protocol.decode_snapshot() and the
PROTOCOL_VERSION constant from the client (server side done in #874).

Core changes:
- protocol.gd: remove const PROTOCOL_VERSION, remove version mismatch guard,
  remove "version" from return dict, add gauntlet_mode/room_id decode
- sim_bridge.gd: remove handshake version check; relax handshake guard to
  require only a valid Dictionary (server no longer sends protocol_version);
  emit handshake_complete(0) for API compat
- loading_screen.gd: drop "· protocol N" suffix from version label
- test_harness.gd: replace Protocol.PROTOCOL_VERSION with literal 23

Test updates (21 files): replace "version": Protocol.PROTOCOL_VERSION with
"version": 23 in all snapshot bytes dicts; remove snapshot.version == N
assertions; remove version-rejection tests (test_rejects_version_6,
test_decode_snapshot_rejects_missing_version, test_decode_snapshot_rejects_old_version,
test_protocol_rejects_version_mismatch, test_sim_bridge_test_snapshot_uses_current_protocol_version).

Also includes: #872 bookmark_catalog carry-forward regression test, and
#873 merge-path flow tests (test_merge_path_flows_sprint37.gd).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 17:29:49 +02:00
jpmschweitzer 1b8e227413 refactor(ui): PR #134 review — MetaScreen/ESC chain tightening, D-192 reword
Addresses Tyre's 9 architecture items from the sprint-36 client review.

- decisions/architecture.md (Tyre #1): D-192 now says "deprecate; removal
  tracked in #868" instead of "remove". The branch does not remove the
  version field or guard — that belongs in the coordinated server+client
  PR. The decision text now matches the code on this branch.
- meta_stack.gd (#2): handle_escape() on a screen with
  closable_by_escape=false now consumes the event unconditionally. Was
  returning whatever on_escape() returned, which default-returned false
  and leaked ESC into main.gd's implant/settings chain — opening the
  settings dialog behind the loading screen.
- debug_console.gd (#4): drop the direct KEY_ESCAPE branch in
  _unhandled_input. ESC now falls through to main.gd → MetaStack, which
  finds the console on top of the stack and closes it via the normal
  path. Other keys are still consumed so movement/action can't leak.
- main.gd (#6, #10): extract the ESC priority chain into
  _handle_menu_key() so "MetaStack → implant → settings" is a named
  thing. Add a comment near connect_to_sim explaining that
  GameState.bookmark_catalog survives the Option A scene transition via
  the autoload.
- main_menu.gd (#7): header comment documenting the double LoadingScreen
  lifecycle — safe today because main_menu.tscn and main.tscn never
  co-exist, noted for future promotion to autoload if that changes.
- meta_screen.gd (#8): apply captures_input symmetrically in open()/
  close() — was set in open() only, so a screen changing the flag
  between open+close kept the opened value forever.
- meta_screen.gd (#9): on_escape() docstring clarifies the tri-state
  (consume-and-hold / consume-and-close / ignore) — and that
  closable_by_escape=false is the screen-wide way to say
  "consume-and-hold".
- bug_report_dialog.gd (#11): capture_cancelled now emits from
  on_close() (covers any close path — ESC, MetaStack pop, programmatic
  close) rather than only on_escape(). A new _completed flag
  distinguishes completion from cancel so the two signals stay
  mutually exclusive.
2026-04-21 12:02:09 +02:00
jpmschweitzer effb83a0d6 fix(ui): PR #134 review — character creation bugs + protocol default
Addresses Hoshe's 3 code-quality items from the sprint-36 client review.

- character_creation: drop CARDINAL_NAMES (was [south, east, north,
  west]) and use CARDINAL_DIRS ([south, west, north, east]) for both
  facing and screenshot filename label. The two arrays indexed by the
  same _screenshot_cardinal_idx produced swapped labels at indices 1
  and 3 — screenshots at those positions had filenames that did not
  match the character's actual facing.
- character_creation: Enter/KP_ENTER now honors _footer_start.disabled.
  Without a bookmark selected the Start button disables, but the
  keyboard path called _on_start() unconditionally — a player could
  confirm creation with empty bookmark/location strings. Guard at the
  top of _on_start.
- protocol.gd: raw_bm.get("career", "tycoon") hardcoded a content
  default in the wire decoder — a missing server field silently became
  "tycoon". Empty string is the correct protocol default;
  _make_bookmark_card already skips the career label when empty.
2026-04-21 12:01:48 +02:00
jpmschweitzerandClaude Opus 4.6 35858fa0fa feat(ui): Bookmark tab content + CharacterProfile signal payload (Workstream 6)
Fills in the Bookmark tab stubbed in W5 with the full spec from
Araminta: card list + detail view + start-button gating. Also changes
the creation_confirmed signal to carry a CharacterProfile instead of
bare CharacterVisualDescriptor, consolidating bookmark + location
selection into one payload object.

Bookmark tab (left pane, 35%):
- ScrollContainer over VBoxContainer of card Buttons, one per entry in
  GameState.bookmark_catalog. Each card: title (PRIMARY_TEXT,
  font_header 15px) / subtitle (DIM_TEXT, font_small 10px, clipped) /
  career badge (ACCENT_ACTIVE, all-caps). Selected state uses existing
  ITEM_SELECTED_BG + ITEM_SELECTED_BORDER. custom_minimum_size
  Vector2(180, 64).

Detail view (right pane, 65%):
- ImplantPanel composed via add_component:
  - ImplantHeader (bookmark.title, bookmark.subtitle)
  - ImplantSeparator
  - ImplantTextBlock (flavor, autowrap, PRIMARY_TEXT)
  - ImplantSeparator
  - ImplantDataRow CAREER (accent_active) / CAPITAL (accent_positive,
    format "%d Tractus") / STARTING LOCATION
  - ImplantSeparator
  - [location picker space reserved — W7 fills it]

Selection:
- Card click stores _selected_bookmark_id, auto-assigns
  _selected_location_id from bookmark.default_location, rebuilds
  detail view.
- Start button (footer) gated on both _selected_bookmark_id and
  _selected_location_id non-empty.
- Randomize while Bookmark tab is active picks a random bookmark +
  one of its allowed_locations and skips appearance randomization.

Signal contract change:
- creation_confirmed(profile: CharacterProfile) replaces
  creation_confirmed(descriptor: CharacterVisualDescriptor).
- CharacterProfile now extends RefCounted (was Resource) with
  non-exported fields — it's a one-shot signal payload, never
  persisted. This also sidesteps the scanner error that the prior
  @export var descriptor: CharacterVisualDescriptor on a Resource
  caused (RefCounted types cannot be @export-ed).
- _on_start emits a CharacterProfile built from _descriptor +
  _selected_bookmark_id + _selected_location_id, then sends
  ConfirmBookmark via SimBridge.send_named_action before scene
  transition.

Test updates:
- test_character_creation_sprint28.gd signal receivers switched to
  untyped to accept CharacterProfile without hitting class_name
  parse-order at test-suite scan time. 88/88 pass.

Verification:
- gdlint clean
- godot --headless --path client --quit — no SCRIPT ERROR (prior
  character_profile.gd scanner noise now gone after the RefCounted
  conversion)
- test_character_creation_sprint28 88/88, test_protocol 62/62,
  test_implant_nav_stack 52/52

Workstream 7 (location picker as sub-component of the Bookmark detail
view) follows. W8 fills the Skills tab.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-20 19:36:00 +02:00
jpmschweitzerandClaude Opus 4.6 2104348000 feat(client): Option A pre-game flow + ESC priority chain (Workstream 4)
main_menu now connects SimBridge before character_creation opens, gating
the transition on first ObserverSnapshot carrying a bookmark_catalog.
Loading screen is shown during the connect; on cancel the SimBridge
subprocess is torn down and the player returns to main_menu. Catalog is
read straight from GameState.bookmark_catalog in W6.

Flow (Option A):
1. New Game → SessionManager.new_game() creates save dir
2. main_menu pushes loading_screen via MetaStack with "Connecting to
   simulation..." message
3. SimBridge.connect_to_sim() spawned; main_menu listens on
   connection_state_changed, then on snapshot_received for the catalog
4. On catalog arrival: loading_screen closed, scene-transition to
   character_creation
5. character_creation Cancel → SimBridge.disconnect_from_sim() + scene
   transition back to main_menu (Tyre's recommendation: clean state per
   session over warm-start savings)
6. character_creation Start → ConfirmBookmark sent (stubbed for W4 with
   first catalog entry; real bookmark + location from W6's UI)

ESC priority chain in main.gd OPEN_MENU handler:
- MetaStack.handle_escape() first — closes the topmost meta overlay
- HudGroups.is_implant_active() / close_app() — closes active implant
- Fallback: toggle settings_dialog (existing W2 behavior)

Files:
- sim_bridge.gd: send_named_action(action_name, action_data) helper.
  Bridges named tag-enum PlayerActions (RequestBookmarkCatalog,
  ConfirmBookmark) into the existing outbound buffer, parallel to
  send_input's InputMapper.Action handling.
- loading_screen.gd: set_message(text) for the connecting/loading label.
- main_menu.gd: full Option A flow rewrite. Tracks _waiting_for_catalog
  so re-clicking New Game during connect is a no-op.
- character_creation.gd: _on_back disconnect path + _on_start
  ConfirmBookmark stub. MAIN_MENU_SCENE / GAME_SCENE constants.
- main.gd: connect_to_sim guard (don't reconnect when Option A leaves
  it CONNECTED). ESC chain wiring.

Verification:
- gdlint clean
- godot --headless --path client --quit — no SCRIPT ERROR
- test_protocol 62/62, test_implant_nav_stack 52/52, test_client_p3
  24/24, test_ui_framework_sprint15 54/54

Pre-existing failing suites unchanged: test_sprint2_proof,
test_dialogue_sprint18, test_client_p2 (camera-smoothing assertions
that pre-date W4 — main.gd has disabled position_smoothing_enabled
since #117 / #501 / #117 manual-lerp; tests were stale).

Workstream 5 (3-tab restructure of character_creation: Bookmark /
Appearance with sub-nav / Skills / Debug) lands next.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-20 11:30:15 +02:00
jpmschweitzerandClaude Opus 4.6 41e895796c feat(client): protocol v23 — bookmark_catalog decode + bookmark actions (Workstream 3)
Adds client-side wire support for the bookmark catalog (#614) and the
two associated player actions. PROTOCOL_VERSION bumps from 21 to 23:
- v22 (server): RequestBookmarkCatalog + ConfirmBookmark player actions
- v23 (server): bookmark_catalog field on ObserverSnapshot

Decode:
- protocol.gd decode_snapshot extracts optional bookmark_catalog.
  Defensive parse of BookmarkWire fields (id, title, subtitle, flavor,
  default_location, allowed_locations, allowed_locations_cultures,
  career, starting_capital_tractus). Missing or malformed → null.
- snapshot_handler.gd caches the catalog into GameState.bookmark_catalog
  on each snapshot (server pushes on tick 0; re-fetchable via
  RequestBookmarkCatalog).
- GameState gains bookmark_catalog: Array = [] (untyped per autoload
  parse-order discipline; default empty so callers can iterate without
  null checks).

Encode:
- encode_request_bookmark_catalog() — unit variant, sent to trigger a
  re-push if the cached catalog is missing.
- encode_confirm_bookmark(bookmark_id, starting_location_id) — struct
  variant matching server rmp_serde shape. Called from character
  creation on Start (lands in Workstream 6).

Tests:
- 5 new cases in test_protocol.gd: hand-built bookmark_catalog decode
  (all 9 fields asserted), fixture-based decode round-trip, missing-
  field null behavior, RequestBookmarkCatalog encode roundtrip,
  ConfirmBookmark encode roundtrip.
- All 12 existing snapshot fixtures regenerated from server via
  `cargo test --test gen_fixtures -- --ignored`. The new
  snapshot_with_bookmark_catalog.msgpack fixture was generated by the
  same pass.

Verification:
- gdlint clean
- godot --headless --path client --quit — no SCRIPT ERROR
- test_protocol 62/62, test_client_p3 24/24, test_implant_nav_stack
  52/52, test_implant_registry 42/42, test_implant_app_lifecycle 36/36

Workstream 4 (Option A sequencing via loading_screen + SimBridge
connect) lands next.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-20 00:25:10 +02:00
jpmschweitzerandClaude Opus 4.6 f24d08f756 refactor(ui): migrate 6 meta screens to MetaScreen pattern (Workstream 2)
Relocates main_menu, character_creation, settings_dialog, debug_console,
bug_report_dialog, loading_screen from flat client/ui/ into structured
client/ui/meta/screens/<name>/. All six now extend MetaScreen instead
of Control; the base handles open/close lifecycle, visibility,
captures_input, and — for overlays — the sim-pause contract.

Screen policies set per Tyre's proposal:
- settings_dialog: pauses_sim=false, PUSHES onto MetaStack
- debug_console: pauses_sim=true, PUSHES (D-088 routing via base)
- bug_report_dialog: pauses_sim=true, PUSHES
- loading_screen: closable_by_escape=false, PUSHES
- main_menu, character_creation: scene-roots, extend MetaScreen for
  the lifecycle contract only, do NOT push onto the stack

character_creation stays at its current surface (tabs, descriptor,
creation_confirmed signal unchanged). Tab consolidation and
CharacterProfile migration happen in Workstreams 5 and 6.

Knock-on changes:
- main.tscn ModalLayer CanvasLayer renamed to MetaLayer; main.gd
  @onready refs updated; constants.gd comment updated; test_client_p3
  and test_ui_framework_sprint15 assertions updated; test_monologue_display
  and .tscn header comments updated.
- OPEN_MENU handler now pushes settings_dialog onto MetaStack before
  calling open(). Full ESC priority chain lands in Workstream 4.
- atlas_app.gd: _unhandled_key_input signature widened from
  InputEventKey to InputEvent with an is-check, per Godot 4 API. Pre-
  existing narrowing was silently tolerated until main.tscn started
  fully instantiating under the new pattern.
- test_client_p3: entity_renderer type annotations corrected from
  ColorRect to Sprite2D (stale since a prior refactor); facing
  indicator rotation assertion switched to angle_difference() for
  modular-safe comparison.

Verification:
- gdlint client/scripts/ client/ui/ — zero problems
- godot --headless --path client --quit — no SCRIPT ERROR
- test_client_p3: 24/24 pass
- test_ui_framework_sprint15: 54/54 pass
- test_implant_nav_stack: 52/52 pass
- test_implant_registry: 42/42 pass
- test_implant_app_lifecycle: 36/36 pass

Workstream 1 foundation (84105916) remains unchanged. Workstreams 3-8
follow: protocol layer, Option A sequencing, 3-tab restructure,
Bookmark tab, location picker, Skills stub.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-19 21:58:24 +02:00
jpmschweitzerandClaude Opus 4.6 84105916cd feat(ui): introduce MetaScreen pattern foundation (#618, #680)
Establishes the faux-game-menu base pattern for non-diegetic UI,
analogous to ImplantApp but for pre-gameplay and meta-overlay
screens (main menu, character creation, settings, debug console,
bug report, loading screen). Workstream 1 of the MetaScreen
refactor — foundation only, no screen migrations yet.

- client/ui/meta/meta_screen.gd: base class (Control) with
  HIDDEN/OPENING/OPEN/CLOSING phase tracking, three orthogonal
  policy booleans (pauses_sim, closable_by_escape, captures_input),
  open/close lifecycle, on_escape contract, closed + escape_pressed
  signals, subclass hooks (on_open, on_close).
- client/ui/meta/meta_stack.gd: autoload coordinator. Overlay stack
  with push/pop/top/is_active; handle_escape chain; sim-pause
  coordination via SimBridge when pauses_sim=true; meta_active_changed
  signal. All class references kept inside method bodies — no
  top-level class_name refs, matching HudGroups / GameState
  autoload parse-order discipline.
- client/scripts/character_profile.gd: Resource wrapping the
  visual descriptor with bookmark_id and start_location_id.
  Target of the creation_confirmed signal once the character
  creation flow migrates.
- client/project.godot: MetaStack registered as autoload after
  HudGroups, before ImplantRegistry.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-19 20:18:29 +02:00
jpmschweitzerandClaude Opus 4.6 284ac44412 feat(ui): PR #131 review round 4 — scene_path, schema_version, screens absorption
Three architectural review comments implemented per Tyre's proposals:

#3 — scene_path consumption (full, Option A)
- ImplantRegistry.instantiate_all(parent) loads and instantiates all
  registered apps with a declared scene_path. Manifests with empty
  scene_path are treated as metadata-only (silently skipped).
- ImplantRegistry.get_app_instance(app_path) returns the live instance.
- hud.tscn no longer direct-instances AtlasApp or EconomicsApp — an
  AppsContainer Control holds the registry-managed children.
- hud.gd._ready() calls ImplantRegistry.instantiate_all($AppsContainer).
- main.gd drops @onready vars for atlas_app/economics_app; looks up
  both from the registry at the top of _ready().

#6c — schema_version on manifest
- ImplantAppManifest: @export var schema_version: int = 1 (first field).
- ImplantRegistry: const CURRENT_SCHEMA_VERSION := 1; tiered check in
  _scan() — older-than-current emits print_verbose, newer-than-current
  emits push_warning, both proceed best-effort.
- apps/atlas/app.tres + apps/economics/app.tres: schema_version = 1.

#10 — absorb _on_screen_changed boilerplate into ImplantApp
- Base class gains _screens: Dictionary, _current_screen_id: String,
  register_screen(id, screen), current_screen_id(), and a real default
  _on_screen_changed implementation that handles leave+hide+enter+show
  with has_method guards and same-screen-replace detection.
- atlas_app: deletes _current_screen_id, _get_screen(),
  _on_screen_changed() override; on_install() collapses to construct →
  setup → wire → register_screen(id, screen) per screen. Preserves
  direct screen refs for atlas-specific signal wiring and method calls.
- economics_app: deletes same scaffolding; on_install() reduces to three
  lines (construct overview_screen, register_screen, nav.set_default).

Also: replaced Resource.get(name, default) dict-style calls with direct
property access on typed ImplantAppManifest reads (2-arg get() is
Dictionary-only; causes "Too many arguments" parse errors on Resources).
_is_valid_manifest gained a Resource type guard and a property-exists
check.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-19 15:11:12 +02:00
jpmschweitzerandClaude Opus 4.6 ea7bcfabfb fix(ui): PR #131 review round 3 — nits sweep
Seven nit-level fixes from PR review:

- Remove dead signal insert_deactivated from ImplantApp; the hook method
  on_insert_deactivated() is the actual contract.
- Rewrite _on_atlas_economics_link comment in main.gd to reflect the
  actual flow (AtlasApp closes as a consequence of HudGroups single-
  active-app, not before emitting anything).
- Document the "pop never empties below default" invariant on
  ImplantNavStack.pop() with a pointer to reset_to_default.
- Add _mutating re-entrancy guard on ImplantNavStack mutation methods.
  push_error + early return if called during a screen_changed emission.
- main.gd registry loop now uses typed ImplantAppManifest property
  access (manifest.app_path, manifest.default_key) instead of
  dictionary-style .get() calls. Empty app_path triggers push_warning.
- Document the economics [/] hotkey exception in main.gd and reference
  the planned handle_global_key lifecycle hook. Arch doc Follow-up
  section gains a bullet for the new hook.
- Comment the independent-version-read rationale above client_ver and
  proto_ver in loading_screen.gd.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-19 14:47:05 +02:00
jpmschweitzerandClaude Opus 4.6 48c8fb7dea fix(ui): PR #131 review round 2 — manifest cleanup, system index helper, stderr help
Addresses 6 mechanical issues from PR review:

- ImplantAppManifest: drop dead display_name and icon_path fields.
  default_key carries a TODO noting its future migration to a keybinds
  manifest (input concern in app manifest is a layering violation,
  tracked explicitly).
- default_mode wire format is now a String ("fullscreen" / "insert") for
  mod-author discovery. ImplantRegistry parses via _MODE_MAP, caches the
  resolved int in _resolved_modes, and exposes get_resolved_mode(app_path).
  main.gd reads the resolved int directly instead of re-parsing.
- Extract client/ui/implant/widgets/system_index.gd (class_name SystemIndex,
  static get_sorted_systems). Removes duplicated star_map_data.json loader +
  sort lambda from atlas_app and economics overview_screen.
- ImplantApp.on_insert_deactivated() default auto-closes only when the app
  is active in INSERT mode. FULLSCREEN apps no longer spuriously close on
  insert state changes.
- tooling/db/sqlite-query and sqlite-exec: --help output goes to stderr
  (exit 0). Keeps stdout reserved for JSON payloads so JSON-parsing
  callers can't get silently corrupted.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-19 14:21:03 +02:00
jpmschweitzerandClaude Sonnet 4.6 df29d26d1e feat(ui): introduce ImplantApp pattern — base class, registry, atlas + economics refactor (#844, #824, #836)
Establishes the ImplantApp faux-mobile-OS architecture (D-191):

Foundation:
- ImplantAppManifest (Resource): @export vars for app_path, scene_path, default_key, default_mode, preserves_state
- ImplantNavStack (Node): push/pop/replace/reset_to_default, synchronous screen_changed signal
- ImplantApp (Control base class): absorbs HudGroups boilerplate; on_install/on_open/on_close/on_insert_deactivated lifecycle hooks
- ImplantRegistry (autoload): lazy-scans res://ui/implant/apps/*/app.tres; avoids autoload parse-order trap

Atlas app (replaces atlas_panel + atlas_reach_map + atlas_system_map + atlas_planet_map + root viewer files):
- apps/atlas/app.tres — manifest (implant/map, FULLSCREEN, key=M)
- apps/atlas/atlas_app.gd — coordinator; 4-screen nav via ImplantNavStack
- apps/atlas/screens/{reach,system,planet,regional}_screen.gd — enter/leave interface
- apps/atlas/{atlas_viewer,atlas_marker_overlay,atlas_overlay_bar}.gd — moved from root

Economics app (replaces economics_panel):
- apps/economics/app.tres — manifest (implant/economics, INSERT, key=N)
- apps/economics/economics_app.gd — thin shell delegating to OverviewScreen
- apps/economics/screens/overview_screen.gd — full panel logic, enter/leave interface

Wiring:
- project.godot: add ImplantRegistry autoload after HudGroups
- main.gd: registry-driven key toggle loop; rename atlas_panel→atlas_app, economics_panel→economics_app
- hud.tscn: swap to new scene paths; remove legacy StarMap node
- snapshot_consumers.gd: on_insert_deactivated() uniformly; rename vars
- hud_groups.gd: remove stale starchart compat comment

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 13:16:56 +02:00
jpmschweitzerandClaude Opus 4.6 a06646d810 feat(ui): unify star map and atlas into single implant/map chain (#844)
Per D-191, the atlas is the star map extended downward — not a separate
app. AtlasPanel now owns the full Reach → system → planet → heightmap
zoom hierarchy as a single HudGroups app (implant/map).

- Add REACH_MAP as Level 0 of AtlasPanel's zoom hierarchy; renumber the
  enum so higher index = deeper zoom
- Port hop-ring rendering (pan/zoom, system markers, hover/info, sector
  layout) from star_map.gd into AtlasPanel methods
- Change AtlasPanel.APP_PATH from "implant/map/atlas" to "implant/map"
- main.gd: KEY_M toggles unified atlas; KEY_A binding removed
- Symmetric nav: ORBITAL_DIAGRAM back goes to REACH_MAP (not
  SYSTEM_PICKER), matching the forward skip
- hud_groups.gd docstring documents the unified path and flags the
  legacy starchart path as kept-for-compat (retirement tracked in #852)

star_map.gd's HudGroups registration stays live but inert — no key
binding reaches it. Full retirement follows in #852 after a sprint of
soak on the unified panel.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-19 11:52:15 +02:00
jpmschweitzer f8b81e1fe7 refactor(ui): atlas review warnings — headers, public API, KEY_N, overlay source of truth (#128)
4. AtlasPanel and AtlasViewer now compose their title + hint from an
   ImplantHeader child rather than hand-rolling them via draw_string, so the
   D-169 "theme swap changes the implant hardware appearance" invariant
   holds end-to-end. _refresh_screen_header() drives content per level and
   on system navigation.

5. AtlasViewer exposes city_canvas_pos(), get_hovered_city(),
   get_selected_city(), and get_overlay_defs() as public API — the marker
   overlay no longer reaches into underscore-prefixed state, which is
   especially important because viewer is an untyped var in the overlay.

6. KEY_N now consumes unconditionally while the viewer is visible, and
   main.gd's global economics-monitor toggle is gated on
   !HudGroups.is_app_active("implant/map/atlas"). Previously pressing N
   without a selected city fell through and closed the fullscreen atlas as
   a side effect.

7. OVERLAY_DEFS lives in AtlasViewer as the single source of truth.
   AtlasOverlayBar reads the list via viewer.get_overlay_defs(), and
   AtlasViewer derives _overlay_visibility / _overlay_locked from the same
   table at _ready() — no more hand-maintained parallel lists, so the bar
   and the guard in set_overlay_visible can't drift.

8. AtlasOverlayBar drops `class_name`: it now loads via
   load("res://ui/implant/atlas_overlay_bar.gd") from AtlasViewer, the same
   pattern AtlasPanel uses for AtlasViewer. _init(viewer_ref = null) keeps
   the required-arg footgun off the editor's introspection path.

9. `star-map-data` make target added to regenerate
   client/data/star_map_data.json from systems.db + wiki, and
   `check-star-map` wired into pre-pr-validate + pre-pr-client so any
   commit that touches the generator (or any downstream systems.db change
   like server #839) fails pre-pr until the JSON is regenerated. The
   terrain_reference data-availability dependency is no longer tribal
   knowledge.

Also addresses review #15 (push_warning on unknown overlay id in
set_overlay_visible) and #16 (disabled always-on buttons drop handler
churn) as part of the same refactor.
2026-04-15 08:59:21 +02:00
jpmschweitzer c33ac8921f feat(ui): atlas heightmap viewer — pan/zoom, marker overlay, city data panel (#835)
Adds Level.HEIGHTMAP_VIEWER to AtlasPanel. The viewer loads a body's
terrain_reference heightmap PNG, pairs it with markers.json (roads, rail, POIs,
cities, rivers/oceans/mountains), and renders markers in texture-space via an
AtlasMarkerOverlay Node2D child of a transformed canvas — pan = offset,
zoom = scale.

Pan/zoom is cursor-centred (wheel zooms under the mouse, drag pans), with a
fit-to-view reset on R. Empty markers.json state renders a bare heightmap;
missing terrain_reference shows a themed "terrain data pending (#839)" notice
instead of crashing.

City data sidebar rebuilds from the selected city: name, pop tier, function,
currency zone, Commission presence, shadow zone, gate distance. Pressing N on
a selected city emits economics_link_requested(system_id) — main.gd bridges
this to EconomicsPanel.select_system() + HudGroups.open_app("implant/economics")
as an insert overlay, satisfying the D-191 Phase 2/3 cross-panel integration.

The overlay renders all nine D-191 overlay layers off of per-overlay visibility
flags in AtlasViewer. Overlay toggling for the regional view (#836) plugs into
set_overlay_visible(); the five always-on layers (terrain, infrastructure,
named features, gate markers, political zones) draw by default, the four
toggleable layers draw from placeholder data, and the two locked layers
(stockpile_weeks, production_vs_baseline) remain off until unlocked.

Per D-191 criteria 1, 4, 5.
2026-04-15 08:22:01 +02:00
jpmschweitzer 760953aeba feat(ui): atlas implant panel — system picker, orbital diagram, body nav (#834)
3-level atlas navigation as an implant/map/atlas FULLSCREEN app (D-170 z=20):
system picker (◄ ► cycle, Enter opens orbital) → orbital diagram (star centre,
bodies grouped by orbit_index with moons sub-orbiting parents, stations as
markers, click-to-open body or mini station panel) → body entry (data sheet,
Enter stub for #835 heightmap viewer, Esc back). Composed from the ImplantPanel
component library (D-169).

Wires the panel into hud.tscn, adds an A-key toggle in main.gd, propagates
insert_state via snapshot_consumers. Extends generate-star-map-data.py to emit
per-system orbit_bodies + stations arrays (with currency_zone, atmosphere,
population, terrain_reference, etc.) from systems.db; star_map_data.json
regenerated deterministically.

Per D-191 §6, Phase 3.
2026-04-14 17:24:50 +02:00
jpmschweitzerandClaude Opus 4.6 cf21904a0f fix(ui): show system cursor during fullscreen implant apps
CursorRenderer now toggles Input.MOUSE_MODE_VISIBLE when gameplay is
occluded, restores MOUSE_MODE_HIDDEN when gameplay resumes. Without
this, fullscreen apps (star map, future atlas) had no cursor at all —
the custom diegetic cursor hid correctly but the system cursor was
never restored.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 23:56:56 +02:00
jpmschweitzerandClaude Opus 4.6 b5dff8c55e fix(ui): gameplay_occluded signal never fired on first fullscreen toggle
was_occluded was computed AFTER _active_mode and _active_app were
updated to the new values, so it always matched now_occluded on the
first toggle (both TRUE). The signal condition (was != now) never
triggered. Moved the check before the state mutation.

This bug affected every GameplayRenderer (world, entities, fog,
cursor) and the stance indicator — none of them hid on first
fullscreen app open.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 23:53:54 +02:00
jpmschweitzerandClaude Opus 4.6 e91bd1c7e4 fix(client): post-sprint polish — protocol v21, schedule workaround, UI fixes
- Bump client protocol version 20 → 21 to match server (#822)
- Fix render_priority parameter name (was _render_priority, unused prefix)
- Fix debug console type inference (var sub := → var sub: String =)
- Economics panel: add population row, improve key hint text
- Stance indicator: hide on gameplay_occluded (D-170 fullscreen apps)
- Remove 5 broken clothing items from manifest and delete their GLBs
  (boots_work, coveralls_basic, jacket_utility, pants_cargo, shirt_henley)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 23:00:40 +02:00
jpmschweitzerandClaude Opus 4.6 4d4dc2014d fix(ui): rebind economics panel toggle from E to N (#124 review R2)
E is claimed by InputMap "interact" action — InputMapper consumes
it before _unhandled_key_input. N is free, adjacent to M (star map),
reads as "Numbers" for the economics monitor.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 14:25:07 +02:00
jpmschweitzerandClaude Opus 4.6 28d95c23a8 fix(ui): address PR #124 review — navigation, error handling, cleanup
- Economics panel: replace dead _gui_input LEFT/RIGHT with public
  navigate() method, wire [ ] keys in main.gd (avoids movement
  key conflict, fixes focus_mode=NONE issue)
- Debug console: add explicit effect guard in econ inject no-commodity
  branch so invalid effects don't fall to commodity-form error
- Snapshot consumer: null-clear GameState.economy_snapshot after
  consuming (matches one-shot consumer invariant)
- Star map: remove duplicate doc comment above set_insert_active()
- Generate script: remove stale comment, dead _WORKTREE_PARENT var,
  dead field extraction in parse_wiki_index, add try/except around
  DB queries

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 14:03:49 +02:00
jpmschweitzerandClaude Opus 4.6 949d721eac feat(ui): economics monitor insert panel with placeholder data (#824)
New implant panel at implant/economics: system selector, 6-commodity
price table with trend indicators, GDP strip. Composed from D-169
component library. Ring buffer caches last 20 ticks per system.
Snapshot routing wired through snapshot_handler → GameState →
snapshot_consumers → economics_panel. Placeholder prices shown
until server ships EconomySnapshot (#822).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 13:22:20 +02:00
jpmschweitzerandClaude Opus 4.6 283518d332 refactor(ui): convert HUD status + interaction prompt to implant (#786, #788)
HUD: merge TimeDisplay into ImplantPanel (time/health/perception in one
panel), remove dead tscn nodes, move HUD to InsertOverlay for D-051 bloom.
Interaction prompt: convert from PanelContainer+StyleBoxFlat to Control
with ImplantPanel+ImplantDataRow, fix panel height for 42px minimum.
Tests updated for new scene structure and public API.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 12:51:58 +02:00
jpmschweitzerandClaude Opus 4.6 34a21d4cd0 feat(rendering): GameplayRenderer base class — occlusion for all renderers (D-170)
New GameplayRenderer extends Node2D with built-in occlusion support.
Subclasses override _gameplay_process() and _gameplay_draw() instead
of _process() and _draw() — these are skipped when a fullscreen
implant app covers gameplay.

Migrated all 5 gameplay renderers:
- CursorRenderer: hover detection + bracket drawing paused
- EntityRenderer: position lerping paused
- FogEntities: fog dot rendering paused
- SoundIndicatorRenderer: directional arrows paused
- WorldRenderer: tile/fog/entity updates paused

No more manual occlusion wiring — the base class handles everything.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 11:51:30 +02:00
jpmschweitzerandClaude Opus 4.6 a01f7c9cfb fix(ui): cursor z-index 100, adjacent wraps, panel clamp with reset_size
- CursorRenderer z_index=100 so it renders above all HUD/implant layers
- Adjacent systems uses ImplantTextBlock (wraps) instead of ImplantDataRow (clips)
- Panel calls reset_size() before clamping to get accurate height on first frame

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 11:34:34 +02:00
jpmschweitzerandClaude Opus 4.6 0d46180e39 fix(ui): remove cursor_renderer from gameplay group
CursorRenderer must always render on top — shouldn't have its z-index
managed by HudGroups. It's a world-space renderer, not a HUD element.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 11:30:35 +02:00
jpmschweitzerandClaude Opus 4.6 e4fafb66d5 style(client): gdformat main.gd
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 11:26:01 +02:00
jpmschweitzerandClaude Opus 4.6 ffef2b3d54 fix(ui): gdlint ordering — signals before enums, signal uses int for Mode
Moved signals above enum per gdlint class-definitions-order rule.
app_changed signal uses int instead of Mode type to avoid the
forward-reference that forced wrong ordering. Callers compare
against HudGroups.Mode.FULLSCREEN etc unchanged.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 11:25:22 +02:00
jpmschweitzerandClaude Opus 4.6 2e01081de7 refactor(ui): HudGroups z-index model + gameplay occlusion (D-170)
Replaced visibility toggling with z-index layer management. Nothing
gets hidden — fullscreen apps render on top, gameplay stays underneath.
Supports future hybrid layouts (insert-mode map alongside gameplay).

Added gameplay_occluded signal: WorldRenderer skips tile/fog/entity
updates when a fullscreen implant app covers it. Prevents wasted
render work behind opaque overlays. Other renderers can connect to
the same signal.

Modes: GAMEPLAY (z=0), INSERT (z=10), FULLSCREEN (z=20), MODAL (z=30).
Star map uses app_changed signal to toggle its own visibility based
on whether its app is active.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 11:21:31 +02:00
jpmschweitzerandClaude Opus 4.6 09e0c522bd fix(client): split PowerProfile enum to fix last gdlint warning
gdformat collapsed enum + 3 inline comments into one 300-char line.
Split to multi-line enum with doc comments.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 11:10:13 +02:00
jpmschweitzerandClaude Opus 4.6 88c7407cb6 style(client): gdformat all 52 GDScript files — zero format warnings
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 11:09:10 +02:00
jpmschweitzerandClaude Opus 4.6 572e66f026 fix(ui): remove Node2D cursor_renderer from Control-typed HudGroups array
CursorRenderer extends Node2D, not Control — can't be registered in
HudGroups. Removed from gameplay group. Also dropped typed array
annotation to avoid runtime type mismatch errors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 11:07:34 +02:00
jpmschweitzerandClaude Opus 4.6 5ad2452213 feat(ui): implant app hierarchy in HudGroups (D-170)
Implant apps use hierarchical groups: implant/map, implant/wiki,
implant/journal, etc. Apps are mutually exclusive with each other
and with gameplay. Opening implant/map hides gameplay + any other
implant app. Closing returns to gameplay.

API: open_app(), close_app(), toggle_app(), is_app_active().
Star map uses implant/map. Future apps register their own group.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 11:04:19 +02:00
jpmschweitzerandClaude Opus 4.6 f037bcaa81 feat(ui): HUD visibility groups — gameplay vs implant layers (D-170)
New HudGroups autoload manages show/hide of related HUD elements as
groups. Exclusive groups (gameplay, implant) are mutually exclusive —
opening the star map hides stance indicator, minimap, interaction
prompts, etc. Closing it restores them.

Gameplay nodes registered in main.gd _ready(). Star map registers as
implant group and uses toggle_group() instead of direct visibility.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 11:01:28 +02:00
jpmschweitzerandClaude Opus 4.6 4eb54a6f7a fix(client): resolve all gdlint warnings — zero warnings policy
Fix 354 gdlint warnings across 65 files: 194 class-definitions-order
(reorder declarations), 138 max-line-length (split long lines),
22 code issues (unused args, no-else-return, naming). Update .gdlintrc
to exclude addons/ and raise max-public-methods for test files.
No logic changes — declaration order, whitespace, and naming only.

Ticket: #783

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 09:59:56 +02:00
jpmschweitzerandClaude Opus 4.6 260eefcdd1 refactor(client): decompose main.gd and game_state.gd god objects
Extract SnapshotHandler (static snapshot parsing), SnapshotConsumers
(non-dialogue consumers + audio handlers), and DialogueCoordinator
(dialogue consumers + signal handlers) as class_name scripts.

main.gd: 28KB → 13KB. game_state.gd: 20KB → 8.5KB.
Autoload parse-order safety maintained via load() inline pattern.

Ticket: #775

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 09:59:34 +02:00
jpmschweitzerandClaude Opus 4.6 469114cb0e fix(ui): wire M keybind for star map toggle, clean up info panel
- Add _unhandled_key_input to main.gd: M toggles star_map.toggle_visible()
- Remove topology line from info panel (internal data, not player-facing)
- Cast aperture_count to int (was showing decimal point)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 08:33:07 +02:00
jpmschweitzerandClaude Opus 4.6 c94c5d7acb fix(client): address PR #109 review — 6 warnings + 5 suggestions
Star map (W1-W3, S3):
- _process visibility guard + dirty flag (no redraw when hidden/unchanged)
- _system_hash masked to 31-bit positive range
- Extracted _find_nearest_system() shared helper

game_state.gd (W4):
- Inline load() in apply_snapshot() replaces per-tick overhead; safe at
  runtime because script is already in resource cache

Data pipeline (W5-W6):
- Script-relative path resolution via __file__
- --check mode + make check-star-map staleness target

Minor (S1-S2, S5):
- Removed redundant bone_idx assignment
- Simplified double-negative test assertion
- Documented autoload parse-order convention in CLAUDE.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-05 00:12:48 +02:00
jpmschweitzerandClaude Opus 4.6 0d5323f66c fix(client): resolve GDScript autoload parse-order errors
Autoload scripts are parsed before regular scripts, so class_name
types (CharacterVisualDescriptor, TestHarness, YamlParser) are not
available at parse time. Replace type annotations with untyped vars
and use load() for in-body class references. Fixes all 14 headless
parse errors (9 pre-existing + 5 from Sprint 30 changes).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 23:40:28 +02:00
jpmschweitzerandClaude Opus 4.6 aed513eb71 feat(ui): star map insert module — concentric hop-ring view (#674)
New StarMapRenderer: 301 systems in concentric hop-rings from player
location, sector-colored, click-to-select with info panel, pan/zoom.
Integrated into HUD (hidden by default), insert state propagation
wired in main.gd. Data enriched from systems.db + star-map.json via
tooling/generate-star-map-data.py regeneration script.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 23:34:36 +02:00
jpmschweitzerandClaude Opus 4.6 4c1e0e9c85 feat(client): wire CharacterVisualDescriptor into startup IPC (#718)
encode_startup_message() now accepts optional CharacterVisualDescriptor
as third param, serialized via to_dict(). sim_bridge passes the
descriptor from GameState on new game start. apply_snapshot() restores
descriptor from server snapshot on save/load cycle.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 23:34:11 +02:00
jpmschweitzerandClaude Opus 4.6 e8a053080c feat(client): add BoneAttachment3D overhead anchor above Head bone (#712)
Infrastructure for floating UI elements (status indicators, thought
bubbles, alert markers). Marker3D at Vector3(0, 0.3, 0) offset from
Head bone, exposed via get_overhead_anchor() public API.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 23:34:00 +02:00
jpmschweitzerandClaude Opus 4.6 4d9840b68f feat(client): character creator overhaul — manifest, segments, eye color
Major changes from runtime testing and iteration:
- Asset manifest (manifest.json) controls all available content —
  replaces filesystem scanning, solves DirAccess/PCK export issue
- Dynamic tabs: only show tabs with manifest content
- Body segmentation: face-based exclusive assignment, hips split from
  torso, torso_upper independent (not a variant)
- Eye color with iris-only mask from T_Eye_Split.png green channel
- Eyebrows tinted with hair color (from body segment, not separate GLB)
- Hair/clothing loaded as skinned meshes on shared skeleton
- Segment hiding disabled for clothing (solidify handles coverage)
- Gender-aware randomizer (no facial hair on female/teen)
- Clothing slots hidden when empty in manifest
- UI: color docks docked to bottom on all tabs, padding, flow layout
- Debug tab with per-segment visibility toggles
- Screenshot automation with test config JSON

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 17:42:33 +01:00
jpmschweitzerandClaude Opus 4.6 18ee89d066 feat(client): add animation support to character compositor
Load UAL animation library onto the skeleton and play idle animation
after character assembly. Adds play_animation() and stop_animation()
public API. Searches all animation libraries for matching animation
name with common idle variant fallbacks.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 18:33:36 +01:00
jpmschweitzerandClaude Opus 4.6 86acb87771 fix(client): explicit Variant type on JSON.parse_string return
GDScript strict mode rejects type inference from Variant-returning
functions. JSON.parse_string() returns Variant — use explicit type
annotation to satisfy the parser.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 18:28:35 +01:00
jpmschweitzerandClaude Opus 4.6 ebac8c9580 fix(client): address PR #99 review round 2 — 3 doc/comment fixes
1. character_creation.gd:125 — cam_pitch_idx comment: 2=overhead(0°) → (-80°)
2. character_visual.gd:37 — segment arithmetic: 17+2+2=21 → 14+2+2=18
3. decisions/scope.md D-158 — overhead angle: 0° → -80° to match code

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 17:54:53 +01:00