Compare commits
4 Commits
5ca64b8dff
..
multi2
| Author | SHA1 | Date | |
|---|---|---|---|
| 34a649f507 | |||
| e04c5519d0 | |||
| 7ac87984bc | |||
| 776aaa3020 |
+27
-2
@@ -141,6 +141,9 @@ func refresh_visuals(ids: Array[String]) -> void:
|
|||||||
|
|
||||||
_make_cosmetic(visual)
|
_make_cosmetic(visual)
|
||||||
container_root.add_child(visual)
|
container_root.add_child(visual)
|
||||||
|
# Must happen after add_child: entering the world is what puts the body
|
||||||
|
# into the physics space, so it can only be taken out again afterwards.
|
||||||
|
_remove_from_physics(visual)
|
||||||
visual.position = positions[idx].position
|
visual.position = positions[idx].position
|
||||||
visual.rotation = positions[idx].rotation
|
visual.rotation = positions[idx].rotation
|
||||||
|
|
||||||
@@ -152,10 +155,18 @@ func refresh_visuals(ids: Array[String]) -> void:
|
|||||||
func _make_cosmetic(visual: Node3D) -> void:
|
func _make_cosmetic(visual: Node3D) -> void:
|
||||||
var net_pickable := visual.get_node_or_null("NetPickable")
|
var net_pickable := visual.get_node_or_null("NetPickable")
|
||||||
if net_pickable:
|
if net_pickable:
|
||||||
net_pickable.queue_free()
|
# Detach and free it outright rather than queue_free(): this runs before
|
||||||
|
# `visual` is added to the tree, and a merely-queued node still enters the
|
||||||
|
# tree with its parent and runs _ready() (which starts syncing and logging)
|
||||||
|
# before the queued deletion lands at the end of the frame.
|
||||||
|
visual.remove_child(net_pickable)
|
||||||
|
net_pickable.free()
|
||||||
if visual is RigidBody3D:
|
if visual is RigidBody3D:
|
||||||
visual.freeze = true
|
visual.freeze = true
|
||||||
visual.freeze_mode = RigidBody3D.FREEZE_MODE_KINEMATIC
|
# STATIC, not KINEMATIC: a kinematic body is still driven by the physics
|
||||||
|
# engine (see _remove_from_physics), and "static decoration" is what this
|
||||||
|
# actually is.
|
||||||
|
visual.freeze_mode = RigidBody3D.FREEZE_MODE_STATIC
|
||||||
visual.collision_layer = 0
|
visual.collision_layer = 0
|
||||||
visual.collision_mask = 0
|
visual.collision_mask = 0
|
||||||
if visual is XRToolsPickable:
|
if visual is XRToolsPickable:
|
||||||
@@ -164,6 +175,20 @@ func _make_cosmetic(visual: Node3D) -> void:
|
|||||||
visual.set_physics_process(false)
|
visual.set_physics_process(false)
|
||||||
|
|
||||||
|
|
||||||
|
# Take a display-only copy out of the physics simulation completely.
|
||||||
|
#
|
||||||
|
# Freezing is not enough. Under Jolt (this project's physics engine) a frozen
|
||||||
|
# KINEMATIC RigidBody3D is still simulated: it is driven toward its target
|
||||||
|
# transform by velocity rather than being teleported. Parented to a plate that
|
||||||
|
# gets picked up and carried, it therefore lags behind, keeps its velocity, and
|
||||||
|
# overshoots — so the food visibly slid off the plate and ended up metres away,
|
||||||
|
# differently on each peer since each simulates its own copy. A body with no
|
||||||
|
# space is never touched by the engine, so it simply follows its parent.
|
||||||
|
func _remove_from_physics(visual: Node3D) -> void:
|
||||||
|
if visual is RigidBody3D:
|
||||||
|
PhysicsServer3D.body_set_space((visual as RigidBody3D).get_rid(), RID())
|
||||||
|
|
||||||
|
|
||||||
#plate (Pickalbe)
|
#plate (Pickalbe)
|
||||||
#XRGrapPoints
|
#XRGrapPoints
|
||||||
#container (script) (meal positions[1], side positions[4])
|
#container (script) (meal positions[1], side positions[4])
|
||||||
|
|||||||
@@ -30,6 +30,13 @@ func _process(_delta: float) -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _set_contained_ids(value: Array[String]) -> void:
|
func _set_contained_ids(value: Array[String]) -> void:
|
||||||
|
# Only rebuild when the contents actually changed. This property is
|
||||||
|
# replicated in ALWAYS mode, so the synchronizer assigns it every network
|
||||||
|
# tick on every peer that doesn't own the plate — and refresh_visuals()
|
||||||
|
# frees and re-instantiates a scene per item each time. That was thousands
|
||||||
|
# of throwaway nodes per run (and a log line from each one's NetPickable).
|
||||||
|
if contained_ids == value:
|
||||||
|
return
|
||||||
contained_ids = value
|
contained_ids = value
|
||||||
# Deferred: this can be written by the replicated spawn payload before
|
# Deferred: this can be written by the replicated spawn payload before
|
||||||
# this node's own @onready vars (container) have resolved.
|
# this node's own @onready vars (container) have resolved.
|
||||||
|
|||||||
@@ -22,3 +22,4 @@ static func get_random_side() -> String:
|
|||||||
var rand_index = randi() % sides_in_play.size()
|
var rand_index = randi() % sides_in_play.size()
|
||||||
print("GameManager: get_random_side() returning ", sides_in_play[rand_index])
|
print("GameManager: get_random_side() returning ", sides_in_play[rand_index])
|
||||||
return sides_in_play[rand_index]
|
return sides_in_play[rand_index]
|
||||||
|
|
||||||
|
|||||||
+41
-11
@@ -24,6 +24,11 @@ var _original_freeze_mode: int
|
|||||||
# branch of apply_held_state() clears while someone else is holding the item.
|
# branch of apply_held_state() clears while someone else is holding the item.
|
||||||
var _original_enabled: bool
|
var _original_enabled: bool
|
||||||
|
|
||||||
|
# Whether the "our own hand still holds this" guard has already been logged for
|
||||||
|
# the current grab. apply_held_state() runs every network tick, so without this
|
||||||
|
# the guard message repeats for as long as you hold the item.
|
||||||
|
var _grab_race_logged := false
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
_pickable = get_parent() as XRToolsPickable
|
_pickable = get_parent() as XRToolsPickable
|
||||||
@@ -55,10 +60,19 @@ func _set_net_held_by(value: int) -> void:
|
|||||||
## Puts the item in the right physics state for whether this peer currently
|
## Puts the item in the right physics state for whether this peer currently
|
||||||
## owns it. Called locally after net_held_by changes, and directly by
|
## owns it. Called locally after net_held_by changes, and directly by
|
||||||
## NetworkManager._set_item_authority right after an authority handoff.
|
## NetworkManager._set_item_authority right after an authority handoff.
|
||||||
|
##
|
||||||
|
## IMPORTANT: this runs on every network tick, not just on a real change.
|
||||||
|
## net_held_by is replicated in ALWAYS mode, so the synchronizer assigns it every
|
||||||
|
## tick on non-authority peers — unchanged value included — and that assignment
|
||||||
|
## lands in _set_net_held_by(), which calls this. So every branch here has to be
|
||||||
|
## idempotent and silent when there is nothing to do: otherwise each item logs a
|
||||||
|
## line and rewrites four physics properties every tick on every peer that
|
||||||
|
## doesn't own it.
|
||||||
func apply_held_state() -> void:
|
func apply_held_state() -> void:
|
||||||
if not _pickable:
|
if not _pickable:
|
||||||
return
|
return
|
||||||
if not NetworkManager.is_online() or is_multiplayer_authority():
|
if not NetworkManager.is_online() or is_multiplayer_authority():
|
||||||
|
_grab_race_logged = false
|
||||||
# We own this item's simulation (offline, loose+server, or currently
|
# We own this item's simulation (offline, loose+server, or currently
|
||||||
# holding it). If it's not actively in our own hand right now, make
|
# holding it). If it's not actively in our own hand right now, make
|
||||||
# sure it isn't still left frozen/collision-less from a previous
|
# sure it isn't still left frozen/collision-less from a previous
|
||||||
@@ -68,15 +82,16 @@ func apply_held_state() -> void:
|
|||||||
if not _pickable.is_picked_up():
|
if not _pickable.is_picked_up():
|
||||||
var changed := _pickable.freeze_mode != _original_freeze_mode \
|
var changed := _pickable.freeze_mode != _original_freeze_mode \
|
||||||
or _pickable.collision_mask != _pickable.original_collision_mask
|
or _pickable.collision_mask != _pickable.original_collision_mask
|
||||||
if changed and NetworkManager.is_online():
|
if changed:
|
||||||
print(
|
if NetworkManager.is_online():
|
||||||
"%s: reclaiming ownership, restoring freeze_mode %d->%d collision_mask %d->%d" % [
|
print(
|
||||||
_pickable.name, _pickable.freeze_mode, _original_freeze_mode,
|
"%s: reclaiming ownership, restoring freeze_mode %d->%d collision_mask %d->%d" % [
|
||||||
_pickable.collision_mask, _pickable.original_collision_mask
|
_pickable.name, _pickable.freeze_mode, _original_freeze_mode,
|
||||||
]
|
_pickable.collision_mask, _pickable.original_collision_mask
|
||||||
)
|
]
|
||||||
_pickable.freeze_mode = _original_freeze_mode
|
)
|
||||||
_pickable.collision_mask = _pickable.original_collision_mask
|
_pickable.freeze_mode = _original_freeze_mode
|
||||||
|
_pickable.collision_mask = _pickable.original_collision_mask
|
||||||
# Unlike freeze/collision (which XRToolsPickable manages itself while
|
# Unlike freeze/collision (which XRToolsPickable manages itself while
|
||||||
# held), `enabled` is only ever written by the non-authority branch
|
# held), `enabled` is only ever written by the non-authority branch
|
||||||
# below, so it must be restored here or it stays false forever: once a
|
# below, so it must be restored here or it stays false forever: once a
|
||||||
@@ -101,13 +116,16 @@ func apply_held_state() -> void:
|
|||||||
# hand mid-grab — only an explicit force_release_item rejection, or
|
# hand mid-grab — only an explicit force_release_item rejection, or
|
||||||
# actually losing authority for real, should end a grab we initiated.
|
# actually losing authority for real, should end a grab we initiated.
|
||||||
if _pickable.is_picked_up() and _pickable.get_picked_up_by() is XRToolsFunctionPickup:
|
if _pickable.is_picked_up() and _pickable.get_picked_up_by() is XRToolsFunctionPickup:
|
||||||
if NetworkManager.is_online():
|
# Log once per grab, not once per tick.
|
||||||
|
if NetworkManager.is_online() and not _grab_race_logged:
|
||||||
|
_grab_race_logged = true
|
||||||
print(
|
print(
|
||||||
"%s: ignoring non-authority sync (net_held_by=%d) — still actively held by our own hand (grab-race guard)" % [
|
"%s: ignoring non-authority sync (net_held_by=%d) — still actively held by our own hand (grab-race guard)" % [
|
||||||
_pickable.name, net_held_by
|
_pickable.name, net_held_by
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
_grab_race_logged = false
|
||||||
# Someone else owns it: stop simulating locally, just follow the sync.
|
# Someone else owns it: stop simulating locally, just follow the sync.
|
||||||
if _pickable.is_picked_up():
|
if _pickable.is_picked_up():
|
||||||
if NetworkManager.is_online():
|
if NetworkManager.is_online():
|
||||||
@@ -117,12 +135,24 @@ func apply_held_state() -> void:
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
_pickable.drop()
|
_pickable.drop()
|
||||||
|
# Bail out when we're already in the follow-the-sync state. Without this the
|
||||||
|
# writes below (and the line logged with them) repeated every tick for every
|
||||||
|
# item on every non-authority peer — 90% of the log, plus four redundant
|
||||||
|
# physics-property writes per item per tick. The comparison also means we
|
||||||
|
# still re-apply if something else perturbs the state (e.g. let_go()
|
||||||
|
# restoring the collision mask after a force-drop).
|
||||||
|
var want_enabled := (net_held_by == 0)
|
||||||
|
if _pickable.freeze \
|
||||||
|
and _pickable.freeze_mode == RigidBody3D.FREEZE_MODE_KINEMATIC \
|
||||||
|
and _pickable.collision_mask == 0 \
|
||||||
|
and _pickable.enabled == want_enabled:
|
||||||
|
return
|
||||||
if NetworkManager.is_online():
|
if NetworkManager.is_online():
|
||||||
print("%s: freezing (non-authority, owner=peer %d)" % [_pickable.name, net_held_by])
|
print("%s: freezing (non-authority, owner=peer %d)" % [_pickable.name, net_held_by])
|
||||||
_pickable.freeze = true
|
_pickable.freeze = true
|
||||||
_pickable.freeze_mode = RigidBody3D.FREEZE_MODE_KINEMATIC
|
_pickable.freeze_mode = RigidBody3D.FREEZE_MODE_KINEMATIC
|
||||||
_pickable.collision_mask = 0
|
_pickable.collision_mask = 0
|
||||||
_pickable.enabled = (net_held_by == 0)
|
_pickable.enabled = want_enabled
|
||||||
|
|
||||||
|
|
||||||
## Local hand grab (not a station snap zone, which is server-only): request
|
## Local hand grab (not a station snap zone, which is server-only): request
|
||||||
|
|||||||
+35
-35
@@ -41,7 +41,7 @@ script = ExtResource("1_72gy5")
|
|||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.36171648, 0, 0.81835127)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.36171648, 0, 0.81835127)
|
||||||
|
|
||||||
[node name="MainMenuPanel3D" parent="." unique_id=1448860532 instance=ExtResource("16_vp")]
|
[node name="MainMenuPanel3D" parent="." unique_id=1448860532 instance=ExtResource("16_vp")]
|
||||||
transform = Transform3D(5, 0, 0, 0, 5, 0, 0, 0, 5, 0.36171648, 2.543398, -1.9758987)
|
transform = Transform3D(5, 0, 0, 0, 5, 0, 0, 0, 5, 0.36171648, 2.0238447, -1.9758987)
|
||||||
screen_size = Vector2(0.6, 0.4)
|
screen_size = Vector2(0.6, 0.4)
|
||||||
scene = ExtResource("17_panel")
|
scene = ExtResource("17_panel")
|
||||||
viewport_size = Vector2(600, 400)
|
viewport_size = Vector2(600, 400)
|
||||||
@@ -61,106 +61,106 @@ shape = SubResource("BoxShape3D_vlqg6")
|
|||||||
mesh = SubResource("BoxMesh_24d3s")
|
mesh = SubResource("BoxMesh_24d3s")
|
||||||
|
|
||||||
[node name="Hob" parent="." unique_id=1687971542 instance=ExtResource("4_5kvh0")]
|
[node name="Hob" parent="." unique_id=1687971542 instance=ExtResource("4_5kvh0")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.29336345, 0.8981018, -1.484)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.29336345, 0.5, -1.484)
|
||||||
|
|
||||||
[node name="WorldEnvironment" type="WorldEnvironment" parent="." unique_id=1177077250]
|
[node name="WorldEnvironment" type="WorldEnvironment" parent="." unique_id=1177077250]
|
||||||
environment = SubResource("Environment_bvwq1")
|
environment = SubResource("Environment_bvwq1")
|
||||||
|
|
||||||
[node name="BurgerBuns" parent="." unique_id=1088240294 instance=ExtResource("6_bktvt")]
|
[node name="BurgerBuns" parent="." unique_id=1088240294 instance=ExtResource("6_bktvt")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.8162017, 1.6081157, -1.4714175)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.5530176, 1.0505146, -1.3074328)
|
||||||
|
|
||||||
[node name="BurgerBunsDispenser" parent="." unique_id=1720683779 instance=ExtResource("7_1nkd0")]
|
[node name="BurgerBunsDispenser" parent="." unique_id=1720683779 instance=ExtResource("7_1nkd0")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.832131, 0.40028095, -1.4813508)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.568947, -0.018512607, -1.317366)
|
||||||
|
|
||||||
[node name="Plate" parent="." unique_id=190487773 instance=ExtResource("8_l1owj")]
|
[node name="Plate" parent="." unique_id=190487773 instance=ExtResource("8_l1owj")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.5717233, 1.5454081, 1.5373346)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.5717233, 1.1548845, 1.5373346)
|
||||||
|
|
||||||
[node name="burger" parent="." unique_id=1417604760 instance=ExtResource("9_220hi")]
|
[node name="burger" parent="." unique_id=1417604760 instance=ExtResource("9_220hi")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.6888188, 1.4195822, -1.7110313)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.6888188, 1.0325116, -1.7110313)
|
||||||
|
|
||||||
[node name="burger2" parent="." unique_id=1584460510 instance=ExtResource("9_220hi")]
|
[node name="burger2" parent="." unique_id=1584460510 instance=ExtResource("9_220hi")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.6931299, 1.5300478, -1.71225)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.6931299, 1.1429771, -1.71225)
|
||||||
|
|
||||||
[node name="burger3" parent="." unique_id=1884095916 instance=ExtResource("9_220hi")]
|
[node name="burger3" parent="." unique_id=1884095916 instance=ExtResource("9_220hi")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.69027674, 1.4969791, -1.7210286)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.69027674, 1.1099085, -1.7210286)
|
||||||
|
|
||||||
[node name="burger4" parent="." unique_id=1844818864 instance=ExtResource("9_220hi")]
|
[node name="burger4" parent="." unique_id=1844818864 instance=ExtResource("9_220hi")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.69134104, 1.4543622, -1.7210286)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.69134104, 1.0672915, -1.7210286)
|
||||||
|
|
||||||
[node name="Hamburger" parent="." unique_id=761091445 instance=ExtResource("10_qacki")]
|
[node name="Hamburger" parent="." unique_id=761091445 instance=ExtResource("10_qacki")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.9352558, 1.623975, 1.2447833)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.9352558, 1.2334514, 1.2447833)
|
||||||
|
|
||||||
[node name="PickableObject" parent="." unique_id=1675596942 instance=ExtResource("11_npf8s")]
|
[node name="PickableObject" parent="." unique_id=1675596942 instance=ExtResource("11_npf8s")]
|
||||||
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, 0, -2.6077032e-08, 1.4901161e-08, 1, 0.6225724, 1.4792972, -1.0473135)
|
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, 0, -2.6077032e-08, 1.4901161e-08, 1, 0.6225724, 1.0654817, -1.0473135)
|
||||||
|
|
||||||
[node name="PickableObject2" parent="." unique_id=713087634 instance=ExtResource("11_npf8s")]
|
[node name="PickableObject2" parent="." unique_id=713087634 instance=ExtResource("11_npf8s")]
|
||||||
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, -5.293956e-23, -2.6077032e-08, 1.4901161e-08, 1, 0.6359743, 1.4639391, -1.1673055)
|
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, -5.293956e-23, -2.6077032e-08, 1.4901161e-08, 1, 0.6359743, 1.0501236, -1.1673055)
|
||||||
|
|
||||||
[node name="PickableObject3" parent="." unique_id=879935619 instance=ExtResource("11_npf8s")]
|
[node name="PickableObject3" parent="." unique_id=879935619 instance=ExtResource("11_npf8s")]
|
||||||
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, 0, -2.6077032e-08, 1.4901161e-08, 1, 0.5142721, 1.4792972, -1.0473135)
|
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, 0, -2.6077032e-08, 1.4901161e-08, 1, 0.5142721, 1.0654817, -1.0473135)
|
||||||
|
|
||||||
[node name="PickableObject4" parent="." unique_id=198541902 instance=ExtResource("11_npf8s")]
|
[node name="PickableObject4" parent="." unique_id=198541902 instance=ExtResource("11_npf8s")]
|
||||||
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, -5.293956e-23, -2.6077032e-08, 1.4901161e-08, 1, 0.5276739, 1.4639391, -1.1673055)
|
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, -5.293956e-23, -2.6077032e-08, 1.4901161e-08, 1, 0.5276739, 1.0501236, -1.1673055)
|
||||||
|
|
||||||
[node name="Hamburger2" parent="." unique_id=1349934579 instance=ExtResource("10_qacki")]
|
[node name="Hamburger2" parent="." unique_id=1349934579 instance=ExtResource("10_qacki")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.9857153, 1.5329368, 0.9472374)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.9857153, 1.1424131, 0.9472374)
|
||||||
|
|
||||||
[node name="PickableObject5" parent="." unique_id=1021333509 instance=ExtResource("11_npf8s")]
|
[node name="PickableObject5" parent="." unique_id=1021333509 instance=ExtResource("11_npf8s")]
|
||||||
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, 0, -2.6077032e-08, 1.4901161e-08, 1, 0.73287535, 1.4792972, -1.0473135)
|
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, 0, -2.6077032e-08, 1.4901161e-08, 1, 0.73287535, 1.0922265, -1.0473135)
|
||||||
|
|
||||||
[node name="PickableObject6" parent="." unique_id=1268843669 instance=ExtResource("11_npf8s")]
|
[node name="PickableObject6" parent="." unique_id=1268843669 instance=ExtResource("11_npf8s")]
|
||||||
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, -5.293956e-23, -2.6077032e-08, 1.4901161e-08, 1, 0.7462772, 1.4639391, -1.1673055)
|
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, -5.293956e-23, -2.6077032e-08, 1.4901161e-08, 1, 0.7462772, 1.0768684, -1.1673055)
|
||||||
|
|
||||||
[node name="Sink" parent="." unique_id=2055277359 instance=ExtResource("12_8apyq")]
|
[node name="Sink" parent="." unique_id=2055277359 instance=ExtResource("12_8apyq")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.3147688, 0.9045367, -1.4940417)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.3147688, 0.5, -1.4940417)
|
||||||
|
|
||||||
[node name="DirtStation" parent="." unique_id=160842153 instance=ExtResource("13_jnwcx")]
|
[node name="DirtStation" parent="." unique_id=160842153 instance=ExtResource("13_jnwcx")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.0864775, 0.8981018, -1.244947)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.0864775, 0.5, -1.244947)
|
||||||
|
|
||||||
[node name="Plate2" parent="." unique_id=356790445 instance=ExtResource("8_l1owj")]
|
[node name="Plate2" parent="." unique_id=356790445 instance=ExtResource("8_l1owj")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.5730225, 1.499024, 0.59790254)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.5730225, 1.1085004, 0.59790254)
|
||||||
|
|
||||||
[node name="CookedBurger" parent="." unique_id=1127807542 instance=ExtResource("14_1lg2m")]
|
[node name="CookedBurger" parent="." unique_id=1127807542 instance=ExtResource("14_1lg2m")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.30671906, 1.4131018, -1.0928738)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.30782473, 1.1446649, -1.0928738)
|
||||||
|
|
||||||
[node name="CookedBurger2" parent="." unique_id=160088590 instance=ExtResource("14_1lg2m")]
|
[node name="CookedBurger2" parent="." unique_id=160088590 instance=ExtResource("14_1lg2m")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.30671906, 1.4496142, -1.0928738)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.30671906, 1.0522771, -1.0928738)
|
||||||
|
|
||||||
[node name="CookedBurger3" parent="." unique_id=992183367 instance=ExtResource("14_1lg2m")]
|
[node name="CookedBurger3" parent="." unique_id=992183367 instance=ExtResource("14_1lg2m")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.3344773, 1.4398065, -0.7096845)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.3344773, 1.0492828, -0.7096845)
|
||||||
|
|
||||||
[node name="CookedBurger4" parent="." unique_id=1837607086 instance=ExtResource("14_1lg2m")]
|
[node name="CookedBurger4" parent="." unique_id=1837607086 instance=ExtResource("14_1lg2m")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.30671906, 1.525444, -1.0928738)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.30671906, 1.098119, -1.0928738)
|
||||||
|
|
||||||
[node name="BurgerBuns2" parent="." unique_id=1210358958 instance=ExtResource("6_bktvt")]
|
[node name="BurgerBuns2" parent="." unique_id=1210358958 instance=ExtResource("6_bktvt")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.3596323, 1.4110342, -0.22482127)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.3596323, 1.0205106, -0.22482127)
|
||||||
|
|
||||||
[node name="PickableObject7" parent="." unique_id=641653545 instance=ExtResource("11_npf8s")]
|
[node name="PickableObject7" parent="." unique_id=641653545 instance=ExtResource("11_npf8s")]
|
||||||
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, 0, -2.6077032e-08, 1.4901161e-08, 1, -1.3556751, 1.4792972, 0.28881657)
|
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, 0, -2.6077032e-08, 1.4901161e-08, 1, -1.3556751, 1.0887735, 0.28881657)
|
||||||
|
|
||||||
[node name="PickableObject8" parent="." unique_id=1733819363 instance=ExtResource("11_npf8s")]
|
[node name="PickableObject8" parent="." unique_id=1733819363 instance=ExtResource("11_npf8s")]
|
||||||
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, -5.293956e-23, -2.6077032e-08, 1.4901161e-08, 1, -1.3422732, 1.4639391, 0.16882455)
|
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, -5.293956e-23, -2.6077032e-08, 1.4901161e-08, 1, -1.3422732, 1.0734154, 0.16882455)
|
||||||
|
|
||||||
[node name="PickableObject9" parent="." unique_id=12419746 instance=ExtResource("11_npf8s")]
|
[node name="PickableObject9" parent="." unique_id=12419746 instance=ExtResource("11_npf8s")]
|
||||||
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, 0, -2.6077032e-08, 1.4901161e-08, 1, -1.4639754, 1.4792972, 0.28881657)
|
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, 0, -2.6077032e-08, 1.4901161e-08, 1, -1.4639754, 1.0887735, 0.28881657)
|
||||||
|
|
||||||
[node name="PickableObject10" parent="." unique_id=313160217 instance=ExtResource("11_npf8s")]
|
[node name="PickableObject10" parent="." unique_id=313160217 instance=ExtResource("11_npf8s")]
|
||||||
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, -5.293956e-23, -2.6077032e-08, 1.4901161e-08, 1, -1.4505737, 1.4639391, 0.16882455)
|
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, -5.293956e-23, -2.6077032e-08, 1.4901161e-08, 1, -1.4505737, 1.0734154, 0.16882455)
|
||||||
|
|
||||||
[node name="Counter" parent="." unique_id=1487893288 instance=ExtResource("15_yy81s")]
|
[node name="Counter" parent="." unique_id=1487893288 instance=ExtResource("15_yy81s")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.7124918, 0.90304357, -1.4886917)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.7124918, 0.5, -1.4886917)
|
||||||
|
|
||||||
[node name="Counter2" parent="." unique_id=368890752 instance=ExtResource("15_yy81s")]
|
[node name="Counter2" parent="." unique_id=368890752 instance=ExtResource("15_yy81s")]
|
||||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -1.7648025, 0.90304357, -0.4458799)
|
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -1.7648025, 0.5, -0.4458799)
|
||||||
|
|
||||||
[node name="Counter3" parent="." unique_id=1498817646 instance=ExtResource("15_yy81s")]
|
[node name="Counter3" parent="." unique_id=1498817646 instance=ExtResource("15_yy81s")]
|
||||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -1.7648025, 0.90304357, 0.55367994)
|
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -1.7648025, 0.5, 0.55367994)
|
||||||
|
|
||||||
[node name="Counter4" parent="." unique_id=906748771 instance=ExtResource("15_yy81s")]
|
[node name="Counter4" parent="." unique_id=906748771 instance=ExtResource("15_yy81s")]
|
||||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -1.7648025, 0.90304357, 1.5539298)
|
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -1.7648025, 0.5, 1.5539298)
|
||||||
|
|
||||||
[node name="Hamburger3" parent="." unique_id=369573848 instance=ExtResource("10_qacki")]
|
[node name="Hamburger3" parent="." unique_id=369573848 instance=ExtResource("10_qacki")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.7201865, 1.5329367, 0.14773655)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.7201865, 1.142413, 0.14773655)
|
||||||
|
|
||||||
[node name="Plate3" parent="." unique_id=2004250522 instance=ExtResource("8_l1owj")]
|
[node name="Plate3" parent="." unique_id=2004250522 instance=ExtResource("8_l1owj")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.5818124, 1.499024, -0.4634577)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.5818124, 1.1085004, -0.4634577)
|
||||||
|
|||||||
@@ -9,30 +9,41 @@
|
|||||||
"stages": [
|
"stages": [
|
||||||
{
|
{
|
||||||
"uuid": "94333de9-ebf5-4f38-b95b-9477f4bb9dde",
|
"uuid": "94333de9-ebf5-4f38-b95b-9477f4bb9dde",
|
||||||
"title": "Todo",
|
"title": "Backlog",
|
||||||
"tasks": [
|
"tasks": [
|
||||||
"854c7e1e-7521-4bcd-83ff-bb3fecec3142",
|
"854c7e1e-7521-4bcd-83ff-bb3fecec3142",
|
||||||
"4e2ca8d1-84e1-4913-ad8f-834007d52160",
|
"4e2ca8d1-84e1-4913-ad8f-834007d52160",
|
||||||
"dd822d14-88e5-4790-808e-7d2cf7f79133",
|
"dd822d14-88e5-4790-808e-7d2cf7f79133",
|
||||||
"784d5cd3-333a-40a9-b35f-3d0c73f00761"
|
"784d5cd3-333a-40a9-b35f-3d0c73f00761",
|
||||||
|
"21bc19f1-ee3c-4bbb-ab9a-07ec6123a823",
|
||||||
|
"d93a31cd-d475-4c5a-87fc-983174b2594f"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"uuid": "211e4050-31cd-4425-a2ba-8ca56b4764cd",
|
"uuid": "211e4050-31cd-4425-a2ba-8ca56b4764cd",
|
||||||
"title": "Doing",
|
"title": "Todo",
|
||||||
"tasks": [
|
"tasks": [
|
||||||
"d9cebd68-792e-4d01-a916-7df5f128b4a8",
|
"55ac73b8-4378-4b58-bf3c-33f64590c804"
|
||||||
"1c3b9543-cb67-431d-bf71-c8753e816776"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"uuid": "fab8a81b-7ca9-4026-96bb-ed66ea58ef2e",
|
"uuid": "fab8a81b-7ca9-4026-96bb-ed66ea58ef2e",
|
||||||
|
"title": "Doing",
|
||||||
|
"tasks": [
|
||||||
|
"f8073eb2-8786-47b7-b63f-fa70c3f7115a",
|
||||||
|
"6942c45a-53a4-484f-9885-4f249cb4572b"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uuid": "0d743057-955a-473a-8123-a3f805505d5d",
|
||||||
"title": "Done",
|
"title": "Done",
|
||||||
"tasks": [
|
"tasks": [
|
||||||
"4ab36c89-a377-4f25-86a1-ac276aadf4a2",
|
"a4dd35ae-dc8e-42a3-9044-bf1cb1af281a",
|
||||||
"cde44fb1-3fdf-4ea0-8267-d28d894e4e7d",
|
"1c3b9543-cb67-431d-bf71-c8753e816776",
|
||||||
|
"d9cebd68-792e-4d01-a916-7df5f128b4a8",
|
||||||
"eee1990e-f957-4499-84b6-e68003fcb78e",
|
"eee1990e-f957-4499-84b6-e68003fcb78e",
|
||||||
"a4dd35ae-dc8e-42a3-9044-bf1cb1af281a"
|
"4ab36c89-a377-4f25-86a1-ac276aadf4a2",
|
||||||
|
"cde44fb1-3fdf-4ea0-8267-d28d894e4e7d"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -196,6 +207,41 @@
|
|||||||
"done": false
|
"done": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uuid": "21bc19f1-ee3c-4bbb-ab9a-07ec6123a823",
|
||||||
|
"title": "Visual indicator Hob",
|
||||||
|
"description": "",
|
||||||
|
"category": "bf4ec62e-526e-4027-876f-0b22bd17f79a",
|
||||||
|
"steps": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uuid": "d93a31cd-d475-4c5a-87fc-983174b2594f",
|
||||||
|
"title": "Visual indicator Sink",
|
||||||
|
"description": "",
|
||||||
|
"category": "bf4ec62e-526e-4027-876f-0b22bd17f79a",
|
||||||
|
"steps": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uuid": "6942c45a-53a4-484f-9885-4f249cb4572b",
|
||||||
|
"title": "Stations progress bar",
|
||||||
|
"description": "",
|
||||||
|
"category": "bf4ec62e-526e-4027-876f-0b22bd17f79a",
|
||||||
|
"steps": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uuid": "f8073eb2-8786-47b7-b63f-fa70c3f7115a",
|
||||||
|
"title": "Game over screen / effect with restart button",
|
||||||
|
"description": "",
|
||||||
|
"category": "bf4ec62e-526e-4027-876f-0b22bd17f79a",
|
||||||
|
"steps": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uuid": "55ac73b8-4378-4b58-bf3c-33f64590c804",
|
||||||
|
"title": "Restart / game reset button",
|
||||||
|
"description": "",
|
||||||
|
"category": "bf4ec62e-526e-4027-876f-0b22bd17f79a",
|
||||||
|
"steps": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"layout": {
|
"layout": {
|
||||||
@@ -208,6 +254,9 @@
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
"fab8a81b-7ca9-4026-96bb-ed66ea58ef2e"
|
"fab8a81b-7ca9-4026-96bb-ed66ea58ef2e"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"0d743057-955a-473a-8123-a3f805505d5d"
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
uid://dqqu56yetl8ok
|
|
||||||
+139
-1
@@ -254,7 +254,11 @@ func _cook_and_plate_round(actor: String, burger: String, buns: String, plate: S
|
|||||||
# Take the finished plate away again, the way a player would carry it off to
|
# Take the finished plate away again, the way a player would carry it off to
|
||||||
# be served. Without this the counter stays occupied and the next round has
|
# be served. Without this the counter stays occupied and the next round has
|
||||||
# nowhere to put its plate.
|
# nowhere to put its plate.
|
||||||
|
await _both("%s_food_on_plate_before_lift" % actor, "verify_plate_visuals", [plate])
|
||||||
await _step(actor, "%s_plate_off_counter2" % actor, "park", [plate, park_at])
|
await _step(actor, "%s_plate_off_counter2" % actor, "park", [plate, park_at])
|
||||||
|
# The food must still be on the plate after it has been carried off the
|
||||||
|
# counter and set down again.
|
||||||
|
await _both("%s_food_stayed_on_plate" % actor, "verify_plate_visuals", [plate])
|
||||||
|
|
||||||
|
|
||||||
# Spawn a second set of ingredients through NetworkManager for the server's
|
# Spawn a second set of ingredients through NetworkManager for the server's
|
||||||
@@ -292,6 +296,7 @@ func _find_client_id() -> int:
|
|||||||
# Run one step as `actor` (locally if that's us, over RPC if it's the client)
|
# Run one step as `actor` (locally if that's us, over RPC if it's the client)
|
||||||
# and record the verdict.
|
# and record the verdict.
|
||||||
func _step(actor: String, label: String, step: String, args: Array) -> void:
|
func _step(actor: String, label: String, step: String, args: Array) -> void:
|
||||||
|
_current_step = label
|
||||||
_banner("STEP %d: %s (on the %s)" % [_next_step_no(), label, actor.to_upper()])
|
_banner("STEP %d: %s (on the %s)" % [_next_step_no(), label, actor.to_upper()])
|
||||||
var res: Dictionary
|
var res: Dictionary
|
||||||
if actor == "server":
|
if actor == "server":
|
||||||
@@ -307,6 +312,7 @@ func _step(actor: String, label: String, step: String, args: Array) -> void:
|
|||||||
# Run the same check on both peers - the server's state and the client's must
|
# Run the same check on both peers - the server's state and the client's must
|
||||||
# agree, which is the whole point of the exercise.
|
# agree, which is the whole point of the exercise.
|
||||||
func _both(label: String, step: String, args: Array) -> void:
|
func _both(label: String, step: String, args: Array) -> void:
|
||||||
|
_current_step = label
|
||||||
_banner("STEP %d: %s (checked on BOTH peers)" % [_next_step_no(), label])
|
_banner("STEP %d: %s (checked on BOTH peers)" % [_next_step_no(), label])
|
||||||
_record(label, "server", await _run_local_step(step, args))
|
_record(label, "server", await _run_local_step(step, args))
|
||||||
_record(label, "client", await _remote(step, args))
|
_record(label, "client", await _remote(step, args))
|
||||||
@@ -372,6 +378,7 @@ func _report() -> void:
|
|||||||
|
|
||||||
@rpc("authority", "reliable")
|
@rpc("authority", "reliable")
|
||||||
func _cmd(step: String, args: Array) -> void:
|
func _cmd(step: String, args: Array) -> void:
|
||||||
|
_current_step = step
|
||||||
_log("<- server: %s%s" % [step, args])
|
_log("<- server: %s%s" % [step, args])
|
||||||
_running = true
|
_running = true
|
||||||
var res := await _run_local_step(step, args)
|
var res := await _run_local_step(step, args)
|
||||||
@@ -424,6 +431,8 @@ func _run_local_step(step: String, args: Array) -> Dictionary:
|
|||||||
return _check_food_exists(args[0])
|
return _check_food_exists(args[0])
|
||||||
"verify_plate_contains":
|
"verify_plate_contains":
|
||||||
return _check_plate_contains(args[0], args[1])
|
return _check_plate_contains(args[0], args[1])
|
||||||
|
"verify_plate_visuals":
|
||||||
|
return _check_plate_visuals(args[0])
|
||||||
return {"ok": false, "detail": "unknown step %s" % step}
|
return {"ok": false, "detail": "unknown step %s" % step}
|
||||||
|
|
||||||
|
|
||||||
@@ -800,9 +809,58 @@ func _describe(item: Node3D) -> Dictionary:
|
|||||||
# literally what the player sees sitting on the plate.
|
# literally what the player sees sitting on the plate.
|
||||||
d["meals_shown"] = _visual_count(item, "Container/MealContainer")
|
d["meals_shown"] = _visual_count(item, "Container/MealContainer")
|
||||||
d["sides_shown"] = _visual_count(item, "Container/SidesContainer")
|
d["sides_shown"] = _visual_count(item, "Container/SidesContainer")
|
||||||
|
# Food sitting on a plate is a cosmetic child of the plate, so it must
|
||||||
|
# travel with it. Keys starting with "_" are per-peer diagnostics that
|
||||||
|
# _compare() skips (floats won't match exactly across peers); the
|
||||||
|
# attached flag is asserted absolutely instead, because this can — and
|
||||||
|
# did — go wrong on both peers at once, which a diff would miss.
|
||||||
|
var off := _max_visual_offset(item)
|
||||||
|
d["_visual_offset"] = off
|
||||||
|
d["visuals_attached"] = off <= MAX_VISUAL_OFFSET
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
## How far a cosmetic item on a plate may sit from the plate's origin. The plate
|
||||||
|
## is ~0.4m across and the furthest slot is ~0.12m out, so anything past this has
|
||||||
|
## come off the plate.
|
||||||
|
const MAX_VISUAL_OFFSET := 0.3
|
||||||
|
|
||||||
|
|
||||||
|
# Largest distance from the plate's origin to any of the cosmetic items it is
|
||||||
|
# displaying. -1 when the plate is showing nothing.
|
||||||
|
func _max_visual_offset(item: Node3D) -> float:
|
||||||
|
var worst := -1.0
|
||||||
|
for path in ["Container/MealContainer", "Container/SidesContainer"]:
|
||||||
|
var root := item.get_node_or_null(path)
|
||||||
|
if not root:
|
||||||
|
continue
|
||||||
|
for c in root.get_children():
|
||||||
|
if c is Node3D and not c.is_queued_for_deletion():
|
||||||
|
worst = maxf(worst, item.global_position.distance_to((c as Node3D).global_position))
|
||||||
|
return snappedf(worst, 0.001)
|
||||||
|
|
||||||
|
|
||||||
|
# What the cosmetic children look like, for diagnosing why they moved.
|
||||||
|
func _visual_diag(item: Node3D) -> String:
|
||||||
|
var parts: Array[String] = []
|
||||||
|
for path in ["Container/MealContainer", "Container/SidesContainer"]:
|
||||||
|
var root := item.get_node_or_null(path)
|
||||||
|
if not root:
|
||||||
|
continue
|
||||||
|
for c in root.get_children():
|
||||||
|
var s := "%s global=%s local=%s parent=%s (%.3fm from plate at %s)" % [
|
||||||
|
c.name, (c as Node3D).global_position, (c as Node3D).position,
|
||||||
|
c.get_parent().name,
|
||||||
|
item.global_position.distance_to((c as Node3D).global_position),
|
||||||
|
item.global_position]
|
||||||
|
if c is RigidBody3D:
|
||||||
|
s += " [RigidBody3D freeze=%s freeze_mode=%d layer=%d top_level=%s queued=%s]" % [
|
||||||
|
c.freeze, c.freeze_mode, c.collision_layer, c.top_level,
|
||||||
|
c.is_queued_for_deletion()]
|
||||||
|
parts.append(s)
|
||||||
|
return "; ".join(parts) if parts else "(nothing on the plate)"
|
||||||
|
|
||||||
|
|
||||||
func _visual_count(item: Node3D, path: String) -> int:
|
func _visual_count(item: Node3D, path: String) -> int:
|
||||||
var n := item.get_node_or_null(path)
|
var n := item.get_node_or_null(path)
|
||||||
if not n:
|
if not n:
|
||||||
@@ -864,10 +922,20 @@ func _compare(server: Dictionary, client: Dictionary) -> Array[String]:
|
|||||||
if dist > SYNC_POS_TOLERANCE:
|
if dist > SYNC_POS_TOLERANCE:
|
||||||
problems.append("%s is %.3fm apart (server %s vs client %s)" % [name, dist, s["pos"], c["pos"]])
|
problems.append("%s is %.3fm apart (server %s vs client %s)" % [name, dist, s["pos"], c["pos"]])
|
||||||
for key in s:
|
for key in s:
|
||||||
if key == "pos":
|
# "_" keys are per-peer diagnostics, not things that must match.
|
||||||
|
if key == "pos" or key.begins_with("_"):
|
||||||
continue
|
continue
|
||||||
if s[key] != c[key]:
|
if s[key] != c[key]:
|
||||||
problems.append("%s.%s: server=%s client=%s" % [name, key, s[key], c[key]])
|
problems.append("%s.%s: server=%s client=%s" % [name, key, s[key], c[key]])
|
||||||
|
# Absolute invariants, checked per peer. A cross-peer diff can't catch a
|
||||||
|
# fault that happens identically on both sides.
|
||||||
|
for peer_name in ["server", "client"]:
|
||||||
|
var snap: Dictionary = server if peer_name == "server" else client
|
||||||
|
for name in snap:
|
||||||
|
var d: Dictionary = snap[name]
|
||||||
|
if d.has("visuals_attached") and not d["visuals_attached"]:
|
||||||
|
problems.append("on the %s, %s's food has come off the plate (%.3fm from it, limit %.2f)"
|
||||||
|
% [peer_name, name, d.get("_visual_offset", -1.0), MAX_VISUAL_OFFSET])
|
||||||
return problems
|
return problems
|
||||||
|
|
||||||
|
|
||||||
@@ -893,6 +961,76 @@ func _audit_sync(label: String) -> void:
|
|||||||
_record("sync_after_" + label, "both", res)
|
_record("sync_after_" + label, "both", res)
|
||||||
|
|
||||||
|
|
||||||
|
# --- Live watch on plate visuals ------------------------------------------
|
||||||
|
#
|
||||||
|
# The per-step checks tell us the food ended up off the plate, but not when or
|
||||||
|
# why. This watches every frame and reports the first frame a cosmetic item
|
||||||
|
# departs from its slot, along with what was happening to the plate at the time.
|
||||||
|
|
||||||
|
## Local offset from its slot at which a cosmetic item counts as having moved.
|
||||||
|
const VISUAL_DRIFT_EPSILON := 0.05
|
||||||
|
|
||||||
|
var _current_step := "(before any step)"
|
||||||
|
var _drift_reported := {}
|
||||||
|
|
||||||
|
|
||||||
|
func _process(_delta: float) -> void:
|
||||||
|
if not _world:
|
||||||
|
return
|
||||||
|
for root in [_world, _world.get_node_or_null("WorldContent")]:
|
||||||
|
if not root:
|
||||||
|
continue
|
||||||
|
for item in root.get_children():
|
||||||
|
if not (item is Node3D) or not item.get_node_or_null("PlateController"):
|
||||||
|
continue
|
||||||
|
_watch_plate(item as Node3D)
|
||||||
|
|
||||||
|
|
||||||
|
func _watch_plate(plate: Node3D) -> void:
|
||||||
|
for path in ["Container/MealContainer", "Container/SidesContainer"]:
|
||||||
|
var holder := plate.get_node_or_null(path)
|
||||||
|
if not holder:
|
||||||
|
continue
|
||||||
|
for c in holder.get_children():
|
||||||
|
if not (c is Node3D) or c.is_queued_for_deletion():
|
||||||
|
continue
|
||||||
|
var drift: float = (c as Node3D).position.length()
|
||||||
|
var key := c.get_instance_id()
|
||||||
|
if drift <= VISUAL_DRIFT_EPSILON:
|
||||||
|
_drift_reported.erase(key)
|
||||||
|
continue
|
||||||
|
if _drift_reported.has(key):
|
||||||
|
continue
|
||||||
|
_drift_reported[key] = true
|
||||||
|
_log("VISUAL DRIFT: %s on %s moved to local %s (%.3f from its slot) during '%s'" % [
|
||||||
|
c.name, plate.name, (c as Node3D).position, drift, _current_step])
|
||||||
|
_log(" plate: pos=%s freeze=%s held_by=%s authority=%d" % [
|
||||||
|
plate.global_position, plate.freeze if plate is RigidBody3D else "-",
|
||||||
|
plate.get_picked_up_by() if plate.has_method("get_picked_up_by") else "-",
|
||||||
|
plate.get_multiplayer_authority()])
|
||||||
|
if c is RigidBody3D:
|
||||||
|
_log(" visual: freeze=%s mode=%d layer=%d top_level=%s sleeping=%s lin_vel=%s" % [
|
||||||
|
c.freeze, c.freeze_mode, c.collision_layer, c.top_level,
|
||||||
|
c.sleeping, c.linear_velocity])
|
||||||
|
|
||||||
|
|
||||||
|
# Food shown on a plate is a cosmetic child of that plate, so it has to stay put
|
||||||
|
# when the plate is picked up and carried around. If it drifts away, the player
|
||||||
|
# sees the burger fly off the plate.
|
||||||
|
func _check_plate_visuals(plate_name: String) -> Dictionary:
|
||||||
|
var plate := _find(plate_name)
|
||||||
|
if not plate:
|
||||||
|
return {"ok": false, "detail": "'%s' does not exist on this peer" % plate_name}
|
||||||
|
var off := _max_visual_offset(plate)
|
||||||
|
if off < 0.0:
|
||||||
|
return {"ok": false, "detail": "%s is not showing any food to check" % plate_name}
|
||||||
|
if off > MAX_VISUAL_OFFSET:
|
||||||
|
return {"ok": false, "detail": "%s's food has come off the plate: %.3fm away (limit %.2f). %s"
|
||||||
|
% [plate_name, off, MAX_VISUAL_OFFSET, _visual_diag(plate)]}
|
||||||
|
return {"ok": true, "detail": "%s's food is still on it (%.3fm from centre). %s"
|
||||||
|
% [plate_name, off, _visual_diag(plate)]}
|
||||||
|
|
||||||
|
|
||||||
# --- Diagnostics -----------------------------------------------------------
|
# --- Diagnostics -----------------------------------------------------------
|
||||||
|
|
||||||
func _diag(item: Node3D) -> String:
|
func _diag(item: Node3D) -> String:
|
||||||
|
|||||||
@@ -108,5 +108,5 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.40037438, 1.0094403, 0.341
|
|||||||
[node name="Counter" parent="." unique_id=1487893288 instance=ExtResource("12_j5uvh")]
|
[node name="Counter" parent="." unique_id=1487893288 instance=ExtResource("12_j5uvh")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1, 0.5, 0)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1, 0.5, 0)
|
||||||
|
|
||||||
[node name="Counter2" parent="." instance=ExtResource("12_j5uvh")]
|
[node name="Counter2" parent="." unique_id=860178119 instance=ExtResource("12_j5uvh")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, 0.5, 0)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, 0.5, 0)
|
||||||
|
|||||||
Reference in New Issue
Block a user