fix(client): address PR #30 review — 9 items from Hoshe and Tyre

- Add SimBridge._last_snapshot = null to P3 before_test() (contamination risk)
- Clarify mono.is_visible is custom property, not CanvasItem builtin
- Camera smoothing test uses approximate equality instead of exact
- Propagate inner _encode_message errors in array/dict encoding
- Add explanatory comment on int_64 encoder branch
- Document unfalsifiable gauntlet guard as intentional future-proof
- Remove loose D-053 citation from LERP_SPEED pin
- Assert FogOverlay is sibling of FogGroup under World
- Add CANVAS_MODAL=30 (ModalLayer) assertion in P3-Z04

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-18 09:46:08 +01:00
co-authored by Claude Opus 4.6
parent 1254fa1b28
commit 4f20a996aa
4 changed files with 43 additions and 12 deletions
+10 -3
View File
@@ -88,6 +88,7 @@ static func _encode_message(buffer: StreamPeerBuffer, value):
buffer.put_u8(types["uint_32"])
buffer.put_u32(value)
elif value < 0:
# Negative beyond int_32 range — encode as int_64 (0xd3)
buffer.put_u8(types["int_64"])
buffer.put_64(value)
else:
@@ -133,7 +134,9 @@ static func _encode_message(buffer: StreamPeerBuffer, value):
return ERR_INVALID_DATA
for obj in value:
_encode_message(buffer, obj)
var inner_err = _encode_message(buffer, obj)
if inner_err:
return inner_err
TYPE_DICTIONARY:
var size = value.size()
@@ -150,8 +153,12 @@ static func _encode_message(buffer: StreamPeerBuffer, value):
return ERR_INVALID_DATA
for key in value:
_encode_message(buffer, key)
_encode_message(buffer, value[key])
var key_err = _encode_message(buffer, key)
if key_err:
return key_err
var val_err = _encode_message(buffer, value[key])
if val_err:
return val_err
TYPE_PACKED_BYTE_ARRAY:
var size = value.size()
+5 -2
View File
@@ -214,8 +214,11 @@ func test_snapshot_without_room_id_shows_no_gauntlet_ui() -> void:
# Apply to GameState — gauntlet-related state should not exist
GameState.apply_snapshot(snapshot)
# GameState should not have gauntlet fields set (they don't exist yet,
# and when added, they must default to null/false)
# Guard: when #496 adds GameState.room_id / gauntlet_mode properties,
# these assertions become falsifiable — they'll catch any code path that
# sets gauntlet state from a non-gauntlet snapshot. Currently Object.get()
# returns null for nonexistent properties, so this passes trivially until
# the properties are defined.
assert_that(GameState.get("room_id")).override_failure_message(
"GameState.room_id should not exist or be null in non-gauntlet mode"
).is_null()
+8 -1
View File
@@ -80,7 +80,10 @@ func test_camera_smoothing_convergence() -> void:
for i in 5:
inst._process(0.016)
var expected := GameState.player_position * Constants.TILE_SIZE
assert_that(camera.global_position).is_equal(expected)
var dist := camera.global_position.distance_to(expected)
assert_that(dist < 0.1).override_failure_message(
"Camera should converge to player position (dist: %.4f)" % dist
).is_true()
func test_camera_viewport_tracks_player_position() -> void:
@@ -191,6 +194,10 @@ func test_entity_player_color_regardless_of_sector() -> void:
func test_monologue_display_visible_hidden() -> void:
# P2-U01: MonologueDisplay starts hidden, becomes visible after show_monologue.
# Note: mono.is_visible is a custom bool property on MonologueDisplay
# (monologue_display.gd:11), not the built-in CanvasItem.is_visible() method.
# The monologue uses tween alpha for visual hide/show, so the built-in
# .visible stays true — we test the script's own state tracking.
var inst := _make_scene()
var mono = inst.get_node("UILayer/MonologueDisplay")
assert_that(mono.is_visible).override_failure_message(
+20 -6
View File
@@ -13,6 +13,7 @@ var _instance: Node = null
func before_test() -> void:
SimBridge.reset_test_state()
SimBridge._last_snapshot = null
GameState.current_tick = 0
GameState.player_position = Vector2.ZERO
GameState.visible_entities = []
@@ -96,7 +97,13 @@ func test_z_fog_above_world_content() -> void:
var inst := _make_scene()
var fog = inst.get_node("World/FogOverlay")
var fog_entities = inst.get_node("World/FogEntities")
var fog_group = inst.get_node("World/FogGroup")
var overhead = inst.get_node("World/FogGroup/Overhead")
# FogOverlay must be a sibling of FogGroup (both children of World),
# not a child of FogGroup — fog renders OVER the composited group.
assert_that(fog.get_parent()).override_failure_message(
"FogOverlay must be sibling of FogGroup (both under World)"
).is_equal(fog_group.get_parent())
assert_that(fog.z_index).override_failure_message(
"FogOverlay z_index must be Z_FOG (%d) per D-049" % Constants.Z_FOG
).is_equal(Constants.Z_FOG)
@@ -117,15 +124,22 @@ func test_z_ui_layer_above_world() -> void:
var inst := _make_scene()
var ui_layer = inst.get_node("UILayer") as CanvasLayer
var insert_layer = inst.get_node("InsertOverlay") as CanvasLayer
assert_that(ui_layer.layer).override_failure_message(
"UILayer must be CanvasLayer %d" % Constants.CANVAS_UI
).is_equal(Constants.CANVAS_UI)
var modal_layer = inst.get_node("ModalLayer") as CanvasLayer
assert_that(insert_layer.layer).override_failure_message(
"InsertOverlay must be CanvasLayer %d" % Constants.CANVAS_INSERT
).is_equal(Constants.CANVAS_INSERT)
assert_that(ui_layer.layer).override_failure_message(
"UILayer must be CanvasLayer %d" % Constants.CANVAS_UI
).is_equal(Constants.CANVAS_UI)
assert_that(modal_layer.layer).override_failure_message(
"ModalLayer must be CanvasLayer %d" % Constants.CANVAS_MODAL
).is_equal(Constants.CANVAS_MODAL)
assert_that(ui_layer.layer > insert_layer.layer).override_failure_message(
"UILayer must render above InsertOverlay"
).is_true()
assert_that(modal_layer.layer > ui_layer.layer).override_failure_message(
"ModalLayer must render above UILayer"
).is_true()
# -- Entity lerp (3) ----------------------------------------------------------
@@ -318,8 +332,8 @@ func test_recognition_blob_removed_when_absent() -> void:
func test_entity_renderer_lerp_speed_constant() -> void:
# P3-T05: LERP_SPEED is tuned at 12.0 per D-053 stance discussion.
# Ensures no accidental changes to the feel of entity movement.
# P3-T05: LERP_SPEED is tuned at 12.0 — exponential smoothing constant
# for entity visual interpolation. Pin value to prevent accidental changes.
assert_that(EntityRenderer.LERP_SPEED).override_failure_message(
"LERP_SPEED must be 12.0 per D-053 tuning"
"LERP_SPEED must be 12.0 (entity movement feel constant)"
).is_equal(12.0)