Skip to content

Commit 7e8c3ce

Browse files
committed
Add GMCPClientSpatial package for spatial audio scene management
Introduces Client.Spatial GMCP package with full scene, entity, emitter, and listener position/orientation handling. Adds corresponding spatial state fields to WorldData and clears them on connection cleanup. Co-Authored-By: Christopher Toth <q.alpha@gmail.com>
1 parent e0e9694 commit 7e8c3ce

6 files changed

Lines changed: 519 additions & 33 deletions

File tree

src/client.ts

Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,29 @@ import {
2727
parseMcpMultiline,
2828
} from "./mcp";
2929

30-
import { Cacophony } from "cacophony";
31-
import { AutoreadMode, preferencesStore } from "./PreferencesStore";
32-
import { WebRTCService } from "./WebRTCService";
30+
import { Cacophony } from "cacophony";
31+
import { AutoreadMode, preferencesStore } from "./PreferencesStore";
32+
import { WebRTCService } from "./WebRTCService";
3333
import FileTransferManager from "./FileTransferManager.js";
3434
import { GMCPMessageRoomInfo, RoomPlayer } from "./gmcp/Room"; // Import RoomPlayer
35+
import type {
36+
SpatialEmitter,
37+
SpatialEntity,
38+
SpatialListenerOrientation,
39+
SpatialVector,
40+
} from "./gmcp/Client/Spatial";
3541

3642
export interface WorldData {
37-
liveKitTokens: string[];
38-
playerId: string;
39-
playerName: string;
40-
roomId: string;
41-
roomPlayers: RoomPlayer[]; // Changed from string[]
43+
liveKitTokens: string[];
44+
playerId: string;
45+
playerName: string;
46+
roomId: string;
47+
roomPlayers: RoomPlayer[]; // Changed from string[]
48+
spatialEntities: Record<string, SpatialEntity>;
49+
spatialEmitters: Record<string, SpatialEmitter>;
50+
listenerEntityId: string;
51+
listenerPosition: SpatialVector | null;
52+
listenerOrientation: SpatialListenerOrientation;
4253
}
4354

4455
function resetMidiIntentionalDisconnectFlags(): void {
@@ -78,13 +89,18 @@ class MudClient extends EventEmitter {
7889
public mcp_getset: McpAwnsGetSet;
7990
public gmcp_char: GMCPChar;
8091
public gmcp_fileTransfer: GMCPClientFileTransfer;
81-
public worldData: WorldData = {
82-
playerId: "",
83-
playerName: "",
84-
roomId: "",
85-
liveKitTokens: [],
86-
roomPlayers: [], // Initialized as RoomPlayer[]
87-
};
92+
public worldData: WorldData = {
93+
playerId: "",
94+
playerName: "",
95+
roomId: "",
96+
liveKitTokens: [],
97+
roomPlayers: [], // Initialized as RoomPlayer[]
98+
spatialEntities: {},
99+
spatialEmitters: {},
100+
listenerEntityId: "",
101+
listenerPosition: null,
102+
listenerOrientation: { forward: null, up: null },
103+
};
88104
public cacophony: Cacophony;
89105
public editors: EditorManager;
90106
public webRTCService: WebRTCService;
@@ -419,13 +435,18 @@ class MudClient extends EventEmitter {
419435
}
420436
}
421437

422-
private cleanupConnection(): void {
423-
this._connected = false;
424-
this.mcpAuthKey = null;
425-
this.telnetBuffer = "";
426-
this.telnetNegotiation = false;
427-
this.currentRoomInfo = null; // Reset room info on cleanup
428-
this.webRTCService.cleanup();
438+
private cleanupConnection(): void {
439+
this._connected = false;
440+
this.mcpAuthKey = null;
441+
this.telnetBuffer = "";
442+
this.telnetNegotiation = false;
443+
this.currentRoomInfo = null; // Reset room info on cleanup
444+
this.worldData.spatialEntities = {};
445+
this.worldData.spatialEmitters = {};
446+
this.worldData.listenerEntityId = "";
447+
this.worldData.listenerPosition = null;
448+
this.worldData.listenerOrientation = { forward: null, up: null };
449+
this.webRTCService.cleanup();
429450
this.fileTransferManager.cleanup();
430451

431452
// Reset intentional disconnect flag after handling disconnect
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import React from "react";
2+
import { act, render, screen } from "@testing-library/react";
3+
import { describe, expect, it, vi } from "vitest";
4+
import { EventEmitter } from "eventemitter3";
5+
6+
import RoomInfoDisplay from "./RoomInfoDisplay";
7+
8+
function createMockClient() {
9+
const emitter = new EventEmitter();
10+
11+
return Object.assign(emitter, {
12+
currentRoomInfo: {
13+
num: 101,
14+
name: "Codex's Lab",
15+
area: "Daystrom Annex",
16+
exits: { west: 100, northeast: 102 },
17+
},
18+
gmcpHandlers: {
19+
"Char.Items": {
20+
sendRoomRequest: vi.fn(),
21+
},
22+
},
23+
sendCommand: vi.fn(),
24+
worldData: {
25+
liveKitTokens: [],
26+
playerId: "codex",
27+
playerName: "codex",
28+
roomId: "101",
29+
roomPlayers: [
30+
{
31+
name: "q",
32+
fullname: "Q",
33+
},
34+
],
35+
spatialEntities: {
36+
codex: {
37+
id: "codex",
38+
position: [1, 1, 0],
39+
},
40+
},
41+
spatialEmitters: {
42+
"radio-1": {
43+
id: "radio-1",
44+
binding: "entity",
45+
sourceEntity: "codex",
46+
},
47+
},
48+
listenerEntityId: "codex",
49+
listenerPosition: [1, 1, 0],
50+
listenerOrientation: {
51+
forward: [0, 1, 0],
52+
up: [0, 0, 1],
53+
},
54+
},
55+
});
56+
}
57+
58+
describe("RoomInfoDisplay", () => {
59+
it("still renders room, players, and items with spatial scene state present", () => {
60+
const client = createMockClient();
61+
62+
render(<RoomInfoDisplay client={client as any} />);
63+
64+
act(() => {
65+
client.emit("itemsList", {
66+
location: "room",
67+
items: [{ id: "lantern", name: "Lantern", location: "room" }],
68+
});
69+
});
70+
71+
expect(screen.getByText("Codex's Lab")).toBeTruthy();
72+
expect(screen.getByText("Area: Daystrom Annex")).toBeTruthy();
73+
expect(screen.getByText("Q")).toBeTruthy();
74+
expect(screen.getByText("Lantern")).toBeTruthy();
75+
});
76+
});

src/createConfiguredClient.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ import {
1717
GMCPClientHaptics,
1818
GMCPClientHtml,
1919
GMCPClientKeystrokes,
20-
GMCPClientMedia,
21-
GMCPClientMidi,
22-
GMCPClientSpeech,
20+
GMCPClientMedia,
21+
GMCPClientMidi,
22+
GMCPClientSpatial,
23+
GMCPClientSpeech,
2324
GMCPCommChannel,
2425
GMCPCommLiveKit,
2526
GMCPCore,
@@ -44,8 +45,9 @@ export function createConfiguredClient(): MudClient {
4445
const client = new MudClient("mongoose.moo.mud.org", 8765);
4546
// GMCP packages
4647
client.registerGMCPPackage(GMCPCore);
47-
client.registerGMCPPackage(GMCPClientMedia);
48-
client.registerGMCPPackage(GMCPClientMidi);
48+
client.registerGMCPPackage(GMCPClientMedia);
49+
client.registerGMCPPackage(GMCPClientSpatial);
50+
client.registerGMCPPackage(GMCPClientMidi);
4951
client.registerGMCPPackage(GMCPClientSpeech);
5052
client.registerGMCPPackage(GMCPClientKeystrokes);
5153
client.registerGMCPPackage(GMCPCoreSupports);

0 commit comments

Comments
 (0)