Expand Glitch operations hub, cabinet authoring, deployment, and RFID support

Operator hub
- Introduce Escape Gallery branding and Glitch: the Dark Machine room views
- Add cabinet illustrations, Pac-Man marquee/gameplay artwork, and waiting states
- Add device feedback panels with RFID readings, input/output states, and stale status
- Add logged operator hardware interventions with request deduplication

Room and cabinet authoring
- Add a visual custom-PCB connection editor with per-cabinet device inventory
- Support device IDs, bindings, wiring notes, input polarity, and validation
- Add physical-room label/dimension toggles and editable doorway annotations
- Preserve doorway changes through undo, save, and revert
- Expand cabinet profiles, room layout, and Glitch scenario resources

Scenarios and gameplay
- Add per-assignment game configuration and runtime feature validation
- Add timed test wins and Frogger individual-home objectives
- Add the three-cabinet timed/score/Frogger test scenario
- Add Cabinet 1 Pac-Man RFID credits, credit sound, and CREDIT 0/1 display
- Require physical Start after an accepted scan and prevent duplicate credits

Pi hardware and deployment
- Add GPIO feedback sampling and a separate multiplexed RFID helper
- Validate reader pin ownership and input/output conflicts
- Add SSH cabinet startup and Pi desktop-session service support
- Add master packaging, site setup, SSH enrollment, and staged cabinet releases
- Verify deployment manifests and support explicit activation/status/log commands

Documentation and verification
- Consolidate the root README around the current architecture and setup workflow
- Add the complete master/Pi/RFID export and deployment walkthrough
- Document network isolation, credentials, card identification, and rollback
- Add PCB, room-editor, feedback, credit, deployment, and controller tests
- Isolate protocol tests from designer-edited cabinet configurations

Known limitations
- RFID helper compilation and physical Pi commissioning remain unverified
- Fleet provisioning does not yet configure RFID-only installations completely
- Broader Pac-Man tests retain a ghost-capture assertion failure
- Staged exports must be rebuilt and checked before deploying runtime 2.3
This commit is contained in:
Jeff Luke committed 2026-10-17 14:53:54 -07:00
1 parent 705ec31e0b
commit c1b2195834
90 files changed
+4969 -456

No files matched your search

+79 -11
View File
@@ -82,6 +82,8 @@ var highlighted_bindings: Dictionary = {}
var binding_labels: Array[Label] = []
var show_connections: bool = true
var show_labels: bool = true
var show_dimensions: bool = true
var selected_binding_key: String = ""
@@ -178,6 +180,7 @@ func _rebuild() -> void:
_build_cabinets()
_build_devices()
_build_binding_labels()
set_labels_visible(show_labels)
queue_redraw()
@@ -976,7 +979,7 @@ func set_connections_visible(
show_connections = visible
for label in binding_labels:
label.visible = visible
label.visible = visible and show_labels
queue_redraw()
@@ -1703,7 +1706,7 @@ func _draw_dimension_label(
text: String
) -> void:
if text.is_empty():
if not show_dimensions or text.is_empty():
return
var font := (
@@ -2075,7 +2078,7 @@ func _draw_walls() -> void:
if room == null:
return
for wall in room.walls:
for wall in _ordered_walls():
if wall == null:
continue
@@ -2111,13 +2114,7 @@ func _draw_walls() -> void:
width += 3.0
draw_line(
start,
end,
wall_color,
width,
true
)
_draw_wall_or_door(wall, start, end, wall_color, width)
#
# Wall dimension.
@@ -2867,7 +2864,9 @@ func _find_wall_at_screen_position(
var hit_radius_pixels: float = 10.0
for wall in room.walls:
var ordered := _ordered_walls()
ordered.reverse()
for wall in ordered:
if wall == null:
continue
@@ -3987,3 +3986,72 @@ func _get_cabinet_profile(
return assignment.cabinet
return null
func set_labels_visible(value: bool) -> void:
show_labels = value
for item in item_nodes.values():
item.title_label.visible = value
item.subtitle_label.visible = value
item.tooltip_text = item.base_title + "\n" + item.base_subtitle
for label in binding_labels:
label.visible = value and show_connections
queue_redraw()
func set_dimensions_visible(value: bool) -> void:
show_dimensions = value
queue_redraw()
func _ordered_walls() -> Array[RoomWallDefinition]:
var ordered: Array[RoomWallDefinition] = []
for wall in room.walls:
if wall != null and not wall.is_doorway:
ordered.append(wall)
for wall in room.walls:
if wall != null and wall.is_doorway:
ordered.append(wall)
return ordered
# Openings are projected onto collinear wall segments without destroying geometry.
func _wall_solid_ranges(wall: RoomWallDefinition) -> Array[Vector2]:
var ranges: Array[Vector2] = [Vector2(0, 1)]
var delta := wall.end - wall.start
if delta.length_squared() < 0.000001:
return ranges
for door in room.walls:
if door == null or not door.is_doorway:
continue
var tolerance := maxf(wall.thickness * 0.5, 0.02)
if absf(delta.cross(door.start - wall.start)) / delta.length() > tolerance or absf(delta.cross(door.end - wall.start)) / delta.length() > tolerance:
continue
var a := delta.dot(door.start - wall.start) / delta.length_squared()
var b := delta.dot(door.end - wall.start) / delta.length_squared()
var low := minf(a, b)
var high := maxf(a, b)
var next: Array[Vector2] = []
for segment in ranges:
if high <= segment.x or low >= segment.y:
next.append(segment)
else:
if low > segment.x:
next.append(Vector2(segment.x, low))
if high < segment.y:
next.append(Vector2(high, segment.y))
ranges = next
return ranges
func _draw_wall_or_door(wall: RoomWallDefinition, start: Vector2, end: Vector2, color: Color, width: float) -> void:
if not wall.is_doorway:
for segment in _wall_solid_ranges(wall):
draw_line(start.lerp(end, segment.x), start.lerp(end, segment.y), color, width, true)
return
var direction := end - start
if direction.length() < 1.0:
return
var angle := direction.angle()
var swing := -PI / 2.0 if wall.swing_reversed else PI / 2.0
var door_color := Color("67d5c5") if wall.wall_id != selected_wall_id else color
draw_dashed_line(start, end, door_color, 1.5, 5.0)
draw_line(start, start + direction.rotated(swing), door_color, 2.0, true)
draw_arc(start, direction.length(), minf(angle, angle + swing), maxf(angle, angle + swing), 32, door_color, 1.5, true)
draw_circle(start, 4.0, door_color)