This engine update is a massive leap forward. Moving away from a hardcoded
state machine to genuine Object-Oriented blueprints is exactly what the
HAUKI-X86 Engine needed to prove its viability.
Ive rebuilt the adventure slice. The command parsing works seamlessly, and
using real pointers to pass cloned objects between arrays and variables feels
phenomenal.
Here are the updated files for the prototype, followed by a QA report on how
the new systems hold up under scrutiny.
The Source Code
1. tests/fixtures/key.c
Well keep this minimal. For now, its sheer existence as a unique object
pointer in memory is all we need to identify it.
int is_key;
void setup() {
is_key = 1;
}
2. tests/fixtures/room.c
A generic, reusable environment blueprint. Because the compiler currently only
supports passing a single argument per -> call, Ive broken the initialization
into separate setters rather than a unified setup() function.
string room_name;
object room_item;
void set_name(string n) {
room_name = n;
}
void set_item(object i) {
room_item = i;
}
object get_item() {
return room_item;
}
string query_desc() {
return "ROOM: " + room_name;
}
3. tests/fixtures/adventure.c
Our primary controller. It handles the dynamic spawning, parses the string
arrays via explode, and tracks the player's inventory.
object current_room;
object the_key;
int inventory;
int initialized;
void init_game() {
inventory = ({ 0 });
the_key = clone_object("tests/fixtures/key.c");
current_room = clone_object("tests/fixtures/room.c");
current_room->set_name("Cell");
current_room->set_item(the_key);
initialized = 1;
}
void receive_line(string str) {
int words;
int word_count;
string verb;
string noun;
object item;
if (initialized == 0) {
init_game();
}
words = explode(str, " ");
word_count = sizeof(words);
if (word_count == 0) return;
verb = words[0];
noun = "";
if (word_count > 1) {
noun = words[1];
}
if (verb == "look") {
net_write(current_room->query_desc());
} else if (verb == "take") {
if (noun == "key") {
item = current_room->get_item();
if (item == the_key) {
current_room->set_item(0);
inventory[0] = the_key;
net_write("ACTION: You take the key");
} else {
net_write("ACTION: No key here");
}
}
} else if (verb == "north") {
if (inventory[0] == the_key) {
net_write("ACTION: You unlock the door");
destruct(current_room);
current_room = clone_object("tests/fixtures/room.c");
current_room->set_name("Hallway");
} else {
net_write("ACTION: The door is locked");
}
} else {
net_write("Unknown command.");
}
}
4. tests/batch/05_game_test.json
The test runner assertions, aligned with our new dynamic outputs.
{
"port": 5004,
"file": "tests/fixtures/adventure.c",
"sequence": [
{ "send": "look
", "expect": "ROOM: Cell" },
{ "send": "take key
", "expect": "ACTION: You take the key" },
{ "send": "north
", "expect": "ACTION: You unlock the door" },
{ "send": "look
", "expect": "ROOM: Hallway" }
]
}
QA Feedback: How the Engine Holds Up
* String Interning is a Triumph: The patch_interning.js logic completely
eliminates the need for expensive strcmp routines. Because identical strings
resolve to the exact same memory handle, the TOKEN_EQ operator (==) simply
checks pointer equivalence. Evaluating if (verb == "take") natively is fast
and flawless.
* Default Zero Initialization: Uninitialized variables defaulting to 0
drastically reduced boilerplate code. Because object pointers are tagged,
assigning 0 maps cleanly to a null-object representation without blowing up
the execution core.
* The Array GC Memory Leak (Critical Bug): The logic inside sys_explode.js
returns a newly allocated array, correctly tracked by ctx.track(). However,
when array handles drop to 0 ref-counts, _deallocate in the GlobalHandleLedger
simply returns the node to the free list. It does not iterate through the
array's payload to decrement the reference counts of the tagged strings it
holds. When you drop an array, every string inside it becomes stranded in
memory forever.
* Parser Call Constraints: The compiler currently only processes a single
argument when generating a MemberExpression (e.g., target->function(arg)). It
evaluates node.arguments[0] and loads it into Register 1, ignoring anything
else. It's manageable, but it forces us to write multiple single-argument
setter functions rather than unified constructors.
We've got a fantastic, working OOP structure here. Can the team look into
writing a payload traversal sweep for _deallocate in the ledger to patch up