From b999031e6afe5f39d97f332637ccc619606549fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Wisniewski?= Date: Fri, 21 Aug 2026 13:31:46 +0200 Subject: [PATCH] Recentre a released stick, slow extractors again, and unbreak the lab layout Both sticks float - the base jumps to wherever the finger lands, so a player does not have to hit an exact spot on a screen they cannot feel. Releasing put only the KNOB back onto the base and left the base parked wherever that touch had been, so an idle stick sat off-centre in a different place every time and read as a control still in use. Release now returns base and knob to the middle of the rect. Extractors produce at 0.5 ore per second, halved again: at 1.0 they still outran the rest of the economy. An extractor now pays back its 25-ore cost in fifty seconds rather than twelve. The ship lab and the crafter were both overlapping themselves. Enlarging the shared resource table to 136px (so it could breathe and show bigger type) pushed its bottom edge 34px into the vessel row beneath it in one and the status text in the other - my regression, visible immediately on a real screen and not at all in any test. The lab's second row of upgrade cards was also already running into the detail text below it, by 13px, before any of this. Both menus are re-spaced end to end rather than nudged, since moving one collision into another is how the second one got there. Two tests carried waits tuned to an extraction rate that has now changed twice. Both derive them from EXTRACTOR_RATE instead, so the next balance pass does not report a broken extractor that is working perfectly - the same fix test_extractor_income already needed one round ago, in the file next to it. Suite 112/112. Co-Authored-By: Claude Opus 5 --- GameState.gd | 15 +-- Main.tscn | 152 +++++++++++++-------------- TouchJoystick.gd | 17 ++- tests/_lab_menu_preview.gd | 29 ++--- tests/test_asteroid_mining.gd.uid | 1 + tests/test_black_hole.gd | 8 +- tests/test_joystick_recentres.gd | 99 +++++++++++++++++ tests/test_joystick_recentres.gd.uid | 1 + 8 files changed, 220 insertions(+), 102 deletions(-) create mode 100644 tests/test_asteroid_mining.gd.uid create mode 100644 tests/test_joystick_recentres.gd create mode 100644 tests/test_joystick_recentres.gd.uid diff --git a/GameState.gd b/GameState.gd index 235808c..d546073 100644 --- a/GameState.gd +++ b/GameState.gd @@ -1223,12 +1223,15 @@ func rotate_formation(radians: float) -> void: # hauler: ore becomes spendable only when a hub-stationed transporter picks # up a full load and physically delivers it to the mothership. Entries are # {"position", "planet_id", "planet_name", "resource_type", "inventory", "mining_progress"}. -# Ore per second. Halved alongside the planet-density change in WorldGen: with -# roughly 2.5x as many planets to put extractors on, holding the per-extractor -# rate would have multiplied passive income rather than shifting where it comes -# from. The intent is more sites each worth less, so expanding is what grows -# the economy rather than one lucky early extractor carrying it. -const EXTRACTOR_RATE: float = 1.0 +# Ore per second, per extractor. +# +# Halved once alongside the planet-density change in WorldGen (roughly 2.5x as +# many planets to build on, so holding the rate would have multiplied passive +# income rather than moving where it comes from), and halved again because it +# still outran the rest of the economy. At 0.5 an extractor pays back its own +# 25-ore cost in fifty seconds and yields 30 a minute after that, so a +# single lucky early site cannot carry a game and expanding is what grows one. +const EXTRACTOR_RATE: float = 0.5 var extractors: Array = [] diff --git a/Main.tscn b/Main.tscn index acd8fb2..43eba93 100644 --- a/Main.tscn +++ b/Main.tscn @@ -1211,9 +1211,9 @@ color = Color(0.03, 0.03, 0.08, 0.94) mouse_filter = 2 anchor_right = 1.0 offset_left = 24 -offset_top = 36 +offset_top = 20 offset_right = -200 -offset_bottom = 166 +offset_bottom = 82 text = "Ship Lab" horizontal_alignment = 1 @@ -1221,9 +1221,9 @@ horizontal_alignment = 1 mouse_filter = 2 anchor_right = 1.0 offset_left = 24 -offset_top = 172 +offset_top = 148 offset_right = -24 -offset_bottom = 308 +offset_bottom = 284 script = ExtResource("24") [node name="VesselA" type="Control" parent="UI/LabMenu"] @@ -1231,8 +1231,8 @@ anchor_left = 0.5 anchor_right = 0.5 offset_left = -300 offset_right = -240 -offset_top = 274 -offset_bottom = 394 +offset_top = 300 +offset_bottom = 420 script = ExtResource("10") icon = 2 glow_color = Color(0.25, 0.85, 1, 1) @@ -1242,8 +1242,8 @@ anchor_left = 0.5 anchor_right = 0.5 offset_left = -210 offset_right = -150 -offset_top = 274 -offset_bottom = 394 +offset_top = 300 +offset_bottom = 420 script = ExtResource("10") icon = 2 glow_color = Color(0.45, 0.8, 1, 1) @@ -1253,8 +1253,8 @@ anchor_left = 0.5 anchor_right = 0.5 offset_left = -120 offset_right = -60 -offset_top = 274 -offset_bottom = 394 +offset_top = 300 +offset_bottom = 420 script = ExtResource("10") icon = 31 glow_color = Color(0.96, 0.32, 0.78, 1) @@ -1264,8 +1264,8 @@ anchor_left = 0.5 anchor_right = 0.5 offset_left = -30 offset_right = 30 -offset_top = 274 -offset_bottom = 394 +offset_top = 300 +offset_bottom = 420 script = ExtResource("10") icon = 12 glow_color = Color(0.95, 0.55, 0.85, 1) @@ -1275,8 +1275,8 @@ anchor_left = 0.5 anchor_right = 0.5 offset_left = 60 offset_right = 120 -offset_top = 274 -offset_bottom = 394 +offset_top = 300 +offset_bottom = 420 script = ExtResource("10") icon = 34 glow_color = Color(1, 0.78, 0.3, 1) @@ -1286,8 +1286,8 @@ anchor_left = 0.5 anchor_right = 0.5 offset_left = 150 offset_right = 210 -offset_top = 274 -offset_bottom = 394 +offset_top = 300 +offset_bottom = 420 script = ExtResource("10") icon = 35 glow_color = Color(0.3, 0.9, 1, 1) @@ -1297,8 +1297,8 @@ anchor_left = 0.5 anchor_right = 0.5 offset_left = 240 offset_right = 300 -offset_top = 274 -offset_bottom = 394 +offset_top = 300 +offset_bottom = 420 script = ExtResource("10") icon = 37 glow_color = Color(0.7, 0.3, 1, 1) @@ -1308,9 +1308,9 @@ visible = false mouse_filter = 2 anchor_right = 1.0 offset_left = 24 -offset_top = 402 +offset_top = 424 offset_right = -24 -offset_bottom = 460 +offset_bottom = 482 theme_override_font_sizes/font_size = 34 horizontal_alignment = 1 text = "" @@ -1321,8 +1321,8 @@ anchor_left = 0.5 anchor_right = 0.5 offset_left = -310 offset_right = -230 -offset_top = 400 -offset_bottom = 472 +offset_top = 424 +offset_bottom = 496 theme_override_font_sizes/font_size = 15 autowrap_mode = 2 horizontal_alignment = 1 @@ -1334,8 +1334,8 @@ anchor_left = 0.5 anchor_right = 0.5 offset_left = -220 offset_right = -140 -offset_top = 400 -offset_bottom = 472 +offset_top = 424 +offset_bottom = 496 theme_override_font_sizes/font_size = 15 autowrap_mode = 2 horizontal_alignment = 1 @@ -1347,8 +1347,8 @@ anchor_left = 0.5 anchor_right = 0.5 offset_left = -130 offset_right = -50 -offset_top = 400 -offset_bottom = 472 +offset_top = 424 +offset_bottom = 496 theme_override_font_sizes/font_size = 15 autowrap_mode = 2 horizontal_alignment = 1 @@ -1360,8 +1360,8 @@ anchor_left = 0.5 anchor_right = 0.5 offset_left = -40 offset_right = 40 -offset_top = 400 -offset_bottom = 472 +offset_top = 424 +offset_bottom = 496 theme_override_font_sizes/font_size = 15 autowrap_mode = 2 horizontal_alignment = 1 @@ -1373,8 +1373,8 @@ anchor_left = 0.5 anchor_right = 0.5 offset_left = 50 offset_right = 130 -offset_top = 400 -offset_bottom = 472 +offset_top = 424 +offset_bottom = 496 theme_override_font_sizes/font_size = 15 autowrap_mode = 2 horizontal_alignment = 1 @@ -1386,8 +1386,8 @@ anchor_left = 0.5 anchor_right = 0.5 offset_left = 140 offset_right = 220 -offset_top = 400 -offset_bottom = 472 +offset_top = 424 +offset_bottom = 496 theme_override_font_sizes/font_size = 15 autowrap_mode = 2 horizontal_alignment = 1 @@ -1399,8 +1399,8 @@ anchor_left = 0.5 anchor_right = 0.5 offset_left = 230 offset_right = 310 -offset_top = 400 -offset_bottom = 472 +offset_top = 424 +offset_bottom = 496 theme_override_font_sizes/font_size = 15 autowrap_mode = 2 horizontal_alignment = 1 @@ -1410,9 +1410,9 @@ text = "Distorder" mouse_filter = 2 anchor_right = 1.0 offset_left = 48 -offset_top = 480 +offset_top = 502 offset_right = -48 -offset_bottom = 484 +offset_bottom = 506 color = Color(0.25, 0.85, 1, 0.6) [node name="UpgradeList" type="Label" parent="UI/LabMenu"] @@ -1421,9 +1421,9 @@ mouse_filter = 2 anchor_right = 1.0 anchor_bottom = 1.0 offset_left = 32 -offset_top = 488 +offset_top = 514 offset_right = -32 -offset_bottom = -470 +offset_bottom = -390 theme_override_font_sizes/font_size = 30 autowrap_mode = 2 text = "" @@ -1434,9 +1434,9 @@ anchor_right = 1.0 anchor_top = 1.0 anchor_bottom = 1.0 offset_left = 32 -offset_top = -460 +offset_top = -380 offset_right = -32 -offset_bottom = -330 +offset_bottom = -280 theme_override_font_sizes/font_size = 32 autowrap_mode = 2 horizontal_alignment = 1 @@ -1448,9 +1448,9 @@ anchor_right = 1.0 anchor_top = 1.0 anchor_bottom = 1.0 offset_left = 32 -offset_top = -324 +offset_top = -274 offset_right = -32 -offset_bottom = -256 +offset_bottom = -226 theme_override_font_sizes/font_size = 30 autowrap_mode = 2 horizontal_alignment = 1 @@ -1464,8 +1464,8 @@ anchor_right = 0.5 anchor_bottom = 1.0 offset_left = -230 offset_right = -110 -offset_top = -240 -offset_bottom = -120 +offset_top = -218 +offset_bottom = -108 script = ExtResource("10") icon = 6 glow_color = Color(0.55, 0.95, 0.9, 1) @@ -1477,8 +1477,8 @@ anchor_right = 0.5 anchor_bottom = 1.0 offset_left = 110 offset_right = 230 -offset_top = -240 -offset_bottom = -120 +offset_top = -218 +offset_bottom = -108 script = ExtResource("10") icon = 4 glow_color = Color(0.45, 0.9, 0.55, 1) @@ -1490,9 +1490,9 @@ anchor_right = 1.0 anchor_top = 1.0 anchor_bottom = 1.0 offset_left = 24 -offset_top = -110 +offset_top = -102 offset_right = -24 -offset_bottom = -20 +offset_bottom = -22 theme_override_font_sizes/font_size = 28 autowrap_mode = 2 horizontal_alignment = 1 @@ -1503,8 +1503,8 @@ anchor_left = 0.5 anchor_right = 0.5 offset_left = -240 offset_right = -120 -offset_top = 488 -offset_bottom = 598 +offset_top = 514 +offset_bottom = 624 script = ExtResource("10") icon = 13 glow_color = Color(0.25, 0.85, 1, 1) @@ -1515,8 +1515,8 @@ anchor_left = 0.5 anchor_right = 0.5 offset_left = -310 offset_right = -50 -offset_top = 603 -offset_bottom = 673 +offset_top = 628 +offset_bottom = 698 theme_override_font_sizes/font_size = 22 autowrap_mode = 2 horizontal_alignment = 1 @@ -1527,8 +1527,8 @@ anchor_left = 0.5 anchor_right = 0.5 offset_left = 120 offset_right = 240 -offset_top = 488 -offset_bottom = 598 +offset_top = 514 +offset_bottom = 624 script = ExtResource("10") icon = 13 glow_color = Color(0.25, 0.85, 1, 1) @@ -1539,8 +1539,8 @@ anchor_left = 0.5 anchor_right = 0.5 offset_left = 50 offset_right = 310 -offset_top = 603 -offset_bottom = 673 +offset_top = 628 +offset_bottom = 698 theme_override_font_sizes/font_size = 22 autowrap_mode = 2 horizontal_alignment = 1 @@ -1551,8 +1551,8 @@ anchor_left = 0.5 anchor_right = 0.5 offset_left = -240 offset_right = -120 -offset_top = 678 -offset_bottom = 783 +offset_top = 706 +offset_bottom = 816 script = ExtResource("10") icon = 13 glow_color = Color(0.25, 0.85, 1, 1) @@ -1563,8 +1563,8 @@ anchor_left = 0.5 anchor_right = 0.5 offset_left = -310 offset_right = -50 -offset_top = 788 -offset_bottom = 833 +offset_top = 820 +offset_bottom = 890 theme_override_font_sizes/font_size = 22 autowrap_mode = 2 horizontal_alignment = 1 @@ -1575,8 +1575,8 @@ anchor_left = 0.5 anchor_right = 0.5 offset_left = 120 offset_right = 240 -offset_top = 678 -offset_bottom = 783 +offset_top = 706 +offset_bottom = 816 script = ExtResource("10") icon = 13 glow_color = Color(0.25, 0.85, 1, 1) @@ -1587,8 +1587,8 @@ anchor_left = 0.5 anchor_right = 0.5 offset_left = 50 offset_right = 310 -offset_top = 788 -offset_bottom = 833 +offset_top = 820 +offset_bottom = 890 theme_override_font_sizes/font_size = 22 autowrap_mode = 2 horizontal_alignment = 1 @@ -1597,10 +1597,10 @@ text = "" [node name="CloseButton" type="Control" parent="UI/LabMenu"] anchor_left = 1.0 anchor_right = 1.0 -offset_left = -172 -offset_top = 24 +offset_left = -140 +offset_top = 20 offset_right = -24 -offset_bottom = 172 +offset_bottom = 136 script = ExtResource("10") icon = 1 glow_color = Color(0.55, 0.95, 0.9, 1) @@ -1619,27 +1619,27 @@ color = Color(0.03, 0.03, 0.08, 0.94) mouse_filter = 2 anchor_right = 1.0 offset_left = 24 -offset_top = 36 +offset_top = 20 offset_right = -200 -offset_bottom = 166 +offset_bottom = 82 text = "Build a ship" horizontal_alignment = 1 [node name="Bar" type="Control" parent="UI/CraftMenu"] mouse_filter = 2 offset_left = 24 -offset_top = 172 +offset_top = 148 offset_right = 520 -offset_bottom = 308 +offset_bottom = 284 script = ExtResource("24") [node name="StatusLabel" type="Label" parent="UI/CraftMenu"] mouse_filter = 2 anchor_right = 1.0 offset_left = 24 -offset_top = 274 +offset_top = 300 offset_right = -200 -offset_bottom = 420 +offset_bottom = 446 theme_override_font_sizes/font_size = 36 autowrap_mode = 2 text = "" @@ -1899,10 +1899,10 @@ glow_color = Color(0.25, 0.85, 1, 1) [node name="CloseButton" type="Control" parent="UI/CraftMenu"] anchor_left = 1.0 anchor_right = 1.0 -offset_left = -172 +offset_left = -140 offset_right = -24 -offset_top = 24 -offset_bottom = 172 +offset_top = 20 +offset_bottom = 136 script = ExtResource("10") icon = 1 diff --git a/TouchJoystick.gd b/TouchJoystick.gd index 4c1cf2a..5685f91 100644 --- a/TouchJoystick.gd +++ b/TouchJoystick.gd @@ -9,6 +9,9 @@ extends Control # finger out to max_radius, and `direction` reports the offset normalised # against that radius (so magnitude is 0..1, usable for throttle later). # +# Releasing puts BOTH the base and the knob back in the middle of the rect, so +# an idle stick always looks the same and always looks idle. +# # Only claims touches that start inside its own rect - anything else falls # through untouched, which is what keeps pinch-to-zoom working everywhere # else on screen. @@ -38,8 +41,15 @@ func _ready() -> void: _relayout() +# Where the stick sits when nobody is touching it: the middle of its own rect. +# The base only moves away from here while a finger is down (see handle_touch), +# and returns the moment it lifts. +func home_pos() -> Vector2: + return size / 2.0 + + func _relayout() -> void: - base_pos = size / 2.0 + base_pos = home_pos() knob_pos = base_pos max_radius = minf(size.x, size.y) * RADIUS_RATIO queue_redraw() @@ -67,6 +77,11 @@ func handle_touch(event: InputEventScreenTouch) -> bool: if event.index != active_touch_index: return false active_touch_index = -1 + # Back to the middle of the rect, BASE included. This used to reset the + # knob onto base_pos and leave the base wherever the finger had landed, so + # a released stick stayed parked off-centre - which reads as a control + # still in use, and left the ring somewhere different every time. + base_pos = home_pos() knob_pos = base_pos direction = Vector2.ZERO queue_redraw() diff --git a/tests/_lab_menu_preview.gd b/tests/_lab_menu_preview.gd index 80db449..636e240 100644 --- a/tests/_lab_menu_preview.gd +++ b/tests/_lab_menu_preview.gd @@ -1,29 +1,22 @@ extends Node2D -# Preview, not a test - #71's ship lab layout with all 7 vessels present, -# checking the vessel-name row no longer overlaps itself. +# Preview, not a test - the ship lab at the real 720x1280 viewport, with a +# vessel selected and its upgrades listed. # -# bash tests/screenshot.sh res://tests/_lab_menu_preview.tscn /tmp/lab.png 20 720x1280 - -var configured: bool = false +# bash tests/screenshot.sh res://tests/_lab_menu_preview.tscn /tmp/lab.png 25 720x1280 -func _process(_delta: float) -> void: - if configured: - return - configured = true +func _ready() -> void: GameState.buildings.clear() GameState.extractors.clear() - GameState.transporters.clear() - GameState.rocks = 500 - GameState.research_points = 5 + GameState.reset_progression() + GameState.bank_inventory.set_amount(GameState.RESOURCE_ORE, 310) + GameState.fleet_inventory.set_amount(GameState.RESOURCE_ORE, 47) + GameState.research_points = 4 var main: Node = load("res://Main.tscn").instantiate() add_child(main) main.get_node("EnemySpawner").set_process(false) main.get_node("AsteroidField").set_process(false) - main.get_node("BuildingField").place(GameState.BUILDING_SHIP_LAB, Vector2(400.0, 0.0)) - var lab_id: int = int(GameState.buildings[-1].id) - var bc: Node = main.get_node("BuildController") - bc.lab_building_id = lab_id - main.get_node("UI/LabMenu").visible = true - bc._refresh_lab() + var build: Node = main.get_node("BuildController") + build.lab_menu.visible = true + build._refresh_lab() diff --git a/tests/test_asteroid_mining.gd.uid b/tests/test_asteroid_mining.gd.uid new file mode 100644 index 0000000..150cb1b --- /dev/null +++ b/tests/test_asteroid_mining.gd.uid @@ -0,0 +1 @@ +uid://cx1x1mb4d614u diff --git a/tests/test_black_hole.gd b/tests/test_black_hole.gd index 6bfe495..52fbf7c 100644 --- a/tests/test_black_hole.gd +++ b/tests/test_black_hole.gd @@ -179,7 +179,13 @@ func _process(delta: float) -> bool: return false 2: # Mining is local, exactly like ore - no black-hole-specific code. - if elapsed < 1.2: + # DERIVED from the extraction rate, not a fixed 1.2s. That was + # long enough only while an extractor produced 2 a second; the + # rate has since been quartered, and a wait tuned to the old one + # reported a broken extractor that was working perfectly. What is + # under test is that dark matter accumulates LOCALLY rather than + # banking itself - not how quickly it arrives. + if elapsed < 1.0 / GameState.EXTRACTOR_RATE + 0.4: return false var mined: int = game_state.extractor_resource(extractor_index, GameState.RESOURCE_DARK_MATTER) if mined <= 0: diff --git a/tests/test_joystick_recentres.gd b/tests/test_joystick_recentres.gd new file mode 100644 index 0000000..bd7a1e8 --- /dev/null +++ b/tests/test_joystick_recentres.gd @@ -0,0 +1,99 @@ +extends SceneTree + +# Headless test: run with +# godot --headless -s res://tests/test_joystick_recentres.gd +# Both sticks float: the base jumps to wherever the finger lands, so a player +# does not have to hit an exact spot on a screen they cannot feel. Releasing +# used to put only the KNOB back onto the base, leaving the base parked +# wherever that touch had been - so an idle stick sat off-centre, in a +# different place every time, and read as a control still in use. +# +# Covers, for the movement stick AND the fire stick: +# A. an untouched stick sits in the middle of its own rect +# B. pressing off-centre moves the base there - the floating behaviour is +# still intact, which is the thing this must not break +# C. releasing puts base AND knob back in the middle +# D. releasing also clears the reported direction, so nothing keeps acting +# on a stick nobody is holding + +const OFFSET := Vector2(-58.0, 41.0) + +var main: Node +var stage: int = 0 +var elapsed: float = 0.0 + + +func _initialize() -> void: + main = load("res://Main.tscn").instantiate() + root.add_child(main) + current_scene = main + + +func _check(stick: Control, label: String) -> bool: + var home: Vector2 = stick.home_pos() + + # --- A. idle means centred ------------------------------------------- + if stick.base_pos.distance_to(home) > 0.01 or stick.knob_pos.distance_to(home) > 0.01: + return _fail("%s starts at base %s knob %s, expected both at %s" \ + % [label, stick.base_pos, stick.knob_pos, home]) + + # --- B. it still floats to the finger --------------------------------- + var press := InputEventScreenTouch.new() + press.index = 2 + press.pressed = true + press.position = stick.global_position + home + OFFSET + if not stick.handle_touch(press): + return _fail("%s refused a touch inside its own rect" % label) + if stick.base_pos.distance_to(home + OFFSET) > 0.01: + return _fail("%s did not float its base to the finger: base %s, expected %s" \ + % [label, stick.base_pos, home + OFFSET]) + + var drag := InputEventScreenDrag.new() + drag.index = 2 + drag.position = press.position + Vector2(stick.max_radius, 0.0) + stick.handle_drag(drag) + if stick.direction == Vector2.ZERO: + return _fail("%s reported no direction while pushed" % label) + if stick.knob_pos.distance_to(stick.base_pos) < stick.max_radius * 0.5: + return _fail("%s knob barely moved while pushed" % label) + + # --- C/D. release recentres everything and stops reporting ------------ + var release := InputEventScreenTouch.new() + release.index = 2 + release.pressed = false + release.position = drag.position + stick.handle_touch(release) + if stick.base_pos.distance_to(home) > 0.01: + return _fail("%s left its base at %s after release, expected %s" \ + % [label, stick.base_pos, home]) + if stick.knob_pos.distance_to(home) > 0.01: + return _fail("%s left its knob at %s after release, expected %s" \ + % [label, stick.knob_pos, home]) + if stick.direction != Vector2.ZERO: + return _fail("%s still reports %s after release" % [label, stick.direction]) + if stick.is_active(): + return _fail("%s still claims to be held after release" % label) + print("%s: floats to the finger, and returns to centre on release" % label) + return true + + +func _process(delta: float) -> bool: + elapsed += delta + if stage != 0: + return false + if elapsed < 0.2: + return false + stage = 1 + if not _check(main.get_node("UI/Joystick"), "movement stick"): + return true + if not _check(main.get_node("UI/FireStick"), "fire stick"): + return true + print("TEST_JOYSTICK_RECENTRES: PASS") + quit(0) + return true + + +func _fail(message: String) -> bool: + print("TEST_JOYSTICK_RECENTRES: FAIL (%s)" % message) + quit(1) + return false diff --git a/tests/test_joystick_recentres.gd.uid b/tests/test_joystick_recentres.gd.uid new file mode 100644 index 0000000..307f322 --- /dev/null +++ b/tests/test_joystick_recentres.gd.uid @@ -0,0 +1 @@ +uid://bk2jy7bvfm51b