Added chatterbox connections
This commit is contained in:
1 parent
41ef12f954
commit
e58ceeb192
58 files changed
+8931
-8
No files matched your search
@@ -0,0 +1,143 @@
|
||||
extends Node
|
||||
# Explicit source-only experiment. Never connects to the production room controller.
|
||||
var server := TCPServer.new()
|
||||
var peers: Array[Dictionary] = []
|
||||
var game: Node
|
||||
var session := ""
|
||||
var phase := "powered_off"
|
||||
var scans := 0
|
||||
var last_credits := 0
|
||||
var seen: Dictionary = {}
|
||||
var rfid_device := "cab1_rfid1"
|
||||
var inject_rfid := false
|
||||
|
||||
func _ready() -> void:
|
||||
var args := OS.get_cmdline_user_args()
|
||||
if not "--glitch-lab" in args:
|
||||
push_error("This scene requires --glitch-lab")
|
||||
get_tree().quit(2)
|
||||
return
|
||||
inject_rfid = "--lab-inject-rfid" in args
|
||||
if inject_rfid and HardwareManager.real_gpio:
|
||||
push_error("Choose physical RFID or lab injection, never both")
|
||||
get_tree().quit(2)
|
||||
return
|
||||
for arg in args:
|
||||
if arg.begins_with("--lab-rfid-device="):
|
||||
rfid_device = arg.trim_prefix("--lab-rfid-device=")
|
||||
session = Crypto.new().generate_random_bytes(16).hex_encode()
|
||||
RoomRuntime.active_assignment = null
|
||||
HardwareManager.active_cabinet = null
|
||||
game = load("res://games/pacman/pacman.gd").new()
|
||||
# Set before _ready to prevent automatic boot.
|
||||
game.run_config = _config()
|
||||
add_child(game)
|
||||
game.game_started.connect(func(): phase = "playing")
|
||||
game.game_won.connect(func(): phase = "won")
|
||||
game.game_lost.connect(func(): phase = "lost")
|
||||
game.power_state_changed.connect(_power)
|
||||
reset()
|
||||
if server.listen(18768, "127.0.0.1") != OK:
|
||||
push_error("Godot lab port 18768 unavailable")
|
||||
get_tree().quit(2)
|
||||
return
|
||||
print("GLITCH GODOT LAB READY (no room controller or reward outputs)")
|
||||
|
||||
func _config() -> Dictionary:
|
||||
return {"power_on_device": "__lab_operator_only__", "rfid_credit_device": rfid_device,
|
||||
"score_to_win": 500, "boards_to_win": 0, "starting_lives": 3}
|
||||
|
||||
func _power(value: String) -> void:
|
||||
phase = {"off": "powered_off", "booting": "booting", "ready": "ready"}.get(value, value)
|
||||
|
||||
func reset() -> void:
|
||||
game.configure(_config())
|
||||
scans = 0
|
||||
last_credits = 0
|
||||
if inject_rfid: game.receive_device_feedback({"fresh": true, "events": [], "devices": []})
|
||||
|
||||
func feed_rfid(feedback: Dictionary) -> void:
|
||||
# Only RFID input is admitted. No button (including BCM23) can power this lab on.
|
||||
var filtered := feedback.duplicate(true)
|
||||
filtered["events"] = feedback.get("events", []).filter(func(e): return e.get("kind") == "rfid")
|
||||
game.receive_device_feedback(filtered)
|
||||
_sync_credit()
|
||||
|
||||
func _sync_credit() -> void:
|
||||
if game.credits > last_credits: scans += 1
|
||||
last_credits = game.credits
|
||||
|
||||
func snapshot() -> Dictionary:
|
||||
_sync_credit()
|
||||
return {"session": session, "cabinet_id": "cabinet_01", "phase": phase,
|
||||
"credits": game.credits, "scans": scans, "simulated": false,
|
||||
"rfid_injection": inject_rfid, "score": game.model.score}
|
||||
|
||||
func reply(ok: bool, duplicate: bool, message: String) -> Dictionary:
|
||||
return {"accepted": ok, "duplicate": duplicate, "message": message, "state": snapshot()}
|
||||
|
||||
func execute(cmd: Dictionary) -> Dictionary:
|
||||
var action := str(cmd.get("action", ""))
|
||||
if action == "snapshot": return reply(true, false, "Real Godot cabinet state")
|
||||
var id := str(cmd.get("id", ""))
|
||||
if id.is_empty() or id.length() > 128 or cmd.get("session") != session:
|
||||
return reply(false, false, "Invalid command ID or stale session")
|
||||
if seen.has(id):
|
||||
var previous: Dictionary = seen[id]
|
||||
if previous.command != cmd: return reply(false, false, "Command ID reused with different contents")
|
||||
return reply(previous.accepted, true, previous.message)
|
||||
var now := int(Time.get_unix_time_from_system() * 1000)
|
||||
var expires := int(cmd.get("expires_ms", 0))
|
||||
if expires <= now or expires - now > 30000: return reply(false, false, "Expired command")
|
||||
var ok := false
|
||||
match action:
|
||||
"power_on":
|
||||
if phase == "powered_off": ok = game.power_on()
|
||||
"start":
|
||||
if phase == "ready": ok = game.start_game()
|
||||
"scan":
|
||||
if inject_rfid and phase == "ready" and game.credits == 0:
|
||||
# Synthetic fixture passes through the real admission logic, never sets credits.
|
||||
game.receive_device_feedback({"fresh": true, "events": [], "devices": []})
|
||||
feed_rfid({"fresh": true,
|
||||
"events": [{"id": id, "kind": "rfid", "device": rfid_device, "value": "LAB-CARD", "age_ms": 0}],
|
||||
"devices": [{"id": rfid_device, "kind": "rfid", "value": "LAB-CARD", "age_ms": 0}]})
|
||||
ok = game.credits == 1
|
||||
"reset":
|
||||
reset()
|
||||
ok = true
|
||||
var message := "Executed by Godot" if ok else "Action unavailable; scan a physical card or play to the score target"
|
||||
seen[id] = {"command": cmd.duplicate(true), "accepted": ok, "message": message}
|
||||
if seen.size() > 256: seen.erase(seen.keys()[0])
|
||||
return reply(ok, false, message)
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if game == null: return
|
||||
if HardwareManager.real_gpio and HardwareManager.client != null:
|
||||
feed_rfid(HardwareManager.client.get_feedback())
|
||||
while server.is_connection_available():
|
||||
var peer := server.take_connection()
|
||||
if peers.size() >= 8:
|
||||
peer.disconnect_from_host()
|
||||
else:
|
||||
peers.append({"peer": peer, "data": "", "deadline": Time.get_ticks_msec() + 1800})
|
||||
for item in peers.duplicate():
|
||||
var peer: StreamPeerTCP = item.peer
|
||||
peer.poll()
|
||||
var available := peer.get_available_bytes()
|
||||
if available > 0:
|
||||
var packet := peer.get_data(mini(available, 8193))
|
||||
if packet[0] == OK: item.data += packet[1].get_string_from_utf8()
|
||||
if item.data.length() > 8192 or Time.get_ticks_msec() > item.deadline or peer.get_status() != StreamPeerTCP.STATUS_CONNECTED:
|
||||
peer.disconnect_from_host()
|
||||
peers.erase(item)
|
||||
elif "\n" in item.data:
|
||||
var cmd = JSON.parse_string(item.data.get_slice("\n", 0))
|
||||
var result := execute(cmd) if cmd is Dictionary else reply(false, false, "Invalid JSON command")
|
||||
peer.put_data((JSON.stringify(result) + "\n").to_utf8_buffer())
|
||||
peer.disconnect_from_host()
|
||||
peers.erase(item)
|
||||
|
||||
func _exit_tree() -> void:
|
||||
server.stop()
|
||||
for item in peers: item.peer.disconnect_from_host()
|
||||
@@ -0,0 +1 @@
|
||||
uid://bajlwadu5w4or
|
||||
@@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://tests/odin_lab/cabinet.gd" id="1"]
|
||||
|
||||
[node name="GlitchLabCabinet" type="Node"]
|
||||
script = ExtResource("1")
|
||||
@@ -0,0 +1,32 @@
|
||||
extends SceneTree
|
||||
var failures := 0
|
||||
func check(ok: bool, message: String) -> void:
|
||||
if not ok:
|
||||
failures += 1
|
||||
push_error(message)
|
||||
func _initialize() -> void: call_deferred("run")
|
||||
func run() -> void:
|
||||
var adapter = load("res://tests/odin_lab/cabinet.gd").new()
|
||||
root.add_child(adapter)
|
||||
adapter.set_process(false)
|
||||
adapter.game.set_physics_process(false)
|
||||
var now := int(Time.get_unix_time_from_system() * 1000)
|
||||
var cmd := {"id": "power", "session": adapter.session, "expires_ms": now + 10000, "action": "power_on"}
|
||||
check(adapter.execute(cmd).accepted, "Power on executes")
|
||||
check(adapter.execute(cmd).duplicate, "Duplicate is acknowledged")
|
||||
adapter.game._physics_process(4.0)
|
||||
cmd = {"id": "scan", "session": adapter.session, "expires_ms": now + 10000, "action": "scan"}
|
||||
check(adapter.execute(cmd).accepted and adapter.game.credits == 1, "Fixture goes through real RFID admission")
|
||||
check(adapter.execute(cmd).duplicate and adapter.scans == 1, "Duplicate scan does not grant another credit")
|
||||
cmd = {"id": "start", "session": adapter.session, "expires_ms": now + 10000, "action": "start"}
|
||||
check(adapter.execute(cmd).accepted and adapter.phase == "playing", "Real game starts")
|
||||
adapter.game.model._add_score(500)
|
||||
check(adapter.phase == "won" and adapter.snapshot().score == 500, "Normal score objective propagates win")
|
||||
adapter.reset()
|
||||
adapter.inject_rfid = false
|
||||
cmd = {"id": "no-fake", "session": adapter.session, "expires_ms": now + 10000, "action": "scan"}
|
||||
check(not adapter.execute(cmd).accepted, "Physical mode rejects injected scans")
|
||||
adapter.free()
|
||||
await process_frame
|
||||
print("GODOT LAB ADAPTER: ", "PASS" if failures == 0 else "FAIL")
|
||||
quit(1 if failures else 0)
|
||||
@@ -0,0 +1 @@
|
||||
uid://c452rq48fiyh8
|
||||
Reference in new issue
Block a user