- Created IT-08-SCREENSHOT-COMPLETION.md detailing the implementation of the Screenshot RPC, including technical summary, testing results, design decisions, and future work. - Introduced IT-10-COLLABORATIVE-EDITING.md outlining the vision, user stories, architecture, implementation plan, and success metrics for real-time collaborative editing in YAZE.
1012 lines
28 KiB
Markdown
1012 lines
28 KiB
Markdown
# IT-10: Collaborative Editing & Multiplayer Sessions
|
||
|
||
**Priority**: P2 (High value, non-blocking)
|
||
**Status**: 📋 Planned
|
||
**Estimated Effort**: 12-15 hours
|
||
**Dependencies**: IT-05 (Test Introspection), IT-08 (Screenshot Capture)
|
||
**Target**: Enable real-time collaborative ROM editing with AI assistance
|
||
|
||
---
|
||
|
||
## Vision
|
||
|
||
Enable multiple users to connect to the same YAZE session, see each other's edits in real-time, and collaborate with AI agents together. Think "Google Docs for ROM hacking" where users can:
|
||
|
||
- **Connect to each other's sessions** over the network
|
||
- **See real-time edits** (tiles, sprites, map changes)
|
||
- **Share AI assistance** (one user asks AI, all users see results)
|
||
- **Coordinate workflows** (e.g., one user edits dungeons, another edits overworld)
|
||
- **Review changes together** with live cursors and annotations
|
||
|
||
---
|
||
|
||
## User Stories
|
||
|
||
### US-1: Session Host & Join
|
||
|
||
**As a ROM hacker**, I want to host a collaborative editing session so my teammates can join and work together.
|
||
|
||
```bash
|
||
# Host creates a session
|
||
$ z3ed collab host --port 5000 --password "dev123"
|
||
✅ Collaborative session started
|
||
Session ID: yaze-collab-f3a9b2c1
|
||
URL: yaze://connect/localhost:5000?session=yaze-collab-f3a9b2c1
|
||
Password: dev123
|
||
|
||
👥 Waiting for collaborators...
|
||
|
||
# Remote user joins
|
||
$ z3ed collab join yaze://connect/192.168.1.100:5000?session=yaze-collab-f3a9b2c1
|
||
🔐 Enter session password: ***
|
||
✅ Connected to session (Host: Alice)
|
||
👥 Active users: Alice (host), Bob (you)
|
||
```
|
||
|
||
**Acceptance Criteria**:
|
||
- Host can create session with optional password
|
||
- Clients can discover and join sessions
|
||
- Connection state visible in GUI status bar
|
||
- Maximum 8 concurrent users per session
|
||
|
||
---
|
||
|
||
### US-2: Real-Time Edit Synchronization
|
||
|
||
**As a collaborator**, I want to see other users' edits in real-time so we stay synchronized.
|
||
|
||
**Scenario**: Alice edits a tile in Overworld Editor
|
||
```
|
||
Alice's GUI:
|
||
- Draws tile at (10, 15) → Sends edit event to all clients
|
||
|
||
Bob's GUI (auto-update):
|
||
- Receives edit event → Redraws tile at (10, 15)
|
||
- Shows Alice's cursor/selection indicator
|
||
```
|
||
|
||
**Acceptance Criteria**:
|
||
- Edits appear on all clients within 100ms
|
||
- Conflict resolution for simultaneous edits
|
||
- Undo/redo synchronized across sessions
|
||
- Cursor positions visible for all users
|
||
|
||
---
|
||
|
||
### US-3: Shared AI Agent
|
||
|
||
**As a team lead**, I want to use AI agents with my team so we can all benefit from automation.
|
||
|
||
```bash
|
||
# Alice (host) runs an AI agent test
|
||
$ z3ed agent test --prompt "Add treasure chest to room 12" --share
|
||
|
||
🤖 AI Agent: Analyzing request...
|
||
Action: Click "Dungeon Editor" tab
|
||
Action: Select Room 12
|
||
Action: Add object type 0x12 (treasure chest) at (5, 8)
|
||
|
||
✅ Proposal generated (ID: prop_3f8a)
|
||
|
||
# All connected users see the proposal in their GUI
|
||
Bob's Screen:
|
||
┌─────────────────────────────────────────┐
|
||
│ 🤖 AI Proposal from Alice │
|
||
│ │
|
||
│ Add treasure chest to room 12 │
|
||
│ • Click "Dungeon Editor" tab │
|
||
│ • Select Room 12 │
|
||
│ • Add treasure chest at (5, 8) │
|
||
│ │
|
||
│ [Accept] [Reject] [Discuss] │
|
||
└─────────────────────────────────────────┘
|
||
|
||
# Team vote: 2/3 accept → Proposal executes for all users
|
||
```
|
||
|
||
**Acceptance Criteria**:
|
||
- AI agent results broadcast to all session members
|
||
- Proposals require majority approval (configurable threshold)
|
||
- All users see agent execution in real-time
|
||
- Failed operations rollback for all users
|
||
|
||
---
|
||
|
||
### US-4: Live Cursors & Annotations
|
||
|
||
**As a collaborator**, I want to see where other users are working so we don't conflict.
|
||
|
||
**Visual Indicators**:
|
||
```
|
||
┌─────────────────────────────────────────────┐
|
||
│ Overworld Editor │
|
||
│ │
|
||
│ 🟦 (Alice's cursor at map 0x40) │
|
||
│ 🟩 (Bob's cursor at map 0x41) │
|
||
│ 🟥 (Charlie editing palette) │
|
||
│ │
|
||
│ Active Editors: │
|
||
│ • Alice: Overworld (read-write) │
|
||
│ • Bob: Overworld (read-write) │
|
||
│ • Charlie: Palette Editor (read-only) │
|
||
└─────────────────────────────────────────────┘
|
||
```
|
||
|
||
**Acceptance Criteria**:
|
||
- Each user has unique color-coded cursor
|
||
- Active editor window highlighted for each user
|
||
- Text chat overlay for quick communication
|
||
- Annotation tools (pins, comments, highlights)
|
||
|
||
---
|
||
|
||
### US-5: Session Recording & Replay
|
||
|
||
**As a project manager**, I want to record collaborative sessions so we can review work later.
|
||
|
||
```bash
|
||
# Host enables session recording
|
||
$ z3ed collab host --record session_2025_10_02.yaml
|
||
|
||
# Recording captures:
|
||
# - All edit operations (tiles, sprites, maps)
|
||
# - AI agent proposals and votes
|
||
# - Chat messages and annotations
|
||
# - User join/leave events
|
||
# - Timestamps for audit trail
|
||
|
||
# Later: Replay the session
|
||
$ z3ed collab replay session_2025_10_02.yaml --speed 2x
|
||
|
||
# Replay shows:
|
||
# - Timeline of all edits
|
||
# - User activity heatmap
|
||
# - Decision points (proposals accepted/rejected)
|
||
# - Final ROM state comparison
|
||
```
|
||
|
||
**Acceptance Criteria**:
|
||
- All session activity recorded in structured format (YAML/JSON)
|
||
- Replay supports speed control (0.5x - 10x)
|
||
- Export to video format (optional, uses screenshots)
|
||
- Audit log for compliance/review
|
||
|
||
---
|
||
|
||
## Architecture
|
||
|
||
### Components
|
||
|
||
#### 1. Collaboration Server (New)
|
||
|
||
**Location**: `src/app/core/collab/collab_server.{h,cc}`
|
||
|
||
**Responsibilities**:
|
||
- Manage WebSocket connections from clients
|
||
- Broadcast edit events to all connected clients
|
||
- Handle session authentication (password, tokens)
|
||
- Enforce access control (read-only vs read-write)
|
||
- Maintain session state (active users, current ROM)
|
||
|
||
**Technology**:
|
||
- **WebSocket** for low-latency bidirectional communication
|
||
- **Protocol Buffers** for efficient serialization
|
||
- **JWT tokens** for session authentication
|
||
- **Redis** (optional) for distributed sessions
|
||
|
||
**Key APIs**:
|
||
```cpp
|
||
class CollabServer {
|
||
public:
|
||
// Start server on specified port
|
||
absl::Status Start(int port, const std::string& password);
|
||
|
||
// Handle new client connection
|
||
void OnClientConnected(ClientConnection* client);
|
||
|
||
// Broadcast edit event to all clients
|
||
void BroadcastEdit(const EditEvent& event, ClientConnection* sender);
|
||
|
||
// Handle AI proposal from client
|
||
void BroadcastProposal(const AgentProposal& proposal);
|
||
|
||
// Get active users in session
|
||
std::vector<UserInfo> GetActiveUsers() const;
|
||
|
||
private:
|
||
std::unique_ptr<WebSocketServer> ws_server_;
|
||
absl::Mutex clients_mutex_;
|
||
std::vector<std::unique_ptr<ClientConnection>> clients_;
|
||
SessionState session_state_;
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
#### 2. Collaboration Client (New)
|
||
|
||
**Location**: `src/app/core/collab/collab_client.{h,cc}`
|
||
|
||
**Responsibilities**:
|
||
- Connect to remote collaboration server
|
||
- Send local edits to server
|
||
- Receive and apply remote edits
|
||
- Sync ROM state on join
|
||
- Handle disconnection/reconnection
|
||
|
||
**Key APIs**:
|
||
```cpp
|
||
class CollabClient {
|
||
public:
|
||
// Connect to session
|
||
absl::Status Connect(const std::string& url, const std::string& password);
|
||
|
||
// Send local edit to server
|
||
void SendEdit(const EditEvent& event);
|
||
|
||
// Callback when remote edit received
|
||
void OnRemoteEdit(const EditEvent& event);
|
||
|
||
// Get list of active users
|
||
std::vector<UserInfo> GetUsers() const;
|
||
|
||
// Disconnect from session
|
||
void Disconnect();
|
||
|
||
private:
|
||
std::unique_ptr<WebSocketClient> ws_client_;
|
||
CollabEventHandler* event_handler_;
|
||
SessionInfo session_info_;
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
#### 3. Edit Event Protocol (New)
|
||
|
||
**Location**: `src/app/core/proto/collab_events.proto`
|
||
|
||
**Message Definitions**:
|
||
```protobuf
|
||
syntax = "proto3";
|
||
|
||
package yaze.collab;
|
||
|
||
// Generic edit event
|
||
message EditEvent {
|
||
string event_id = 1; // Unique event ID
|
||
string user_id = 2; // User who made the edit
|
||
int64 timestamp_ms = 3; // Unix timestamp
|
||
|
||
oneof event_type {
|
||
TileEdit tile_edit = 10;
|
||
SpriteEdit sprite_edit = 11;
|
||
PaletteEdit palette_edit = 12;
|
||
MapEdit map_edit = 13;
|
||
ObjectEdit object_edit = 14;
|
||
}
|
||
}
|
||
|
||
// Tile edit (Tile16 Editor, Tilemap)
|
||
message TileEdit {
|
||
string editor = 1; // "tile16", "tilemap"
|
||
int32 x = 2;
|
||
int32 y = 3;
|
||
int32 layer = 4;
|
||
bytes tile_data = 5; // Tile pixel data or ID
|
||
}
|
||
|
||
// Sprite edit
|
||
message SpriteEdit {
|
||
int32 sprite_id = 1;
|
||
int32 x = 2;
|
||
int32 y = 3;
|
||
bytes sprite_data = 4;
|
||
}
|
||
|
||
// Map edit (Overworld/Dungeon)
|
||
message MapEdit {
|
||
string map_type = 1; // "overworld", "dungeon"
|
||
int32 map_id = 2;
|
||
bytes map_data = 3;
|
||
}
|
||
|
||
// User cursor position
|
||
message CursorEvent {
|
||
string user_id = 1;
|
||
string editor = 2; // Active editor window
|
||
int32 x = 3;
|
||
int32 y = 4;
|
||
string color = 5; // Cursor color (hex)
|
||
}
|
||
|
||
// AI proposal event
|
||
message ProposalEvent {
|
||
string proposal_id = 1;
|
||
string user_id = 2; // User who initiated agent
|
||
string prompt = 3;
|
||
repeated ProposalAction actions = 4;
|
||
|
||
enum ProposalStatus {
|
||
PENDING = 0;
|
||
ACCEPTED = 1;
|
||
REJECTED = 2;
|
||
EXECUTING = 3;
|
||
COMPLETED = 4;
|
||
}
|
||
ProposalStatus status = 5;
|
||
|
||
// Voting
|
||
map<string, bool> votes = 6; // user_id -> accept/reject
|
||
int32 votes_needed = 7;
|
||
}
|
||
|
||
message ProposalAction {
|
||
string action_type = 1; // "click", "type", "edit"
|
||
map<string, string> params = 2;
|
||
}
|
||
|
||
// Session state
|
||
message SessionState {
|
||
string session_id = 1;
|
||
string host_user_id = 2;
|
||
repeated UserInfo users = 3;
|
||
bytes rom_checksum = 4; // SHA256 of ROM
|
||
int64 session_start_ms = 5;
|
||
}
|
||
|
||
message UserInfo {
|
||
string user_id = 1;
|
||
string username = 2;
|
||
string color = 3; // User's cursor color
|
||
bool is_host = 4;
|
||
bool read_only = 5;
|
||
string active_editor = 6;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
#### 4. Conflict Resolution System
|
||
|
||
**Challenge**: Multiple users edit the same tile/sprite simultaneously
|
||
|
||
**Solution**: Operational Transformation (OT) with timestamps
|
||
|
||
```cpp
|
||
class ConflictResolver {
|
||
public:
|
||
// Resolve conflicting edits
|
||
EditEvent ResolveConflict(const EditEvent& local,
|
||
const EditEvent& remote);
|
||
|
||
private:
|
||
// Last-write-wins with timestamp
|
||
EditEvent LastWriteWins(const EditEvent& e1, const EditEvent& e2);
|
||
|
||
// Merge edits if possible (e.g., different layers)
|
||
std::optional<EditEvent> TryMerge(const EditEvent& e1,
|
||
const EditEvent& e2);
|
||
};
|
||
```
|
||
|
||
**Conflict Resolution Rules**:
|
||
1. **Same tile, different times**: Last write wins (based on timestamp)
|
||
2. **Same tile, same time (<100ms)**: Host user wins (host authority)
|
||
3. **Different tiles**: No conflict, apply both
|
||
4. **Different layers**: No conflict, apply both
|
||
5. **Undo/Redo**: Undo takes precedence (explicit user intent)
|
||
|
||
---
|
||
|
||
#### 5. GUI Integration
|
||
|
||
**Status Bar Indicator**:
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ File Edit View Tools Help 👥 3 users connected │
|
||
│ 🟢 Alice (Host) │
|
||
│ 🔵 Bob │
|
||
│ 🟣 Charlie │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**Collaboration Panel**:
|
||
```
|
||
┌─────────────────────────────────┐
|
||
│ Collaboration │
|
||
├─────────────────────────────────┤
|
||
│ Session: yaze-collab-f3a9b2c1 │
|
||
│ Status: 🟢 Connected │
|
||
│ │
|
||
│ Users (3): │
|
||
│ 🟢 Alice (Host) - Dungeon │
|
||
│ 🔵 Bob (You) - Overworld │
|
||
│ 🟣 Charlie - Palette │
|
||
│ │
|
||
│ Activity: │
|
||
│ • Alice edited room 12 │
|
||
│ • Bob added sprite #23 │
|
||
│ • Charlie changed palette 2 │
|
||
│ │
|
||
│ [Chat] [Proposals] [Disconnect] │
|
||
└─────────────────────────────────┘
|
||
```
|
||
|
||
**Cursor Overlay**:
|
||
```cpp
|
||
// In canvas rendering
|
||
void OverworldCanvas::DrawCollaborativeCursors() {
|
||
for (const auto& user : collab_client_->GetUsers()) {
|
||
if (user.active_editor == "overworld" && user.user_id != my_user_id) {
|
||
ImVec2 cursor_pos = TileToScreen(user.cursor_x, user.cursor_y);
|
||
ImU32 color = ImGui::GetColorU32(user.color);
|
||
|
||
// Draw cursor indicator
|
||
draw_list->AddCircleFilled(cursor_pos, 5.0f, color);
|
||
|
||
// Draw username label
|
||
draw_list->AddText(cursor_pos + ImVec2(10, -5), color, user.username.c_str());
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## CLI Commands
|
||
|
||
### Session Management
|
||
|
||
```bash
|
||
# Host a session
|
||
z3ed collab host [options]
|
||
--port <port> Port to listen on (default: 5000)
|
||
--password <password> Session password (optional)
|
||
--max-users <n> Maximum concurrent users (default: 8)
|
||
--read-only <users> Comma-separated list of read-only users
|
||
--record <file> Record session to file
|
||
|
||
# Join a session
|
||
z3ed collab join <url> [options]
|
||
--password <password> Session password
|
||
--username <name> Display name (default: system username)
|
||
--read-only Join in read-only mode
|
||
|
||
# List active sessions (LAN discovery)
|
||
z3ed collab list
|
||
|
||
# Disconnect from session
|
||
z3ed collab disconnect
|
||
|
||
# Kick user (host only)
|
||
z3ed collab kick <user-id>
|
||
```
|
||
|
||
### Session Replay
|
||
|
||
```bash
|
||
# Replay recorded session
|
||
z3ed collab replay <file> [options]
|
||
--speed <n> Playback speed multiplier (default: 1.0)
|
||
--start-time <time> Start at specific timestamp
|
||
--end-time <time> End at specific timestamp
|
||
--export-video <file> Export to video (requires ffmpeg)
|
||
|
||
# Export session timeline
|
||
z3ed collab export <file> [options]
|
||
--format <format> Output format: json, yaml, csv (default: json)
|
||
--include-chat Include chat messages
|
||
--include-proposals Include AI proposals
|
||
```
|
||
|
||
---
|
||
|
||
## Implementation Plan
|
||
|
||
### Phase 1: Core Networking (4-5 hours)
|
||
|
||
**Tasks**:
|
||
1. Set up WebSocket server (using `libwebsockets` or `Boost.Beast`)
|
||
2. Implement client connection handling
|
||
3. Define `EditEvent` protobuf messages
|
||
4. Implement basic message routing (send/receive)
|
||
5. Test with 2 clients: edit on one, see on other
|
||
|
||
**Deliverables**:
|
||
- `collab_server.{h,cc}` with basic WebSocket handling
|
||
- `collab_client.{h,cc}` with connection management
|
||
- `collab_events.proto` with edit event definitions
|
||
|
||
**Validation**:
|
||
- Client A connects to host
|
||
- Client A sends test edit event
|
||
- Host receives and broadcasts to Client B
|
||
- Client B receives edit event
|
||
|
||
---
|
||
|
||
### Phase 2: Edit Synchronization (3-4 hours)
|
||
|
||
**Tasks**:
|
||
1. Hook into editor event system (Overworld, Dungeon, Tile16, Palette)
|
||
2. Capture local edits and convert to `EditEvent` messages
|
||
3. Apply remote `EditEvent` messages to local ROM
|
||
4. Implement conflict resolution (last-write-wins)
|
||
5. Add undo/redo synchronization
|
||
|
||
**Deliverables**:
|
||
- Edit event capture hooks in all editors
|
||
- Edit event application logic
|
||
- Conflict resolver implementation
|
||
|
||
**Validation**:
|
||
- User A edits tile → User B sees tile change
|
||
- User A edits palette → User B sees palette change
|
||
- Simultaneous edits resolve correctly (no data corruption)
|
||
|
||
---
|
||
|
||
### Phase 3: GUI Integration (2-3 hours)
|
||
|
||
**Tasks**:
|
||
1. Add status bar indicator (connected users count)
|
||
2. Create Collaboration panel (ImGui window)
|
||
3. Implement live cursor rendering
|
||
4. Add user color assignment (unique per user)
|
||
5. Display active editor for each user
|
||
|
||
**Deliverables**:
|
||
- Status bar widget showing connection status
|
||
- Collaboration panel showing user list and activity
|
||
- Cursor overlay in canvas editors
|
||
|
||
**Validation**:
|
||
- User can see who's connected
|
||
- User can see other users' cursor positions
|
||
- User can see what editor others are using
|
||
|
||
---
|
||
|
||
### Phase 4: AI Agent Sharing (2-3 hours)
|
||
|
||
**Tasks**:
|
||
1. Broadcast AI proposals to all session members
|
||
2. Implement proposal voting system (majority rule)
|
||
3. Synchronize agent execution across clients
|
||
4. Handle proposal rejection/timeout
|
||
5. Add proposal history to Collaboration panel
|
||
|
||
**Deliverables**:
|
||
- `ProposalEvent` protobuf messages
|
||
- Voting UI in GUI (Accept/Reject/Discuss buttons)
|
||
- Proposal broadcast and execution logic
|
||
|
||
**Validation**:
|
||
- User A runs agent → All users see proposal
|
||
- Users vote on proposal → Executes if majority accepts
|
||
- Proposal execution synchronized (all users see same result)
|
||
|
||
---
|
||
|
||
### Phase 5: Session Recording & Replay (1-2 hours)
|
||
|
||
**Tasks**:
|
||
1. Implement session recorder (capture all events to file)
|
||
2. Create replay engine (read events from file, re-apply)
|
||
3. Add timeline UI for replay (seek, pause, speed control)
|
||
4. Export session to JSON/YAML for analysis
|
||
|
||
**Deliverables**:
|
||
- `session_recorder.{h,cc}` with file I/O
|
||
- `session_player.{h,cc}` with playback control
|
||
- CLI commands for replay
|
||
|
||
**Validation**:
|
||
- Record session → All events captured
|
||
- Replay session → ROM state matches original
|
||
- Seek/speed control works correctly
|
||
|
||
---
|
||
|
||
## Security & Safety Considerations
|
||
|
||
### Authentication
|
||
|
||
**Password Protection**:
|
||
- Session host sets optional password
|
||
- Clients must provide password to join
|
||
- Passwords hashed with bcrypt (never transmitted in plaintext)
|
||
|
||
**Token-Based Auth** (Future):
|
||
- Host generates JWT tokens for trusted users
|
||
- Tokens expire after session ends
|
||
- Revocation list for kicked users
|
||
|
||
---
|
||
|
||
### Authorization
|
||
|
||
**Access Levels**:
|
||
1. **Host**: Full control (read-write, kick users, end session)
|
||
2. **Editor**: Read-write access to ROM
|
||
3. **Viewer**: Read-only access (can see edits, can't make edits)
|
||
|
||
**Permission Model**:
|
||
```cpp
|
||
enum class AccessLevel {
|
||
kHost, // Full control
|
||
kEditor, // Read-write
|
||
kViewer // Read-only
|
||
};
|
||
|
||
class SessionPermissions {
|
||
public:
|
||
bool CanEdit(const std::string& user_id) const;
|
||
bool CanKick(const std::string& user_id) const;
|
||
bool CanEndSession(const std::string& user_id) const;
|
||
|
||
void SetAccessLevel(const std::string& user_id, AccessLevel level);
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
### Data Integrity
|
||
|
||
**ROM Checksum Verification**:
|
||
- Host broadcasts ROM checksum (SHA256) on session start
|
||
- Clients verify their ROM matches host's ROM
|
||
- Mismatched ROMs rejected (prevents desyncs)
|
||
|
||
**Edit Validation**:
|
||
- Server validates edit events before broadcasting
|
||
- Invalid edits rejected (e.g., out-of-bounds tile index)
|
||
- Clients trust server's validation (reduces client-side checks)
|
||
|
||
---
|
||
|
||
### Network Security
|
||
|
||
**Encryption** (Optional, for public internet sessions):
|
||
- Use WSS (WebSocket Secure) with TLS/SSL
|
||
- Self-signed certificates for LAN (trust on first use)
|
||
- Let's Encrypt certificates for public servers
|
||
|
||
**Rate Limiting**:
|
||
- Max 100 edit events per second per user
|
||
- Max 10 AI proposals per minute per session
|
||
- Prevents spam/DOS attacks
|
||
|
||
---
|
||
|
||
## Testing Strategy
|
||
|
||
### Unit Tests
|
||
|
||
**Conflict Resolution**:
|
||
```cpp
|
||
TEST(ConflictResolverTest, LastWriteWins) {
|
||
ConflictResolver resolver;
|
||
|
||
EditEvent e1 = MakeTileEdit(10, 15, /*timestamp=*/1000);
|
||
EditEvent e2 = MakeTileEdit(10, 15, /*timestamp=*/2000);
|
||
|
||
EditEvent resolved = resolver.ResolveConflict(e1, e2);
|
||
|
||
EXPECT_EQ(resolved.timestamp_ms(), 2000); // e2 wins
|
||
}
|
||
|
||
TEST(ConflictResolverTest, MergeDifferentLayers) {
|
||
ConflictResolver resolver;
|
||
|
||
EditEvent e1 = MakeTileEdit(10, 15, /*layer=*/0, /*timestamp=*/1000);
|
||
EditEvent e2 = MakeTileEdit(10, 15, /*layer=*/1, /*timestamp=*/1000);
|
||
|
||
auto merged = resolver.TryMerge(e1, e2);
|
||
|
||
ASSERT_TRUE(merged.has_value());
|
||
// Both edits should be applied (different layers)
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### Integration Tests
|
||
|
||
**Session Connection**:
|
||
```cpp
|
||
TEST(CollabServerTest, ClientConnection) {
|
||
CollabServer server;
|
||
server.Start(5000, "password123");
|
||
|
||
CollabClient client;
|
||
auto status = client.Connect("ws://localhost:5000", "password123");
|
||
|
||
EXPECT_TRUE(status.ok());
|
||
EXPECT_EQ(server.GetActiveUsers().size(), 1);
|
||
}
|
||
|
||
TEST(CollabServerTest, EditBroadcast) {
|
||
CollabServer server;
|
||
server.Start(5000, "password123");
|
||
|
||
CollabClient client1, client2;
|
||
client1.Connect("ws://localhost:5000", "password123");
|
||
client2.Connect("ws://localhost:5000", "password123");
|
||
|
||
// Client 1 sends edit
|
||
EditEvent edit = MakeTileEdit(10, 15, 1000);
|
||
client1.SendEdit(edit);
|
||
|
||
// Wait for broadcast
|
||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||
|
||
// Client 2 should receive edit
|
||
EXPECT_TRUE(client2.HasReceivedEdit(edit.event_id()));
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### E2E Tests
|
||
|
||
**Collaborative Workflow**:
|
||
```bash
|
||
#!/bin/bash
|
||
# tests/e2e/test_collab_workflow.sh
|
||
|
||
# Start host in background
|
||
z3ed collab host --port 5000 --password test &
|
||
HOST_PID=$!
|
||
sleep 2
|
||
|
||
# Client 1 joins
|
||
z3ed collab join ws://localhost:5000 --password test --username Alice &
|
||
CLIENT1_PID=$!
|
||
sleep 1
|
||
|
||
# Client 2 joins
|
||
z3ed collab join ws://localhost:5000 --password test --username Bob &
|
||
CLIENT2_PID=$!
|
||
sleep 1
|
||
|
||
# Verify 3 users connected (host + 2 clients)
|
||
USERS=$(z3ed collab users --json | jq '. | length')
|
||
if [ "$USERS" -ne 3 ]; then
|
||
echo "❌ Expected 3 users, got $USERS"
|
||
exit 1
|
||
fi
|
||
|
||
# Client 1 makes edit via CLI
|
||
z3ed project overworld edit --map 0x40 --tile-x 10 --tile-y 15 --tile-id 0x123
|
||
|
||
# Wait for sync
|
||
sleep 0.5
|
||
|
||
# Verify edit appeared on Client 2
|
||
TILE_ID=$(z3ed project overworld get --map 0x40 --tile-x 10 --tile-y 15 --json | jq '.tile_id')
|
||
if [ "$TILE_ID" != "0x123" ]; then
|
||
echo "❌ Edit not synchronized"
|
||
exit 1
|
||
fi
|
||
|
||
echo "✅ Collaborative workflow test passed"
|
||
|
||
# Cleanup
|
||
kill $HOST_PID $CLIENT1_PID $CLIENT2_PID
|
||
```
|
||
|
||
---
|
||
|
||
## Performance Considerations
|
||
|
||
### Bandwidth Usage
|
||
|
||
**Typical Edit Event Size**:
|
||
- TileEdit: ~50 bytes (protobuf encoded)
|
||
- CursorEvent: ~30 bytes
|
||
- ProposalEvent: ~500 bytes (with actions)
|
||
|
||
**Estimated Bandwidth** (3 users, moderate activity):
|
||
- Edit events: 10/sec × 50 bytes = 500 bytes/sec
|
||
- Cursor updates: 30/sec × 30 bytes = 900 bytes/sec
|
||
- **Total**: ~1.4 KB/sec per client (~11 Kbps)
|
||
|
||
**Optimization**:
|
||
- Batch cursor updates (send every 50ms instead of every frame)
|
||
- Delta compression for tile data (send only changed pixels)
|
||
- Message prioritization (edits > cursors > chat)
|
||
|
||
---
|
||
|
||
### Latency Targets
|
||
|
||
| Metric | Target | Acceptable | Critical |
|
||
|--------|--------|------------|----------|
|
||
| Edit propagation | <50ms | <100ms | >200ms |
|
||
| Cursor update | <16ms | <33ms | >100ms |
|
||
| Proposal broadcast | <100ms | <200ms | >500ms |
|
||
| Connection timeout | - | - | >5s |
|
||
|
||
**Optimization Strategies**:
|
||
- WebSocket for low-latency bidirectional communication
|
||
- Protobuf for efficient serialization (vs JSON)
|
||
- Message batching for cursor updates
|
||
- Predictive rendering (interpolate remote cursors)
|
||
|
||
---
|
||
|
||
## Future Enhancements
|
||
|
||
### Voice Chat Integration
|
||
|
||
**Idea**: Embed WebRTC voice chat for real-time communication
|
||
|
||
```bash
|
||
z3ed collab host --voice --voice-codec opus
|
||
```
|
||
|
||
**Benefits**:
|
||
- Faster coordination than text chat
|
||
- Enhances team collaboration
|
||
- Optional (users can disable if not needed)
|
||
|
||
---
|
||
|
||
### Persistent Sessions
|
||
|
||
**Idea**: Sessions persist across host disconnects (using Redis)
|
||
|
||
**Architecture**:
|
||
```
|
||
┌──────────┐ ┌───────────┐ ┌──────────┐
|
||
│ Client A ├────►│ Redis │◄────┤ Client B │
|
||
└──────────┘ │ (session │ └──────────┘
|
||
│ state) │
|
||
┌──────────┐ │ │ ┌──────────┐
|
||
│ Client C ├────►│ │◄────┤ Client D │
|
||
└──────────┘ └───────────┘ └──────────┘
|
||
```
|
||
|
||
**Benefits**:
|
||
- Host can disconnect without ending session
|
||
- Any user can become new host (elected automatically)
|
||
- Session state preserved (users, edits, proposals)
|
||
|
||
---
|
||
|
||
### Cloud-Hosted Sessions
|
||
|
||
**Idea**: Deploy collab server to cloud for public access
|
||
|
||
```bash
|
||
# Deploy to AWS/GCP/Azure
|
||
z3ed collab deploy --provider aws --region us-east-1
|
||
|
||
# Join cloud session
|
||
z3ed collab join wss://yaze-collab.example.com/session/abc123
|
||
```
|
||
|
||
**Benefits**:
|
||
- No port forwarding required
|
||
- Better uptime and reliability
|
||
- Centralized session management
|
||
|
||
---
|
||
|
||
### Integration with Version Control
|
||
|
||
**Idea**: Auto-commit edits to Git during collaborative sessions
|
||
|
||
```bash
|
||
z3ed collab host --git-auto-commit --git-interval 5m
|
||
```
|
||
|
||
**Behavior**:
|
||
- Every 5 minutes, commit current ROM state to Git
|
||
- Commit message includes user activity summary
|
||
- Creates audit trail for rollback/review
|
||
|
||
---
|
||
|
||
## Success Metrics
|
||
|
||
### Adoption Metrics
|
||
- **Active Sessions**: 50+ concurrent sessions within 3 months
|
||
- **User Retention**: 60% of users who try collab use it again
|
||
- **Session Duration**: Average 30+ minutes per session
|
||
|
||
### Technical Metrics
|
||
- **Edit Latency**: <100ms p99 (99th percentile)
|
||
- **Uptime**: >99.5% (excluding client disconnects)
|
||
- **Conflict Rate**: <1% of edits result in conflicts
|
||
- **Bandwidth**: <5 KB/sec per client average
|
||
|
||
### User Satisfaction
|
||
- **NPS Score**: 40+ (Net Promoter Score)
|
||
- **Bug Reports**: <5 critical bugs per 1000 sessions
|
||
- **Support Tickets**: <10 per month related to collab features
|
||
|
||
---
|
||
|
||
## Risks & Mitigation
|
||
|
||
### Risk 1: Network Latency
|
||
|
||
**Impact**: High latency (>200ms) degrades user experience
|
||
|
||
**Mitigation**:
|
||
- Implement client-side prediction (apply edits immediately, rollback if rejected)
|
||
- Show latency indicator in GUI (yellow/red warning)
|
||
- Suggest LAN-only for high-latency users
|
||
|
||
---
|
||
|
||
### Risk 2: Data Corruption
|
||
|
||
**Impact**: Conflicting edits corrupt ROM data
|
||
|
||
**Mitigation**:
|
||
- Validate all edits server-side before broadcasting
|
||
- Implement robust conflict resolution (last-write-wins with timestamps)
|
||
- Periodic checksum verification (every 60 seconds)
|
||
- Auto-save backups every 5 minutes (local + server)
|
||
|
||
---
|
||
|
||
### Risk 3: Security Vulnerabilities
|
||
|
||
**Impact**: Malicious users could inject invalid edits or DOS the server
|
||
|
||
**Mitigation**:
|
||
- Rate limiting (100 edits/sec per user)
|
||
- Input validation (all edit events validated before broadcast)
|
||
- Optional authentication (password or JWT tokens)
|
||
- Audit logging (all events logged for forensics)
|
||
|
||
---
|
||
|
||
### Risk 4: Scalability
|
||
|
||
**Impact**: Server struggles with 8+ concurrent users
|
||
|
||
**Mitigation**:
|
||
- Load testing with 20+ simulated users
|
||
- Message batching (reduce per-message overhead)
|
||
- Horizontal scaling with Redis (future enhancement)
|
||
- Hard cap at 8 users per session (prevents overload)
|
||
|
||
---
|
||
|
||
## Summary
|
||
|
||
IT-10 brings **real-time collaborative editing** to YAZE, enabling teams to work together on ROM hacks with AI assistance. Key features:
|
||
|
||
✅ **Session Hosting**: Host collaborative sessions with password protection
|
||
✅ **Real-Time Sync**: Edits appear on all clients within 100ms
|
||
✅ **Shared AI Agents**: Team votes on AI proposals before execution
|
||
✅ **Live Cursors**: See where teammates are working
|
||
✅ **Session Recording**: Replay sessions for review and audit
|
||
|
||
**Estimated Effort**: 12-15 hours
|
||
**Dependencies**: IT-05 (Introspection), IT-08 (Screenshot Capture)
|
||
**Priority**: P2 (High value, but not blocking other work)
|
||
|
||
**Recommended Timeline**:
|
||
- **After IT-09 Complete**: Once CI/CD integration is done
|
||
- **Before Agent Workflow (Phase A3)**: Collab enhances agent workflows (team AI usage)
|
||
|
||
**Next Steps**:
|
||
1. Finalize IT-08 (Enhanced Error Reporting)
|
||
2. Complete IT-09 (CI/CD Integration)
|
||
3. Prototype collab server with 2-client test
|
||
4. Gather user feedback on collaborative workflows
|
||
|
||
---
|
||
|
||
**Document Created**: October 2, 2025
|
||
**Author**: GitHub Copilot (AI Assistant)
|
||
**Project**: YAZE - Yet Another Zelda3 Editor
|
||
**Component**: z3ed CLI Tool - Collaborative Editing
|