feat: release spools time back up — thaw ramp on hand close, curved in-band thaw
Closing the hand no longer snaps to full speed: time ramps from where it stood (near-zero out of a freeze, current slow-mo out of a thaw, x1 out of realtime — depletion force-close gifts no slow-mo) up to x1 over release_thaw_s wall-clock seconds (default 0.5; 0 = snap). Reopening mid-ramp cancels it. In-band thaw gains an acceleration curve (thaw_curve, default 2.2): hangs slow longer, then rushes to full speed, instead of linear. Smoke gate: release must NOT be at x1 immediately after close (ramp present) and must reach x1 within 3s; both modes covered. 3/3 green on nh3-dev headless 4.7.1.
This commit is contained in:
+27
-2
@@ -51,6 +51,12 @@ extends CanvasLayer
|
||||
@export var band_unavailable := 0.1
|
||||
## Slowest thaw speed (time_scale at the top of the thaw band).
|
||||
@export var thaw_min_scale := 0.05
|
||||
## Closing the hand ramps time back to full speed over this many real
|
||||
## seconds instead of snapping (applies in both modes; 0 = snap).
|
||||
@export var release_thaw_s := 0.5
|
||||
## In-band thaw acceleration: 1 = linear; higher hangs slow longer,
|
||||
## then rushes to full speed.
|
||||
@export var thaw_curve := 2.2
|
||||
|
||||
@export_group("KINETIC card")
|
||||
@export var kinetic_cost := 1
|
||||
@@ -70,6 +76,8 @@ var hand_open := false
|
||||
var meter := 0.0
|
||||
var _hit_progress := 0
|
||||
var _last_ms := 0
|
||||
var _release_from := 1.0
|
||||
var _release_t := -1.0
|
||||
|
||||
# Slot order is the hand: Q = slot 1, E = slot 2.
|
||||
var cards: Array[Dictionary] = []
|
||||
@@ -123,6 +131,15 @@ func _process(_delta: float) -> void:
|
||||
if meter_mode:
|
||||
# Bands shift as the meter drains under an open hand.
|
||||
_apply_time_state()
|
||||
|
||||
# Release ramp: after the hand closes, time spools back up to full
|
||||
# speed on the wall clock instead of snapping.
|
||||
if not hand_open and _release_t >= 0.0:
|
||||
_release_t += real_dt
|
||||
var k := clampf(_release_t / release_thaw_s, 0.0, 1.0)
|
||||
Engine.time_scale = lerpf(_release_from, 1.0, k)
|
||||
if k >= 1.0:
|
||||
_release_t = -1.0
|
||||
$HUD.queue_redraw()
|
||||
|
||||
|
||||
@@ -165,6 +182,14 @@ func try_play(slot: int) -> void:
|
||||
|
||||
|
||||
func _set_open(open: bool) -> void:
|
||||
if open:
|
||||
_release_t = -1.0
|
||||
elif release_thaw_s > 0.0:
|
||||
# Spool up from wherever time stood: near-zero out of a freeze,
|
||||
# the current slow-mo out of a thaw, x1 out of realtime (so a
|
||||
# depletion force-close never gifts free slow-mo).
|
||||
_release_from = thaw_min_scale if get_tree().paused else Engine.time_scale
|
||||
_release_t = 0.0
|
||||
hand_open = open
|
||||
_apply_time_state()
|
||||
|
||||
@@ -173,7 +198,7 @@ func _apply_time_state() -> void:
|
||||
var tree := get_tree()
|
||||
if not hand_open:
|
||||
tree.paused = false
|
||||
Engine.time_scale = 1.0
|
||||
Engine.time_scale = _release_from if _release_t >= 0.0 else 1.0
|
||||
$Dim.visible = false
|
||||
return
|
||||
$Dim.visible = true
|
||||
@@ -185,7 +210,7 @@ func _apply_time_state() -> void:
|
||||
_set_dim(1.0)
|
||||
elif f >= band_realtime:
|
||||
tree.paused = false
|
||||
var t := (band_thaw - f) / (band_thaw - band_realtime)
|
||||
var t := pow((band_thaw - f) / (band_thaw - band_realtime), thaw_curve)
|
||||
Engine.time_scale = clampf(t, thaw_min_scale, 1.0)
|
||||
_set_dim(1.0 - t)
|
||||
else:
|
||||
|
||||
+10
-4
@@ -124,11 +124,16 @@ func _test_hand(arena: Node, mech: CharacterBody2D) -> void:
|
||||
if target.heat < target.heat_threshold:
|
||||
_failures.append("HEAT x2 left target below threshold (heat %.1f)" % target.heat)
|
||||
|
||||
# Release: time resumes, the over-threshold grunt detonates.
|
||||
# Release: the world unpauses into the spool-up ramp (not a snap),
|
||||
# reaches full speed, and the over-threshold grunt detonates.
|
||||
await _tap("hand_toggle")
|
||||
if hand.hand_open or get_tree().paused or Engine.time_scale != 1.0:
|
||||
if hand.hand_open or get_tree().paused:
|
||||
_failures.append("hand did not close / world did not resume")
|
||||
return
|
||||
if hand.release_thaw_s > 0.0 and Engine.time_scale >= 1.0:
|
||||
_failures.append("release snapped straight to full speed — no ramp")
|
||||
if not await _until(func() -> bool: return Engine.time_scale >= 0.999, 3.0, "release ramp never reached full speed"):
|
||||
return
|
||||
for i in 10:
|
||||
await get_tree().physics_frame
|
||||
if is_instance_valid(target):
|
||||
@@ -184,8 +189,9 @@ func _test_meter(hand: Node) -> void:
|
||||
_failures.append("toggle mode at knob 0 did not open+pause")
|
||||
await _tap("hand_toggle")
|
||||
hand.meter_mode = true
|
||||
if get_tree().paused or Engine.time_scale != 1.0:
|
||||
_failures.append("world did not resume after toggle-mode close")
|
||||
if get_tree().paused:
|
||||
_failures.append("world stayed paused after toggle-mode close")
|
||||
await _until(func() -> bool: return Engine.time_scale >= 0.999, 3.0, "toggle-mode release ramp never reached full speed")
|
||||
|
||||
|
||||
func _nearest_grunt(mech: CharacterBody2D) -> Node2D:
|
||||
|
||||
Reference in New Issue
Block a user