Replace src/build.sh with Makefile (GNU make)

New targets:
  help (default)     — show usage (was the -help/--help equivalent)
  compile            — compile main.c → build/nyuller.prg
  run                — compile + run in oscar64 built-in emulator
  run-vice           — compile + run in VICE x64 (foreground)
  run-vice-cycle     — compile + run in VICE x64sc (cycle-exact)
  play               — compile + launch VICE x64 detached
  play-cycle         — compile + launch VICE x64sc detached
  kill               — kill any detached VICE
  clean              — remove build/ artifacts
  build/nyuller.d64  — build the .d64 disk image from the .prg

Optimization: override with 'make OPT=O3' (or O0/O1/O2/Os/g).
Default is -O1 (oscar64 default).

The Makefile builds oscar64 automatically if missing, uses
c1541 to create the .d64 with verification, and passes
-drive8type 1541 to VICE (required for autostart).

Removed src/build.sh and updated all docs (README, tasks.md,
AGENT_CONTEXT.md, GAME.md, helloworld.c) to reference make.
This commit is contained in:
ballz
2026-07-18 16:22:38 +02:00
parent 3e9683a2fc
commit b7219dccfc
7 changed files with 276 additions and 260 deletions
+13 -12
View File
@@ -28,29 +28,30 @@ context you need to do the work.
├── source_images/ # The 5 source PNGs for the game screens
└── src/ # YOUR CODE GOES HERE
├── helloworld.c # Existing minimal working program
├── build.sh # Build script (handles compiler build, runs tests)
├── build.sh # Legacy build script (replaced by Makefile)
└── build/ # Output directory (gitignored)
```
## Build and test (this is the ONLY way to verify your work)
```sh
cd /home/ballz/work/teletype/nyuller/src
./build.sh # compile helloworld.c → build/helloworld.prg
./build.sh -e # run in oscar64's built-in emulator (HEADLESS, fast)
./build.sh -v # (DO NOT USE) VICE x64 — requires a real display
./build.sh -V # (DO NOT USE) VICE x64sc — same problem
cd /home/ballz/work/teletype/nyuller
make # show help with all targets
make compile # compile → build/nyuller.prg
make run # compile + run in oscar64 emulator (HEADLESS, fast)
make play # compile + launch VICE in background
make kill # kill any detached VICE
```
**The oscar64 built-in emulator (`-e`) is the only test tool.** VICE
**The oscar64 built-in emulator (`make run`) is the only test tool.** VICE
does not work in this headless environment (no display, blank
screenshots, manual ROM fetching). Do not waste time on VICE.
The `-e` flag runs the same `.prg` file the C64 will run, with no
The `run` target runs the same `.prg` file the C64 will run, with no
setup, no ROMs, and at high speed.
`build.sh` will auto-build the oscar64 compiler if it's missing.
It calls `oscar64 -i=/home/ballz/work/teletype/nyuller/oscar64/include -o=build/helloworld.prg helloworld.c`
to compile, and the same command with `-e` to run.
`make` will auto-build the oscar64 compiler if it's missing.
It runs `cd src && oscar64 -i=…/include -o=…/build/nyuller.prg main.c`
to compile, and `cd src && oscar64 -i=…/include -o=…/build/nyuller.prg -e main.c` to run.
The build artifacts in `build/` are: `helloworld.prg` (the C64 program),
`helloworld.asm` (6502 listing), `helloworld.map` (region/section/object
@@ -180,6 +181,6 @@ $21-$24) goes to $D800-$DBE7.
When you're done, report back:
1. What you built (1-2 sentence summary)
2. The output of `./build.sh -e` (proves it compiled and runs)
2. The output of `make run` (proves it compiled and runs)
3. The git commit hash and one-line summary
4. Any concerns or follow-up work for the next phase
-217
View File
@@ -1,217 +0,0 @@
#!/bin/sh
# build.sh — compile and optionally run a single C64 program with Oscar64.
#
# Usage:
# ./build.sh # compile main.c → build/nyuller.prg (-O1)
# ./build.sh -e # run in oscar64's built-in emulator (headless, fast)
# ./build.sh -p # PLAY: compile, wrap in .d64, launch VICE x64 detached
# ./build.sh -P # PLAY: same as -p but with VICE x64sc (cycle-exact)
# ./build.sh -v # run in VICE x64 (foreground, blocks terminal)
# ./build.sh -V # run in VICE x64sc (foreground, blocks terminal)
# ./build.sh --kill # kill any running VICE x64/x64sc process
# ./build.sh -c # just compile
# ./build.sh -O0 # compile with -O0 (no optimization; debug build)
# ./build.sh -O2 # compile with -O2 (more aggressive inlining)
# ./build.sh -O3 # compile with -O3 (release build; auto-ZP, outliner)
# ./build.sh -Os # compile with -Os (optimize for size)
# ./build.sh -g # compile with -g (adds source-level debug info)
#
# Output (in <repo>/build/):
# nyuller.prg — loadable C64 program (run with x64, VICE, or real hw)
# nyuller.asm — full 6502 listing
# nyuller.map — region/section/object placement
# nyuller.lbl — VICE monitor label commands
# nyuller.d64 — disk image wrapping nyuller.prg (for VICE autostart)
#
# VICE note: -drive8type 1541 is required for autostart to work. VICE
# defaults to the 1541-II (drive type 1542), whose ROM we don't have
# installed; without this flag the autostart `LOAD"*",8,1` fails with
# ?DEVICE NOT PRESENT. We have the original 1541 ROM at
# ~/.local/share/vice/DRIVES/dos1541-325302-01+901229-05.bin.
#
# For the development loop, use -e (oscar64's built-in emulator). It runs
# without a display, needs no ROMs, and is faster than VICE. Use -p when
# you want to play the game interactively — it launches VICE in the
# background and returns control to your shell so you can do other things.
#
# Default joystick keys in VICE (Settings → Input devices → Joystick
# settings to rebind if needed):
# Port 2 (left, Scoot): W/A/S/D for up/left/right/down, Left Ctrl for fire
# Port 1 (right, Hare): Arrow keys for up/left/right/down, Right Shift for fire
# (You can rebind under Settings → Input devices → Joystick settings.)
#
# Optimization: the default is oscar64's default (-O1). Pass -O3 to
# produce a release build (auto-zero-page, outliner, aggressive inlining).
# The release build is functionally identical to the default build on
# cycle-accurate timings — the audio and frame counters are still
# 50 Hz because they use the raster IRQ, not CPU-bound loops.
set -e
# --- locate the oscar64 compiler -----------------------------------------
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
OSCAR64_DIR="$ROOT/oscar64"
OSCAR64_BIN="$OSCAR64_DIR/bin/oscar64"
BUILD_DIR="$ROOT/build"
PRG="$BUILD_DIR/nyuller.prg"
D64="$BUILD_DIR/nyuller.d64"
mkdir -p "$BUILD_DIR"
# --- kill any running VICE ------------------------------------------------
if [ "${1:-}" = "--kill" ]; then
# pgrep -f uses ERE: + is a quantifier, so the regex must escape it.
# x64(sc)? matches both "x64" and "x64sc". The process is launched
# with the literal argument string `+confirmonexit`.
pids=$(pgrep -f "x64(sc)? \+confirmonexit.*nyuller\.d64" 2>/dev/null || true)
if [ -n "$pids" ]; then
echo "killing VICE process(es): $pids"
kill $pids 2>/dev/null || true
sleep 1
kill -9 $pids 2>/dev/null || true
else
echo "no VICE process running (autostart of nyuller.d64)"
fi
rm -f "$BUILD_DIR/vice.pid" "$BUILD_DIR/vice.log"
exit 0
fi
# Build the compiler if it's missing.
if [ ! -x "$OSCAR64_BIN" ]; then
echo "oscar64 compiler not found at $OSCAR64_BIN; building it..."
( cd "$OSCAR64_DIR" && make -C make compiler )
fi
if [ ! -x "$OSCAR64_BIN" ]; then
echo "error: $OSCAR64_BIN is still missing after build" >&2
echo " try: cd $OSCAR64_DIR && make -C make compiler" >&2
exit 1
fi
# --- parse args -----------------------------------------------------------
SRC=main.c
EMU_CMD=""
PLAY_VICE=""
OPT_FLAGS=""
for arg in "$@"; do
case "$arg" in
-e) EMU_CMD="oscar64" ;;
-v) EMU_CMD="x64" ;;
-V) EMU_CMD="x64sc" ;;
-p) PLAY_VICE="x64" ;;
-P) PLAY_VICE="x64sc" ;;
-c) ;;
-O0) OPT_FLAGS="$OPT_FLAGS -O0" ;;
-O1) OPT_FLAGS="$OPT_FLAGS -O1" ;;
-O2) OPT_FLAGS="$OPT_FLAGS -O2" ;;
-O3) OPT_FLAGS="$OPT_FLAGS -O3" ;;
-Os) OPT_FLAGS="$OPT_FLAGS -Os" ;;
-g) OPT_FLAGS="$OPT_FLAGS -g" ;;
-*) echo "unknown flag: $arg" >&2; exit 1 ;;
esac
done
# Reject conflicting flag combinations: -v/-V (foreground VICE) and
# -p/-P (background VICE) would both fire, leaving two emulator windows.
if [ -n "$EMU_CMD" ] && [ -n "$PLAY_VICE" ]; then
echo "error: cannot combine a foreground VICE flag (-v/-V) with a play flag (-p/-P)" >&2
echo " use -v to run in the foreground, or -p to detach and continue" >&2
exit 1
fi
# --- compile --------------------------------------------------------------
echo "compiling $SRC with $OSCAR64_BIN -> $BUILD_DIR/"
"$OSCAR64_BIN" -i="$OSCAR64_DIR/include" -o="$PRG" $OPT_FLAGS "$SRC"
# build_d64() — wrap $PRG in $D64. c1541's exit code is unreliable
# (the interactive form always exits 0 even on failure), so the only
# reliable success signal is listing the disk afterwards and checking
# the PRG is there. The flag-form `c1541 -list image` doesn't work
# (c1541 treats it as a unit number), so we use the interactive
# heredoc form. All c1541 calls are wrapped in `|| true` so set -e
# doesn't trip on transient non-zero exits from the format / write
# steps; the final list-and-grep is the authoritative check.
build_d64() {
if ! command -v c1541 >/dev/null 2>&1; then
echo "error: c1541 not found (needed to build the .d64 wrapper)" >&2
return 1
fi
c1541 -format ny,of d64 "$D64" >/dev/null 2>&1 || true
c1541 "$D64" >/dev/null 2>&1 <<EOF || true
write $PRG
EOF
if c1541 "$D64" 2>/dev/null <<EOF | grep -q "nyuller.prg"
list
quit
EOF
then
:
else
echo "error: failed to write $PRG to $D64 (c1541 silently exited 0)" >&2
return 1
fi
}
# --- optional actions -----------------------------------------------------
case "$EMU_CMD" in
"oscar64")
echo "running nyuller.prg in oscar64's built-in emulator"
"$OSCAR64_BIN" -i="$OSCAR64_DIR/include" -o="$PRG" $OPT_FLAGS -e "$SRC"
;;
"x64"|"x64sc")
echo "wrapping $PRG in $D64 (VICE autostart needs a disk image)..."
build_d64
if [ -z "${DISPLAY:-}" ]; then
echo "warning: no \$DISPLAY set; VICE won't show a window" >&2
fi
echo "running nyuller in VICE ($EMU_CMD, blocking)"
"$EMU_CMD" +confirmonexit -drive8type 1541 -autostart "$D64"
;;
esac
if [ -n "$PLAY_VICE" ]; then
if [ -z "${DISPLAY:-}" ]; then
echo "error: no \$DISPLAY set; cannot launch VICE in a window" >&2
exit 1
fi
if ! command -v "$PLAY_VICE" >/dev/null 2>&1; then
echo "error: $PLAY_VICE not found in PATH" >&2
exit 1
fi
# If a previous VICE is still running, kill it before launching a
# new one — otherwise the two windows will fight for the same
# display and the old one's nyuller will keep running in the
# background.
existing=$(pgrep -f "x64(sc)? \+confirmonexit.*nyuller\.d64" 2>/dev/null || true)
if [ -n "$existing" ]; then
echo "killing previous VICE process(es): $existing"
kill $existing 2>/dev/null || true
sleep 1
kill -9 $existing 2>/dev/null || true
fi
echo "wrapping $PRG in $D64 (VICE autostart needs a disk image)..."
build_d64
echo "launching VICE $PLAY_VICE in the background with $D64..."
log="$BUILD_DIR/vice.log"
( setsid nohup "$PLAY_VICE" +confirmonexit -drive8type 1541 -autostart "$D64" \
> "$log" 2>&1 < /dev/null & echo $! > "$BUILD_DIR/vice.pid" )
# setsid + nohup detach VICE from this shell so it survives our exit.
pid=$(cat "$BUILD_DIR/vice.pid")
sleep 1
if kill -0 "$pid" 2>/dev/null; then
echo ""
echo "VICE launched (PID $pid). Look for the x64 window on your desktop."
echo "Default keys: Port 1 (Hare) = Arrows + Right Shift (fire)"
echo " Port 2 (Scoot) = W A S D + Left Ctrl (fire)"
echo "Rebind under Settings → Input devices → Joystick settings."
echo "To stop VICE later: ./build.sh --kill"
echo "Log file: $log"
else
echo "error: VICE failed to start; see $log for details" >&2
exit 1
fi
fi
echo "done: $PRG"
+2 -3
View File
@@ -2,9 +2,8 @@
//
// Builds a .prg that prints "Hello World" on the C64 text screen and exits.
//
// Build: ../scripts/build.sh
// Run: x64 helloworld.prg (or use ../scripts/build.sh -e to run in
// the built-in oscar64 emulator)
// Build: make compile
// Run: make run
#include <stdio.h>