Implement the following plan:
Fix life.occ display buffering and PRI ALT busy-wait
Context
The Game of Life example (historical-examples/life.occ) compiles and runs, but the editor mode doesn't visibly work (issue #72, latest comment). Two bugs:
-
Screen output never flushes in editor mode: The screen goroutine uses bufio.NewWriter (4096-byte buffer) and only flushes on byte 255 (never sent) or buffer full. In edit mode, cursor movements produce ~8-24 bytes per keypress — far too few to trigger a flush. "Idle" is visible only because the busy-wait loop writes it thousands of times.
-
PRI ALT guarded SKIP causes busy-wait: The controller's PRI ALT has (activity <> idle) & SKIP. When idle, this guard is false, so the ALT should block on the keyboard channel. But the Go translation uses unconditional default: in select, which always fires — creating a 100% CPU spin loop.
Fix 1: Auto-flush screen output when channel drains
File: codegen/codegen.go (lines 483-502, screen writer goroutine)
After w.WriteByte(b) (line 495), add:
if len(screen) == 0 {
w.Flush()
}
Apply the same fix to the error writer goroutine (line 521, after the error WriteByte).
This flushes after each burst of output while still batching rapid sequential writes.
Fix 2: Guarded SKIP in ALT — conditional blocking
File: codegen/codegen.go, generateAltBlock() (lines 1947-2033)
Approach: Dual-select pattern
When an ALT has a guarded SKIP case (c.IsSkip && c.Guard != nil), generate two select blocks instead of one:
_altSkipReady := <guard expression>
if _altSkipReady {
select {
case x = <-_alt0:
...channel body...
default:
...skip body...
}
} else {
select {
case x = <-_alt0:
...channel body...
}
}
When guard is true: default fires if no channel ready (correct PRI ALT semantics).
When guard is false: no default, select blocks on channels (correct blocking).
Implementation steps
-
Extract helper method generateAltChannelCase(i int, c ast.AltCase) — generates a single channel case label + body (the code currently at lines 1989-2013 + 2014-2030, minus the SKIP handling). This avoids duplicating the channel case generation code across both select blocks.
-
In generateAltBlock(), detect guarded SKIP:
go
guardedSkipIdx := -1
for i, c := range alt.Cases {
if c.IsSkip && c.Guard != nil {
guardedSkipIdx = i
break
}
}
-
If guardedSkipIdx >= 0: generate _altSkipReady variable, then the if/else with two selects (using helper for channel cases). The SKIP body no longer needs the inner if guard { } wrapper since the guard is checked in the outer if.
-
If guardedSkipIdx < 0: existing codegen unchanged (unguarded SKIP → default:, no SKIP → no default).
Tests
E2E test for guarded SKIP with false guard (blocking behavior)
Add TestE2E_AltGuardedSkipFalseBlocking in codegen/e2e_misc_test.go — verify that when SKIP guard is false, the ALT blocks on the channel case and correctly receives.
Fix existing flaky test
TestE2E_AltGuardedSkipFalse (line 263) is currently racy — it passes by luck because Go's scheduler runs the sender goroutine before the receiver reaches the select. With the fix, the blocking select correctly waits for channel data.
Verification
go test ./... # all tests pass
go test ./codegen -run TestE2E_Alt # ALT-specific tests
./occam2go -o /tmp/life.go historical-examples/life.occ
go vet /tmp/life.go # compiles cleanly
go run /tmp/life.go # manual test: e→arrows→*→q→r works
If you need specific details from before exiting plan mode (like exact code snippets, error messages, or content you generated), read the full transcript at: /home/david/.claude/projects/-home-david-projects-code-associates-occam2go/218201bc-e51d-4c80-a4dc-65311974873c.jsonl