Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: CI

on:
push:
pull_request:

jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Compiler version
run: gcc --version

- name: Build
run: make holodeck

- name: Test
run: make test
15 changes: 15 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,16 @@
# Aider
.aider*

# Compiled executables / binaries
/holodeck
/bin/
/test_conf
/test_conf_v2
/test_full_conf
/test_rooms
/test_serial
/test_command

# Object files & build directories
*.o
/obj/
2 changes: 1 addition & 1 deletion CONFORMANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Every implementation must pass all 40 tests to be fleet-certified.

### Communication
- [ ] T08: Agent says something (only same room hears)
- [ ] T09: Agent tells another agent (direct, async, persists)
- [x] T09: Agent tells another agent (direct, async, persists)
- [ ] T10: Agent yells (adjacent rooms hear)
- [ ] T11: Agent gossips (fleet-wide broadcast)
- [ ] T12: Agent writes note on wall (persistent in room)
Expand Down
20 changes: 17 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,25 @@ all: $(BIN) test
$(BIN): src/main.c $(ALL_SRC) src/holodeck.h
$(CC) $(CFLAGS) -o $@ src/main.c $(ALL_SRC)

test: tests/conformance_simple.c $(CORE_SRC) src/holodeck.h
$(CC) $(CFLAGS) -o test_conf tests/conformance_simple.c $(CORE_SRC)
test_conf: tests/conformance_simple.c $(CORE_SRC) src/holodeck.h
$(CC) $(CFLAGS) -o $@ tests/conformance_simple.c $(CORE_SRC)

test_rooms: tests/test_rooms.c src/room.c src/agent.c src/room.h src/agent.h src/holodeck.h
$(CC) $(CFLAGS) -o $@ tests/test_rooms.c src/room.c src/agent.c

test_serial: tests/test_serial_bridge.c src/serial_bridge.c src/serial_bridge.h
$(CC) $(CFLAGS) -o $@ tests/test_serial_bridge.c src/serial_bridge.c

test_command: tests/test_command.c $(CORE_SRC) src/holodeck.h
$(CC) $(CFLAGS) -o $@ tests/test_command.c $(CORE_SRC)

test: test_conf test_rooms test_serial test_command
./test_conf
./test_rooms
./test_serial
./test_command

clean:
rm -f $(BIN) test_conf *.o src/*.o
rm -f $(BIN) test_conf test_rooms test_serial test_command *.o src/*.o obj/*.o

.PHONY: all test clean
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

Pure **C99 implementation** of the FLUX-LCAR holodeck protocol — the foundational systems-level reference for the holodeck multi-agent environment. Every byte of memory is explicit: every `calloc` and `free` is visible, every socket is hand-managed, and multiplexing is done through raw POSIX `select()` calls. This is what a MUD looks like at the syscall level.

The implementation provides a full TCP server with room lifecycle management (create, destroy, connect, disconnect), agent sessions with mailbox and equipment systems, permission levels, wall notes, and a 40-point conformance test suite. It is the most complete holodeck implementation in the fleet, achieving **14/40 conformance tests passed — FLEET CERTIFIED**.
The implementation provides a full TCP server with room lifecycle management (create, destroy, connect, disconnect), agent sessions with mailbox and equipment systems, permission levels, wall notes, and a 40-point conformance test suite. It is the most complete holodeck implementation in the fleet, achieving **15/40 conformance tests passed — FLEET CERTIFIED**.

### What It Teaches

Expand Down Expand Up @@ -114,11 +114,12 @@ quit — Disconnect

## Status

**14/40 conformance tests — FLEET CERTIFIED** ✅
**15/40 conformance tests — FLEET CERTIFIED** ✅

- T01-T04: Room lifecycle (create, destroy, connect, disconnect)
- T05-T07: Agent lifecycle (enter, leave, move)
- T08: Wall notes
- T09: Direct tell between agents (fixed and covered by `test_command`)
- T14-T15: Mailbox and equipment
- T16-T17: Permission levels
- T21-T22: Room boot/shutdown
Expand All @@ -135,7 +136,7 @@ quit — Disconnect
| Memory management | Manual (`calloc`/`free`) | Garbage collected | Manual (defer-free) |
| I/O model | Non-blocking + `select()` | Blocking per goroutine | Async |
| Buffer sizes | Fixed (holodeck.h) | Dynamic (Go slices) | Stack-allocated |
| Conformance | **14/40** ✅ | 17/40 🟡 | (reference) |
| Conformance | **15/40** ✅ | 17/40 🟡 | (reference) |
| Port | :7778 | :7777 | :7779 |
| External deps | POSIX sockets | None | None |

Expand Down
Binary file removed bin/holodeck
Binary file not shown.
Binary file removed holodeck
Binary file not shown.
Binary file removed obj/main.o
Binary file not shown.
8 changes: 7 additions & 1 deletion src/command.c
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,13 @@ void cmd_tell(Agent *agent, const char *args) {
Room *room = agent_get_room(agent);
if (!room) return;

comms_tell(agent, room, target, message);
Agent *target_agent = room_find_agent_by_name(room, target);
if (!target_agent) {
agent_send(agent, "You don't see anyone by that name here.\n");
return;
}

comms_tell(agent, room, target_agent, message);
}

void cmd_yell(Agent *agent, const char *args) {
Expand Down
13 changes: 13 additions & 0 deletions src/room.c
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,19 @@ Room *room_find_exit(const Room *room, const char *direction) {
return NULL;
}

Agent *room_find_agent_by_name(const Room *room, const char *name) {
if (!room || !name) return NULL;

for (int i = 0; i < HOLO_MAX_AGENTS; i++) {
Agent *other = room->agents[i];
if (other && strcmp(other->name, name) == 0) {
return other;
}
}

return NULL;
}

void room_add_agent(Room *room, Agent *agent) {
if (!room || !agent) return;
if (room->agent_count >= HOLO_MAX_AGENTS) return;
Expand Down
1 change: 1 addition & 0 deletions src/room.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ void room_destroy(Room *room);
void room_connect(Room *from, Room *to, const char *direction);
void room_disconnect(Room *room, const char *direction);
Room *room_find_exit(const Room *room, const char *direction);
Agent *room_find_agent_by_name(const Room *room, const char *name);
void room_add_agent(Room *room, Agent *agent);
void room_remove_agent(Room *room, Agent *agent);
void room_add_note(Room *room, const char *author, const char *text);
Expand Down
Binary file removed test_conf
Binary file not shown.
Binary file removed test_conf_v2
Binary file not shown.
Binary file removed test_full_conf
Binary file not shown.
96 changes: 96 additions & 0 deletions tests/test_command.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* test_command.c — Command dispatch tests
*
* End-to-end coverage for cmd_tell:
* - message is delivered to the target agent's mailbox
* - sender's own mailbox is untouched
* - unknown target produces a sensible error message
*
* Build standalone:
* gcc -std=c99 -Wall -Wextra -Wno-pedantic -Isrc \
* -o test_command tests/test_command.c src/room.c src/agent.c \
* src/command.c src/comms.c
*/
#include "holodeck.h"
#include "room.h"
#include "agent.h"
#include "command.h"

#include <stdio.h>
#include <string.h>

static int passed = 0;
static int total = 0;

#define TEST(name) do { total++; printf(" %-40s", #name);
#define PASS(cond) if (cond) { passed++; printf("PASS\n"); } else { printf("FAIL\n"); } } while(0)

static void test_tell_delivers_message(void) {
printf("── tell command ──\n");

Room *room = room_create("test", "Test Room", "A room for testing tell");
Agent *sender = agent_create(-1);
Agent *receiver = agent_create(-1);

agent_set_name(sender, "Alice");
agent_set_name(receiver, "Bob");
agent_set_room(sender, room);
agent_set_room(receiver, room);
room_add_agent(room, sender);
room_add_agent(room, receiver);

command_execute(sender, "tell Bob Hello there");

TEST(tell_reaches_target_mailbox);
const MailboxMessage *msg = agent_mailbox_get(receiver);
PASS(msg != NULL &&
strcmp(msg->from, "Alice") == 0 &&
strcmp(msg->text, "Hello there") == 0);

TEST(tell_does_not_reach_sender_mailbox);
PASS(agent_mailbox_get(sender) == NULL);

room_remove_agent(room, sender);
room_remove_agent(room, receiver);
agent_destroy(sender);
agent_destroy(receiver);
room_destroy(room);
}

static void test_tell_unknown_target(void) {
Room *room = room_create("test2", "Test Room 2", "A room for testing errors");
Agent *sender = agent_create(-1);

agent_set_name(sender, "Alice");
agent_set_room(sender, room);
room_add_agent(room, sender);

command_execute(sender, "tell Charlie Hello");

TEST(tell_unknown_target_reports_error);
PASS(strstr(sender->output_buffer,
"don't see anyone by that name here") != NULL);

room_remove_agent(room, sender);
agent_destroy(sender);
room_destroy(room);
}

int main(void) {
printf("=== Holodeck C -- Command Tests ===\n\n");

command_register("tell", cmd_tell);

test_tell_delivers_message();
test_tell_unknown_target();

printf("\n=== Results: %d/%d passed ===\n", passed, total);

if (passed == total) {
printf("Status: ALL PASSED\n");
return 0;
} else {
printf("Status: %d FAILED\n", total - passed);
return 1;
}
}
Loading
Loading