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>
This commit is contained in:
2026-04-05 00:12:48 +02:00
co-authored by Claude Opus 4.6
parent 0d5323f66c
commit c94c5d7acb
9 changed files with 116 additions and 76 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
{
"_meta": {
"generated_from": "docs/design/star-map.json + server/data/systems.db",
"generated_from": "star-map.json + systems.db",
"system_count": 301,
"edge_count": 335,
"note": "Client-side star map data. Regenerate with: tooling/generate-star-map-data.py"
@@ -4360,4 +4360,4 @@
"GJ 71"
]
]
}
}
+7 -3
View File
@@ -367,10 +367,14 @@ func apply_snapshot(snapshot: Dictionary) -> void:
# Server persists the descriptor and includes it in ObserverSnapshot after load.
# Only update when field is present (null means no change).
if snapshot.has("character_visual_descriptor") and snapshot.character_visual_descriptor is Dictionary:
# load() returns a cached script — safe to call per-tick once the resource is in cache.
# Cannot use CharacterVisualDescriptor directly: autoloads compile before global class_names
# are registered, causing a parse-time "not declared" error.
var CVD := load("res://scripts/rendering/character_visual_descriptor.gd")
var restored = CVD.from_dict(snapshot.character_visual_descriptor)
if restored != null:
character_visual_descriptor = restored
if CVD != null:
var restored = CVD.from_dict(snapshot.character_visual_descriptor)
if restored != null:
character_visual_descriptor = restored
# v14: player_knowledge (#264, D-041) — partial KG dump for journal panel.
# Only update when field is present (null means no change, server sends when KG changes).
@@ -331,7 +331,6 @@ func _create_overhead_anchor() -> void:
return
_overhead_attachment = BoneAttachment3D.new()
_overhead_attachment.bone_name = "Head"
_overhead_attachment.bone_idx = bone_idx
_overhead_attachment.name = "OverheadAttachment"
_skeleton.add_child(_overhead_attachment)
+1 -1
View File
@@ -230,7 +230,7 @@ func test_descriptor_has_no_hair_highlight_tint_field() -> void:
assert_bool(d.has("hair_highlight_tint")).override_failure_message(
"to_dict() must NOT include hair_highlight_tint — highlight is auto-derived"
).is_false()
assert_bool(desc.get("hair_highlight_tint") != null and typeof(desc.get("hair_highlight_tint")) != TYPE_NIL).override_failure_message(
assert_bool("hair_highlight_tint" in desc).override_failure_message(
"CharacterVisualDescriptor must not define a hair_highlight_tint property"
).is_false()
+29 -25
View File
@@ -96,6 +96,7 @@ var _pan_start_offset: Vector2 = Vector2.ZERO
var _data_loaded: bool = false
var _insert_active: bool = true
var _show_edges: bool = false # toggle edge display
var _dirty: bool = true # redraw needed — set by state changes, cleared after _draw
func _ready() -> void:
@@ -106,8 +107,11 @@ func _ready() -> void:
func _process(_delta: float) -> void:
if _insert_active and _data_loaded:
if not visible:
return
if _dirty and _data_loaded:
queue_redraw()
_dirty = false
## Called from main.gd when insert state changes.
@@ -122,6 +126,8 @@ func set_insert_active(active: bool) -> void:
## Toggle visibility (e.g., from a keybind or button).
func toggle_visible() -> void:
visible = not visible
if visible:
_dirty = true
## Return the currently selected system data, or empty dict.
@@ -259,8 +265,8 @@ func _sector_sort_key(node: Dictionary) -> float:
## Deterministic float in [-1, 1] from a string key.
func _system_hash(key: String) -> float:
var h: int = key.hash()
return fmod(float(h) / 2147483647.0, 1.0) * 2.0 - 1.0
var h: int = key.hash() & 0x7FFFFFFF # mask to 31-bit positive range
return float(h) / 2147483647.0 * 2.0 - 1.0
# =============================================================================
@@ -465,9 +471,15 @@ func _gui_input(event: InputEvent) -> void:
_pan_start = mb.position
_pan_start_offset = _pan_offset
MOUSE_BUTTON_WHEEL_UP:
var old_zoom := _zoom
_zoom = clampf(_zoom + ZOOM_STEP, ZOOM_MIN, ZOOM_MAX)
if _zoom != old_zoom:
_dirty = true
MOUSE_BUTTON_WHEEL_DOWN:
var old_zoom := _zoom
_zoom = clampf(_zoom - ZOOM_STEP, ZOOM_MIN, ZOOM_MAX)
if _zoom != old_zoom:
_dirty = true
else:
if mb.button_index == MOUSE_BUTTON_MIDDLE:
_is_panning = false
@@ -476,17 +488,17 @@ func _gui_input(event: InputEvent) -> void:
var mm := event as InputEventMouseMotion
if _is_panning:
_pan_offset = _pan_start_offset + (mm.position - _pan_start)
_dirty = true
else:
_update_hover(mm.position)
func _handle_click(pos: Vector2) -> void:
## Find the system_id of the nearest node to screen position, or "" if none within HIT_RADIUS.
func _find_nearest_system(pos: Vector2) -> String:
var sz: Vector2 = get_rect().size
var center: Vector2 = sz * MAP_CENTER_FRACTION + _pan_offset
var best_dist: float = HIT_RADIUS
var best_sid: String = ""
for node: Dictionary in _nodes:
var sid: String = node.get("system_id", "")
if not _node_positions.has(sid):
@@ -496,30 +508,22 @@ func _handle_click(pos: Vector2) -> void:
if dist < best_dist:
best_dist = dist
best_sid = sid
return best_sid
if best_sid != "":
_selected_system = best_sid
func _handle_click(pos: Vector2) -> void:
var nearest := _find_nearest_system(pos)
if nearest != "":
_selected_system = nearest
_show_edges = true
else:
_selected_system = ""
_show_edges = false
_dirty = true
func _update_hover(pos: Vector2) -> void:
var sz: Vector2 = get_rect().size
var center: Vector2 = sz * MAP_CENTER_FRACTION + _pan_offset
var best_dist: float = HIT_RADIUS
var best_sid: String = ""
for node: Dictionary in _nodes:
var sid: String = node.get("system_id", "")
if not _node_positions.has(sid):
continue
var node_pos: Vector2 = center + _node_positions[sid] * _zoom
var dist: float = pos.distance_to(node_pos)
if dist < best_dist:
best_dist = dist
best_sid = sid
_hovered_system = best_sid
var nearest := _find_nearest_system(pos)
if nearest != _hovered_system:
_hovered_system = nearest
_dirty = true