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:
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user