extends Node ## Central registry for all runtime-tunable parameters. ## Add a new param with one _reg_f() call in _register_all() — the menu picks it up automatically. ## Access: DP.f("key") • Persist: DP.save() / DP.load_saved() • Reset: DP.reset_all() signal any_changed(key: String, value: Variant) const SAVE_PATH := "user://debug_params.cfg" # _params holds the full metadata (min/max/step/default/section/type) and is the # authority the debug menu reads. _values is a flat key→value mirror so the hot # f()/b() reads — called dozens of times per matador per physics frame — cost a # single hash lookup instead of a nested dict access. Every write updates both. var _params: Dictionary = {} var _values: Dictionary = {} func _ready() -> void: _register_all() load_saved() func _register_all() -> void: # ── Movement ────────────────────────────────────────────────────────────── # WASD/arrows drive the bull: move_accel ramps flat velocity toward max_speed in # the input direction; move_friction bleeds it off when no direction is held. _reg_f("Movement", "max_speed", 10.0, 2.0, 1000.0) _reg_f("Movement", "move_accel", 120.0, 5.0, 600.0) _reg_f("Movement", "move_friction", 90.0, 5.0, 600.0) _reg_f("Movement", "jump_velocity", 4.5, 1.0, 200.0) _reg_f("Movement", "visual_turn_speed", 12.0, 1.0, 200.0) _reg_f("Movement", "bounce_restitution", 0.55, 0.0, 2.0, 0.05) _reg_f("Movement", "bounce_min_impact", 1.0, 0.0, 20.0) _reg_f("Movement", "wall_bounce_restitution", 0.6, 0.0, 1.5, 0.05) _reg_f("Movement", "wall_min_bounce_speed", 6.0, 0.0, 30.0) # ── Bull health ─────────────────────────────────────────────────────────── # The bull soaks bull_max_hp clean hits before it dies; each hit grants # bull_hit_iframes seconds of invulnerability so a single pass can't drain it. _reg_f("Bull", "bull_max_hp", 10.0, 1.0, 100.0, 1.0) _reg_f("Bull", "bull_hit_iframes", 0.8, 0.0, 3.0, 0.1) # ── Dust speed thresholds ───────────────────────────────────────────────── _reg_f("Dust", "dust_walk_spd", 1.5, 0.1, 10.0) _reg_f("Dust", "dust_charge_spd", 7.0, 1.0, 20.0) # ── Dust ────────────────────────────────────────────────────────────────── _reg_f("Dust", "dust_lifetime", 0.9, 0.1, 4.0) _reg_f("Dust", "dust_explosiveness", 0.35, 0.0, 1.0) _reg_f("Dust", "dust_spread", 45.0, 5.0, 90.0) _reg_f("Dust", "dust_gravity_y", -1.5,-15.0, 0.0) _reg_f("Dust", "walk_scale_min", 0.35, 0.05, 3.0) _reg_f("Dust", "walk_scale_max", 0.65, 0.05, 3.0) _reg_f("Dust", "walk_vel_min", 0.8, 0.1, 12.0) _reg_f("Dust", "walk_vel_max", 1.8, 0.1, 12.0) _reg_f("Dust", "charge_scale_min", 1.0, 0.05, 6.0) _reg_f("Dust", "charge_scale_max", 2.2, 0.05, 6.0) _reg_f("Dust", "charge_vel_min", 2.8, 0.1, 20.0) _reg_f("Dust", "charge_vel_max", 5.5, 0.1, 20.0) # ── Tail ────────────────────────────────────────────────────────────────── _reg_f("Tail", "tail_gravity", 9.8, 0.0, 30.0) _reg_f("Tail", "tail_damping", 0.08, 0.0, 0.5, 0.01) _reg_f("Tail", "tail_body_radius", 0.35, 0.05, 2.0, 0.05) _reg_f("Tail", "tail_wag_freq", 4.0, 0.5, 15.0) _reg_f("Tail", "tail_wag_idle", 0.3, 0.0, 2.0, 0.05) # ── Charge pose (horns-down lean during dash / roll) ───────────────────── _reg_f("Charge", "charge_head_pitch", 0.3, 0.0, 1.2, 0.01) _reg_f("Charge", "charge_head_speed", 5.0, 0.5, 20.0) # ── Body ────────────────────────────────────────────────────────────────── _reg_f("Body", "body_tilt_fwd", 0.015, 0.0, 0.1, 0.001) _reg_f("Body", "body_tilt_side", 0.01, 0.0, 0.1, 0.001) _reg_f("Body", "aerial_pitch_scale", 0.6, 0.0, 2.0) _reg_f("Body", "aerial_pitch_speed", 5.0, 0.5, 20.0) # ── Legs ────────────────────────────────────────────────────────────────── _reg_f("Legs", "leg_step_threshold", 0.15, 0.01, 1.0, 0.01) _reg_f("Legs", "leg_step_duration", 0.12, 0.02, 0.5, 0.01) _reg_f("Legs", "leg_step_height", 0.15, 0.0, 0.5) _reg_f("Legs", "leg_step_overshoot", 0.3, 0.0, 1.0) _reg_f("Legs", "gait_freq", 1.5, 0.1, 5.0) _reg_f("Legs", "launch_hold_time", 0.15, 0.0, 1.0, 0.01) _reg_f("Legs", "ragdoll_wobble_amp", 0.15, 0.0, 1.0) _reg_f("Legs", "tuck_apex_threshold", 0.5, -5.0, 5.0) _reg_f("Legs", "jump_tuck_speed", 8.0, 1.0, 30.0) _reg_f("Legs", "jump_tuck_up", 0.2, 0.0, 1.0) _reg_f("Legs", "jump_tuck_fwd", -0.1, -0.5, 0.5) _reg_f("Legs", "jump_land_threshold", 1.5, 0.5, 5.0) # ── Camera ──────────────────────────────────────────────────────────────── _reg_f("Camera", "charge_zoom_factor", 0.65, 0.3, 1.0, 0.05) _reg_f("Camera", "cam_lerp_power", 6.0, 0.5, 30.0) _reg_f("Camera", "cam_sensitivity", 0.005, 0.001, 0.05, 0.001) _reg_f("Camera", "cam_min_zoom", 3.5, 0.5, 20.0) _reg_f("Camera", "cam_max_zoom", 40.0, 2.0, 80.0) _reg_f("Camera", "cam_spring_length", 7.0, 1.0, 30.0) _reg_f("Camera", "cam_min_vert_angle", -60.0, -90.0, 0.0, 1.0) _reg_f("Camera", "cam_max_vert_angle", 45.0, 0.0, 90.0, 1.0) # Trailing "drag" camera: the focus point lags the bull and slides opposite to # its velocity, so the camera visibly drags behind during a fast charge. # cam_drag = metres of trail per m/s of bull speed (0 = rigid follow) # cam_drag_speed = how fast the trail offset eases toward its target # cam_follow_lerp = how tightly the focus point chases the bull (higher = snappier) _reg_f("Camera", "cam_drag", 0.12, 0.0, 1.0, 0.01) _reg_f("Camera", "cam_drag_speed", 3.5, 0.5, 20.0) _reg_f("Camera", "cam_follow_lerp", 10.0, 1.0, 40.0) # ── Matador ─────────────────────────────────────────────────────────────── _reg_f("Matador", "mat_spawn_count", 1.0, 1.0, 30.0, 1.0) # Hydra split: a slain matador is replaced on the spot by mat_split_factor fresh # matadors, so the horde doubles each generation (1 → 2 → 4 → 8 …) at the default # 2. Capped at mat_split_generations deep so the arena stays winnable (factor <2 or # generations 0 = no splitting, the classic one-and-done arena). _reg_f("Matador", "mat_split_factor", 2.0, 1.0, 6.0, 1.0) _reg_f("Matador", "mat_split_generations", 3.0, 0.0, 6.0, 1.0) # The bull wins outright once it has slain this many matadors, even if the hydra # still has bodies on the sand. 0 = disabled (win only when the horde dies out) _reg_f("Matador", "mat_win_kills", 21.0, 0.0, 200.0, 1.0) _reg_f("Matador", "mat_walk_speed", 5.5, 0.5, 10.0) _reg_f("Matador", "mat_wander_radius", 22.0, 5.0, 50.0) # Radius of the inner fighting floor: bull + matadors spawn on this ring (so # they stay off the benches). Tune in the console to hug the bench edge. _reg_f("Matador", "arena_spawn_radius", 10.0, 2.0, 22.0, 0.5) _reg_f("Matador", "mat_idle_min", 1.2, 0.0, 5.0, 0.1) _reg_f("Matador", "mat_idle_max", 4.0, 0.5, 10.0, 0.1) _reg_f("Matador", "mat_hit_threshold", 4.0, 1.0, 30.0) _reg_f("Matador", "mat_ragdoll_impulse", 6.0, 0.5, 20.0) # Ragdoll joint limits — lower swing/twist = stiffer corpse that keeps its # shape; higher angular damp settles the flail instead of flopping flat. _reg_f("Matador", "mat_joint_swing", 35.0, 0.0, 180.0, 1.0) _reg_f("Matador", "mat_joint_twist", 40.0, 0.0, 180.0, 1.0) _reg_f("Matador", "mat_ragdoll_damp", 1.5, 0.0, 10.0, 0.1) _reg_f("Matador", "mat_flee_range", 3.5, 2.0, 40.0) _reg_f("Matador", "mat_flee_speed", 7.0, 0.5, 12.0) _reg_f("Matador", "mat_charge_speed", 10.0, 1.0, 30.0) _reg_f("Matador", "mat_brace_range", 5.0, 2.0, 20.0) _reg_f("Matador", "mat_brace_duration", 0.7, 0.05, 1.5, 0.05) _reg_f("Matador", "mat_commit_dist", 2.5, 0.5, 6.0, 0.1) _reg_f("Matador", "mat_step_speed", 6.5, 2.0, 20.0) _reg_f("Matador", "mat_step_duration", 0.35, 0.1, 1.0, 0.05) # How strongly the sidestep biases toward the bull so the blade sweeps through # its path during the pass (0 = pure lateral dodge, 1 = straight at the bull). _reg_f("Matador", "mat_pass_lunge", 0.35, 0.0, 1.0, 0.05) _reg_f("Matador", "mat_dodge_cooldown", 2.5, 0.5, 12.0, 0.25) _reg_f("Matador", "mat_dodge_chance", 0.72, 0.0, 1.0, 0.05) _reg_f("Matador", "mat_attack_range", 8.0, 2.0, 20.0) _reg_f("Matador", "mat_attack_speed", 7.5, 1.0, 12.0) _reg_f("Matador", "mat_attack_duration", 1.8, 0.5, 5.0, 0.1) _reg_f("Matador", "mat_attack_min_dist", 3.2, 0.5, 6.0, 0.1) # Forward drive during the opening of a melee swing — turns the stationary stab # into an estocada lunge so the blade covers ground toward the bull. _reg_f("Matador", "mat_lunge_speed", 13.0, 0.0, 20.0) _reg_f("Matador", "mat_lunge_time", 0.22, 0.0, 0.6, 0.01) # Reach of the reach-based stab (the reliable melee hit) and its re-strike gap. # The bull dies if it comes inside mat_stab_reach of a matador that's attacking, # bracing, or passing with a drawn blade — closing in is genuinely deadly. _reg_f("Matador", "mat_stab_reach", 1.7, 0.5, 4.0, 0.1) _reg_f("Matador", "mat_stab_cooldown", 0.6, 0.1, 3.0, 0.1) # Half-angle (deg) the matador must be facing the bull within for a stab to land. # Stops "sword's off to the side but I still died" — you're only gored when the # blade is actually presented at you. _reg_f("Matador", "mat_stab_arc", 55.0, 5.0, 180.0, 5.0) _reg_f("Matador", "mat_roll_chance", 0.35, 0.0, 1.0, 0.05) _reg_f("Matador", "mat_roll_duration", 0.45, 0.1, 1.5, 0.05) # Playback rate of the Draw_weapon clip used for both drawing and sheathing — # higher = snappier unholster/holster. _reg_f("Matador", "mat_draw_speed", 1.9, 0.25, 3.0, 0.05) # Cross-fade between matador animation clips — smooths Run/Attack/Taunt cuts. _reg_f("Matador", "mat_anim_blend", 0.12, 0.0, 0.5, 0.01) # ── Matador heuristics (utility AI) ─────────────────────────────────────── # Every mat_decide_interval seconds the matador scores its options — dodge the # pass, close for a strike, retreat, throw the blade, or strut/taunt — and picks # the highest. These weights bias the personality: crank aggression for a reckless # bullfighter, caution for a slippery one. mat_w_commit is a stickiness bonus for # the current choice so it doesn't dither between two near-tied options. _reg_f("Matador", "mat_decide_interval", 0.2, 0.05, 1.0, 0.05) _reg_f("Matador", "mat_w_aggression", 1.1, 0.0, 3.0, 0.05) _reg_f("Matador", "mat_w_caution", 1.0, 0.0, 3.0, 0.05) _reg_f("Matador", "mat_w_flee", 0.8, 0.0, 3.0, 0.05) # Showmanship (strut, circle the bull, taunt) — the supporting matadors lean on # this to spread out and give the lead room, so a whole crowd reads as a bullring, # not a swarm. Crank it for more posturing, drop it for a relentless mob. _reg_f("Matador", "mat_w_showmanship", 1.6, 0.0, 3.0, 0.05) # Weight on hurling the blade. Kept low — a thrown sword is an occasional flourish, # not a barrage; raising this brings back the pelting. _reg_f("Matador", "mat_w_throw", 0.35, 0.0, 3.0, 0.05) # How many matadors press the bull at once. The nearest this-many engage (close, # throw); everyone else keeps their distance and taunts, taking turns as the bull # moves. 1 = strict one-on-one, higher = a bolder pack. _reg_f("Matador", "mat_engage_slots", 2.0, 1.0, 8.0, 1.0) # Preferred distance (m) a taunting matador circles the bull at while waiting its # turn — the showmanship ring the crowd spreads out onto. _reg_f("Matador", "mat_taunt_ring", 7.0, 3.0, 15.0, 0.5) # How long (s) a matador safely out of the action holds a taunt before drifting to # a new spot — much longer than the between-passes idle, so bystanders mostly pose # for the crowd instead of milling about. Actual hold is randomised ±30%. _reg_f("Matador", "mat_taunt_hold", 8.0, 1.0, 20.0, 0.5) # Small so it smooths dithering without locking the matador into idling. _reg_f("Matador", "mat_w_commit", 0.15, 0.0, 2.0, 0.05) # ── Sword ───────────────────────────────────────────────────────────────── # Local seating of the sword in the right hand (drawn / fighting). # Defaults are identity — the bone is oriented in Blender to seat the sword # directly; these exist to fine-tune in-game without re-exporting. _reg_f("Sword", "sword_pos_x", 0.0, -1.0, 1.0, 0.01) _reg_f("Sword", "sword_pos_y", 0.0, -1.0, 1.0, 0.01) _reg_f("Sword", "sword_pos_z", 0.0, -1.0, 1.0, 0.01) _reg_f("Sword", "sword_rot_x", 0.0, -180.0, 180.0, 1.0) _reg_f("Sword", "sword_rot_y", 0.0, -180.0, 180.0, 1.0) _reg_f("Sword", "sword_rot_z", 0.0, -180.0, 180.0, 1.0) # Local seating of the sword in the hip holster (sheathed / wandering). # Independent of the hand grip so each pose can be tuned without affecting the # other. Defaults angle the blade down along the matador's side. _reg_f("Sword", "sword_rest_pos_x", 0.0, -1.0, 1.0, 0.01) _reg_f("Sword", "sword_rest_pos_y", 0.0, -1.0, 1.0, 0.01) _reg_f("Sword", "sword_rest_pos_z", 0.0, -1.0, 1.0, 0.01) _reg_f("Sword", "sword_rest_rot_x", -120.0, -180.0, 180.0, 1.0) _reg_f("Sword", "sword_rest_rot_y", 0.0, -180.0, 180.0, 1.0) _reg_f("Sword", "sword_rest_rot_z", 0.0, -180.0, 180.0, 1.0) # ── Spear throw (matador ranged attack) ────────────────────────────────── # Local seating of the spear in the throwing hand during the wind-up. Tune to # seat the shaft in the grip without re-exporting; flight orientation is derived # from the aim direction at release, not from this pose. _reg_f("Sword", "spear_pos_x", 0.0, -1.0, 1.0, 0.01) _reg_f("Sword", "spear_pos_y", 0.0, -1.0, 1.0, 0.01) _reg_f("Sword", "spear_pos_z", 0.0, -1.0, 1.0, 0.01) _reg_f("Sword", "spear_rot_x", 0.0, -180.0, 180.0, 1.0) _reg_f("Sword", "spear_rot_y", 0.0, -180.0, 180.0, 1.0) _reg_f("Sword", "spear_rot_z", 0.0, -180.0, 180.0, 1.0) # Delay before a fresh sword reappears in the holster if one is ever lost. _reg_f("Sword", "mat_resword_delay", 2.0, 0.0, 10.0, 0.5) _reg_f("Sword", "mat_throw_range", 18.0, 5.0, 40.0) _reg_f("Sword", "mat_throw_min_dist", 5.0, 2.0, 15.0, 0.1) _reg_f("Sword", "mat_throw_windup", 0.5, 0.2, 2.0, 0.05) _reg_f("Sword", "mat_throw_release", 0.55, 0.1, 0.95, 0.01) # Horizontal speed of the thrown spear; the vertical launch is solved from this # and the gravity below so the arc lands on the bull. Lower = a higher, slower lob. _reg_f("Sword", "mat_throw_speed", 11.0, 5.0, 60.0) # Gravity scale on the spear (higher = a more pronounced arc) and the vertical aim # offset from the bull origin down onto its low collision body. _reg_f("Sword", "mat_throw_gravity", 1.0, 0.1, 3.0, 0.05) _reg_f("Sword", "mat_throw_aim_y", -0.45,-1.5, 0.5, 0.05) # Aim scatter (± degrees) so thrown blades can be read and dodged on the move. _reg_f("Sword", "mat_throw_spread", 11.0, 0.0, 30.0, 0.5) _reg_f("Sword", "mat_throw_arm_cock", 70.0,-180.0, 180.0, 1.0) _reg_f("Sword", "mat_throw_arm_release", -90.0,-180.0, 180.0, 1.0) _reg_f("Sword", "mat_throw_elbow", 80.0,-180.0, 180.0, 1.0) # ── Abilities ───────────────────────────────────────────────────────────── _reg_f("Abilities", "kick_cooldown", 1.5, 0.2, 10.0, 0.1) _reg_f("Abilities", "kick_range", 6.0, 0.5, 8.0, 0.1) _reg_f("Abilities", "kick_strength", 12.0, 1.0, 30.0) _reg_f("Abilities", "slam_cooldown", 4.0, 0.5, 15.0, 0.1) _reg_f("Abilities", "slam_jump_velocity", 7.0, 2.0, 20.0) # Descent gravity multiplier — higher = faster crash, less airtime. _reg_f("Abilities", "slam_fall_mult", 3.0, 1.0, 8.0, 0.5) # Upward launch velocity (m/s) given to a slammed matador's ragdoll — pops them # skyward. ~7 ≈ a 2.5 m hop; 0 disables the launch. _reg_f("Abilities", "slam_launch_up", 7.0, 0.0, 15.0, 0.5) _reg_f("Abilities", "slam_range", 4.0, 1.0, 10.0, 0.1) _reg_f("Abilities", "slam_strength", 14.0, 1.0, 30.0) _reg_f("Abilities", "dash_cooldown", 0.5, 0.1, 10.0, 0.1) _reg_f("Abilities", "dash_speed", 66.0, 5.0, 200.0) # Exponential rate the dash bleeds from dash_speed back to cruise (max_speed). # Higher = a snappier bullet that dies off quicker; lower = a longer glide. _reg_f("Abilities", "dash_decel", 7.0, 0.5, 30.0, 0.5) # ── Roll (homing chain ball) ────────────────────────────────────────────── # The bull curls into a ball and ACCELERATES the longer it rolls, from # roll_base_speed up to roll_max_speed over roll_rampup_time. It HOMES on the # closest matador; on slamming one the ball POPS it (launched skyward via # roll_knockup) then CHAINS to the next closest, up to roll_chain_count times. # roll_turn is the homing turn rate. When no chain targets remain, the roll ends. _reg_f("Roll", "roll_cooldown", 3.0, 0.5, 15.0, 0.1) _reg_f("Roll", "roll_duration", 2.5, 0.5, 6.0, 0.1) # max roll time _reg_f("Roll", "roll_chain_count", 2.0, 0.0, 8.0, 1.0) # extra matadors chained after the first _reg_f("Roll", "roll_base_speed", 28.0, 5.0, 120.0) # speed at the start of the roll _reg_f("Roll", "roll_max_speed", 95.0, 10.0, 200.0) # top speed once fully ramped _reg_f("Roll", "roll_rampup_time", 2.0, 0.2, 6.0, 0.1) # time to reach top speed _reg_f("Roll", "roll_turn", 7.0, 0.2, 20.0) # homing turn rate (higher = tighter tracking) _reg_f("Roll", "roll_spin_radius", 1.4, 0.3, 4.0, 0.1) # ball radius for tumble spin (smaller = faster) _reg_f("Roll", "roll_hit_radius", 2.4, 0.5, 6.0, 0.1) # contact radius that pops the ball _reg_f("Roll", "roll_hit_strength", 22.0, 1.0, 40.0) # horizontal launch on the pop _reg_f("Roll", "roll_knockup", 11.0, 0.0, 25.0) # vertical knock-up on the pop # ── Debug overlays (see debug_draw.gd) ─────────────────────────────────── _reg_b("Debug", "show_collisions", false) # PS1 post-process filter (see ps1_filter.gd on the fullscreen ColorRect): # screen-covering CRT/PS1 shader — pixelation, colour-banding + dither, and a # rolling scanline. Keys map 1:1 onto the shader uniforms; show_ps1 toggles the # whole overlay. Ranges mirror the shader's hint_range so the console can't push # a uniform out of bounds. _reg_b("PS1", "show_ps1", true) _reg_f("PS1", "ps1_pixel_scale", 4.0, 1.0, 8.0, 1.0) _reg_f("PS1", "ps1_color_depth", 5.0, 2.0, 8.0, 1.0) _reg_f("PS1", "ps1_dither_strength", 0.8, 0.0, 2.0, 0.05) _reg_f("PS1", "ps1_scanline_strength", 0.2, 0.0, 1.0, 0.05) _reg_f("PS1", "ps1_roll_speed", 3.0, 0.0, 10.0, 0.1) _reg_f("PS1", "ps1_roll_strength", 1.0, 0.0, 1.0, 0.05) _reg_b("Debug", "show_hitboxes", false) _reg_b("Debug", "show_bones", false) _reg_b("Debug", "show_raycasts", false) _reg_b("Debug", "show_tail", false) _reg_b("Debug", "show_stats", false) _reg_b("Debug", "show_states", false) _reg_b("Debug", "show_animation_name", false) _reg_b("Debug", "show_wireframe", false) _reg_b("Debug", "show_grid", false) _reg_b("Debug", "show_axes", false) # Force the on-screen touch controls (joystick + ability buttons) on even on a # desktop build — otherwise they appear only when a touchscreen is detected. _reg_b("Touch", "force_touch_controls", false) func _reg_f(section: String, key: String, default: float, min_val: float, max_val: float, step: float = 0.05) -> void: _params[key] = { "value": default, "default": default, "section": section, "type": TYPE_FLOAT, "min": min_val, "max": max_val, "step": step, } _values[key] = default func _reg_b(section: String, key: String, default: bool) -> void: _params[key] = { "value": default, "default": default, "section": section, "type": TYPE_BOOL, } _values[key] = default ## Read a float param. Panics on unknown key — intentional: typos should be loud. func f(key: String) -> float: return _values[key] as float ## Read a bool param. func b(key: String) -> bool: return _values[key] as bool func set_value(key: String, value: Variant) -> void: if not _params.has(key): return _params[key]["value"] = value _values[key] = value any_changed.emit(key, value) ## Returns the full metadata dict — used by Console to build UI. func get_all() -> Dictionary: return _params func save() -> void: var cfg := ConfigFile.new() for key: String in _params: var p: Dictionary = _params[key] cfg.set_value(p["section"], key, p["value"]) cfg.save(SAVE_PATH) func load_saved() -> void: var cfg := ConfigFile.new() if cfg.load(SAVE_PATH) != OK: return for section: String in cfg.get_sections(): for key: String in cfg.get_section_keys(section): if not _params.has(key): continue var p: Dictionary = _params[key] var val: Variant = cfg.get_value(section, key) if p["type"] == TYPE_FLOAT: val = clampf(val as float, p["min"] as float, p["max"] as float) _params[key]["value"] = val _values[key] = val func reset_all() -> void: for key: String in _params: var default: Variant = _params[key]["default"] _params[key]["value"] = default _values[key] = default any_changed.emit("__all__", null) ## Reset a single param to its default. No-op on unknown key. func reset(key: String) -> void: if not _params.has(key): return var default: Variant = _params[key]["default"] _params[key]["value"] = default _values[key] = default any_changed.emit(key, default) ## True when the param currently differs from its registered default. func is_modified(key: String) -> bool: return _params.has(key) and _params[key]["value"] != _params[key]["default"]