main
15
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
086d9ed56e |
fix(client): bake the version into the build, so an export can invalidate its cache (T-1241)
current_schema_version() line-scanned res://../project.yaml at runtime. That resolves to the repo root in a dev run and to nothing in an exported build, so a shipped game got the "?.?.?" fallback every time. Since that tag is the Atlas disk cache's ONLY invalidation signal, every exported build stamped and compared the same sentinel: a canvas cached by one build would be served by every later build, forever. T-1239 is what that failure looks like once it happens. loading_screen.gd carried a byte-for-byte copy of the same function, so the version shown to the player was "?.?.?" in exactly the builds where a version string is worth showing. Both call sites now share client/scripts/build_version.gd, which reads application/config/version out of ProjectSettings — a value Godot bakes into the PCK, identical in the editor and in an export by construction rather than by luck. No file IO, no fallback branch. project.yaml stays the source of truth (CLAUDE.md); client/project.godot mirrors it. A mirror nobody checks would be worse than the bug it replaces -- the old code failed loudly everywhere, a stale mirror fails silently -- so tooling/check-client-version compares the two and the pre-push hook runs it unconditionally. Not gated on "were those files in this push": drift persists on main once introduced, and gating would let an existing drift ride along. The test this replaces asserted that current_schema_version() did not return its fallback, and passed -- in the one environment where the code under test worked. Three tests now pin the property that actually matters: a real version, sourced from the baked setting, matching project.yaml. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
c765efd54e |
feat(assets): wardrobe wave 1 — per-body shells, 8 garments, try-on UI (T-1089)
Infra: offset-shell gains --per-body mode (each body's own segments, cut/mask thresholds derived from that body's bone landmarks — reproduces the hand-calibrated reference constants exactly on average_m); compositor prefers <body>_mask.png with reference_mask.png fallback; tshirt re-authored per-body on all 11 (the Q-060 torso poke-through class is GONE — residual flags are a sleeve-hem epsilon artifact on thick arms, offset-insensitive, documented). Garments (all per-body x 11, chromakey-gated <=150px worst, previewed): hoodie (hood-down roll, kangaroo pocket, logo), button-down (collar/placket), shorts, jeans (analytic denim field driving albedo+mask together; boundary weld + open-rim flattening — real segment-splitter findings), formal pants, jacket (over-shirt standoff, zip), suit_jacket_black (lapel region, tintable shirt triangle — the hand-author proof), uniform_utility (11-segment coverall, gap-free waist join by construction, 4-zone showcase, logo patch). Try-on UI: creation screen shows per-region tint pickers (multi_region garments) + logo picker (logo_capable + logos/*.png scan), data-driven off manifest+coverage. Manifest merged by the lead: 12 clothing entries. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
698c5eea9b |
fix(client): parse-order fixes for Protocol autoload and MetaScreen extends
Protocol.gd is an autoload — replace Messagepack class_name refs with inline load() calls via a static helper. main_menu.gd extends MetaScreen by class_name which fails at parse time; switch to path-based extends. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
bc310a3ca5 |
fix(client): sprint 37 test-pass fixes — parse error + test hygiene
- character_creation.gd:1614: type-annotate dir_name to fix GDScript inference parse error. CARDINAL_DIRS is an untyped const Array, so `var dir_name := CARDINAL_DIRS[idx]` failed type inference and blocked test_character_creation_sprint28.gd test discovery. Runtime was lenient but gdUnit4's scanner is strict. - test_merge_path_flows_sprint37.gd (_load_char_create): call add_child before _update_start_btn_state so @onready vars (_footer_start) are bound when the helper dereferences them. Unblocks 2 merge-path tests. - test_anti_tedium.gd (test_bug_report_sends_unpause_on_close): call dialog.close() instead of the old dialog._close() rename casualty. `on_close()` is a lifecycle hook — it doesn't actually transition state; `close()` is what MetaScreen exposes. - test_anti_tedium.gd (before_test): clear MetaStack._stack. Prior tests leave stale freed dialog refs on the stack; _any_pausing() iterates the stack during close() and crashes with "previously freed". Net: sprint-37 test files (test_merge_path_flows_sprint37.gd, test_anti_tedium.gd) now pass. Overall suite: 2428/2488 passing (60 remaining failures are pre-existing, unrelated to sprint 37). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
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. |
||
|
|
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.
|
||
|
|
9d09ee548e |
fix(ui): loading screen blocks input + main menu polls during catalog wait
- loading_screen: opaque BG (was 0.75 alpha) + mouse_filter STOP so the loading state genuinely occludes the underlying screen - main_menu: poll SimBridge.poll_snapshot in _process while waiting for the bookmark catalog so the new-game flow doesn't stall on the catalog round-trip introduced in Workstream 3 (#680) |
||
|
|
682bd821e3 |
feat(ui): Skills tab stub content (Workstream 8, #618)
Replaces the W5 placeholder inside the Skills tab with a properly framed stub per Araminta's spec. Final workstream of the #618 + #680 + MetaScreen implementation. Content: - MarginContainer (8 px sides, 4 px top — matches existing tab padding) - Single centered Label: "Skills allocation — coming soon." - DIM_TEXT color, font_body size (11 px), horizontally and vertically centered inside the tab content area No inputs, no interactivity — real skill allocation lands in a future sprint when the skills system exists server-side. Players selecting a bookmark still proceed to Start regardless of what they see on this tab. Closes the implementation half of #618 (CK3-style character creation screen) and #680 (location picker) — Hoshe's revised test plans for MetaScreen pattern, #618, and #680 can now run end-to-end. Verification: - gdlint clean - Headless parse + widened scanner check (per hardened pr-push skill) clean; pre-existing autoload class_name noise filtered. - test_character_creation_sprint28 88/88, test_protocol 62/62, test_client_p3 24/24, test_ui_framework_sprint15 54/54 Next: Hoshe runs her full revised test gauntlet against the shipped shape; if green, the PR pushes via /pr-push. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
fb7cd357b8 |
feat(ui): location picker in Bookmark tab (Workstream 7, #680)
Adds the starting-location picker as a sub-component of the Bookmark tab detail view, per Araminta's spec and D-128's "culture implicit in location" constraint. Fills the space W6 reserved below the CAREER / CAPITAL data rows. Picker structure: - "STARTING LOCATION" section label (DIM_TEXT, 10px, all-caps). - ScrollContainer min_height=80 → VBoxContainer of selectable items. - Each item: Button with child VBox carrying the location name Label (PRIMARY_TEXT, 12px) and an optional culture tag Label (DIM_TEXT, 10px, mouse_filter IGNORE per D-128). Culture label is NOT rendered when `allowed_locations_cultures[i]` is empty — no "Unknown" placeholder, the row just shrinks. Behavior: - Clicking a bookmark card auto-selects its default_location in the picker (handled via _selected_location_id + _update_location_selection). - Clicking a location item updates _selected_location_id and re-gates the Start button (already checked in W6). - Switching bookmarks rebuilds the picker list for the new allowed_locations; prior selection cleared. - Parallel-array length mismatch defended: reads `cultures[i] if i < cultures.size() else ""` so a short cultures array won't crash rendering. D-128 compliance: - No culture dropdown or filter anywhere. - Culture tag Label is strictly display: MOUSE_FILTER_IGNORE, no signal handlers. - CharacterProfile carries only start_location_id; no culture_id. Other: removes W6's placeholder "Starting Location: X" ImplantDataRow since the picker supersedes it; separator before the picker preserved. Verification: - gdlint clean - Headless parse + scanner check (widened per hardened pr-push skill): no new errors. Pre-existing autoload class_name noise filtered per CLAUDE.md. - test_character_creation_sprint28 88/88, test_protocol 62/62, test_implant_* all green, test_client_p3 24/24, test_ui_framework_sprint15 54/54. Workstream 8 (Skills tab stub content) lands next — #618 closes then. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
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>
|
||
|
|
256703e9b7 |
feat(ui): 3-tab restructure of character creation (Workstream 5)
Consolidates the character creation TabContainer from 8 flat tabs
(Body / Head / Hair / Clothing / Accessories / Debug plus the two
being-added Skills / Bookmark) into 4 top-level tabs per Araminta's
revised spec: Bookmark, Appearance, Skills, Debug.
The existing five appearance sub-tabs (Body, Head, Hair, Clothing,
Accessories) now live inside the Appearance tab as a horizontal
segmented sub-navigation using the existing `_make_slot_btn()` pattern
— consistent with the Clothing/Accessories slot row vocabulary.
Selected sub-section uses existing ITEM_SELECTED_BG / BORDER styling.
Structural changes:
- New APPEARANCE_SUB_NAMES const lists the five sub-sections.
- Renamed _tab_search → _appearance_search, _tab_grids →
_appearance_grids. Scope changed from "top-level tabs" to
"Appearance sub-sections" but index 0..4 semantics preserved.
- Added _appearance_active_idx, _appearance_sub_btns,
_appearance_sub_sections state.
- _ready() builds exactly 4 top-level tabs; tab builders invoked
explicitly per index.
- New _build_bookmark_tab / _build_skills_tab render TEXT_DIM
placeholder labels ("Bookmark content lands in Workstream 6", etc.)
— actual content in W6/W8.
- _build_appearance_tab constructs the sub-nav strip and stacks all
5 sub-sections up front with visibility-toggle swap
(_on_appearance_sub_selected). Comment explains the up-front build
choice and the free-and-rebuild fallback if performance regresses.
- Existing _build_body_tab / _head / _hair / _clothing / _accessories /
_debug remain unchanged — they now receive Appearance sub-section
Controls as their tab argument instead of top-level tabs. _make_tab_vbox
anchors full-rect in both parent contexts, so layout is preserved.
Verification:
- gdlint clean
- godot --headless --path client --quit — no SCRIPT ERROR
- test_protocol 62/62, test_client_p3 24/24, test_ui_framework_sprint15
54/54, test_implant_nav_stack 52/52, test_implant_registry 42/42,
test_implant_app_lifecycle 36/36
Workstream 6 (Bookmark tab content: card list + detail view + location
picker per Araminta's spec) lands next. W7 (location picker as a
sub-component of Bookmark tab) follows. W8 (Skills stub content) is
last. Hoshe's parallel Task #21 (test hygiene triage) commits
separately.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
||
|
|
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> |
||
|
|
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 (
|
||
|
|
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> |