Add NUFLI image pipeline
- Add mufflon C source (gitignored, auto-downloaded by make) - Add tools/nuf_to_asm.py for converting .nuf to oscar64 assembly - Add Makefile targets: nufli, nufli-clean, ensure-mufflon - Generate NUFLI .asm/.h files for all 5 screens - Add NUFLI integration plan to tasks.md
This commit is contained in:
+16
@@ -25,6 +25,22 @@ oscar64/build/
|
|||||||
__pycache__/
|
__pycache__/
|
||||||
*.pyc
|
*.pyc
|
||||||
|
|
||||||
|
# python venvs
|
||||||
|
tools/img-convert/venv/
|
||||||
|
|
||||||
|
# mufflon source (not a submodule, from SVN repo)
|
||||||
|
mufflon/
|
||||||
|
|
||||||
|
# mufflon build output
|
||||||
|
build/mufflon
|
||||||
|
|
||||||
|
# NUFLI generated files (BMP intermediates, .nuf output, result/error maps)
|
||||||
|
src/data/nufli/*.bmp
|
||||||
|
src/data/nufli/*.nuf
|
||||||
|
|
||||||
|
# temp / comparison output
|
||||||
|
tmp/
|
||||||
|
|
||||||
# editor / IDE
|
# editor / IDE
|
||||||
.vscode/
|
.vscode/
|
||||||
.idea/
|
.idea/
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ ROOT := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
|
|||||||
OSCAR64_DIR := $(ROOT)/oscar64
|
OSCAR64_DIR := $(ROOT)/oscar64
|
||||||
OSCAR64_BIN := $(OSCAR64_DIR)/bin/oscar64
|
OSCAR64_BIN := $(OSCAR64_DIR)/bin/oscar64
|
||||||
BUILD_DIR := $(ROOT)/build
|
BUILD_DIR := $(ROOT)/build
|
||||||
|
MUFFLON_DIR := $(ROOT)/mufflon
|
||||||
|
MUFFLON_BIN := $(BUILD_DIR)/mufflon
|
||||||
|
TOOLS_DIR := $(ROOT)/tools
|
||||||
|
|
||||||
SRC := main.c
|
SRC := main.c
|
||||||
PRG := $(BUILD_DIR)/nyuller.prg
|
PRG := $(BUILD_DIR)/nyuller.prg
|
||||||
@@ -17,6 +20,10 @@ LOG := $(BUILD_DIR)/vice.log
|
|||||||
PID_FILE := $(BUILD_DIR)/vice.pid
|
PID_FILE := $(BUILD_DIR)/vice.pid
|
||||||
SRC_DIR := $(ROOT)/src
|
SRC_DIR := $(ROOT)/src
|
||||||
|
|
||||||
|
# --- NUFLI source images -----------------------------------
|
||||||
|
NUFLI_SRC_DIR := $(ROOT)/source_images
|
||||||
|
NUFLI_OUT_DIR := $(SRC_DIR)/data/nufli
|
||||||
|
|
||||||
# --- oscar64 flags ----------------------------------------
|
# --- oscar64 flags ----------------------------------------
|
||||||
# Override with: make OPT=O3 (or O0/O1/O2/Os/g)
|
# Override with: make OPT=O3 (or O0/O1/O2/Os/g)
|
||||||
OPT ?= O1
|
OPT ?= O1
|
||||||
@@ -31,7 +38,8 @@ HDR_FILES := $(wildcard $(SRC_DIR)/*.h)
|
|||||||
|
|
||||||
# --- phony targets ----------------------------------------
|
# --- phony targets ----------------------------------------
|
||||||
.PHONY: help compile run run-vice run-vice-cycle \
|
.PHONY: help compile run run-vice run-vice-cycle \
|
||||||
play play-cycle kill clean ensure-build-dir ensure-oscar64
|
play play-cycle kill clean ensure-build-dir ensure-oscar64 \
|
||||||
|
ensure-mufflon nufli nufli-clean
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# help (default)
|
# help (default)
|
||||||
@@ -61,6 +69,10 @@ help:
|
|||||||
@echo " Clean"
|
@echo " Clean"
|
||||||
@echo " make clean remove build/ artifacts and VICE log/pid files"
|
@echo " make clean remove build/ artifacts and VICE log/pid files"
|
||||||
@echo ""
|
@echo ""
|
||||||
|
@echo " NUFLI images"
|
||||||
|
@echo " make nufli convert source PNGs → NUFLI assembly data"
|
||||||
|
@echo " make nufli-clean remove NUFLI generated files"
|
||||||
|
@echo ""
|
||||||
@echo " Output (in build/)"
|
@echo " Output (in build/)"
|
||||||
@echo " nyuller.prg loadable C64 program"
|
@echo " nyuller.prg loadable C64 program"
|
||||||
@echo " nyuller.d64 disk image wrapping nyuller.prg (for VICE autostart)"
|
@echo " nyuller.d64 disk image wrapping nyuller.prg (for VICE autostart)"
|
||||||
@@ -101,10 +113,74 @@ ensure-oscar64:
|
|||||||
exit 1; \
|
exit 1; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# ensure-mufflon — download source + build if missing
|
||||||
|
# ============================================================
|
||||||
|
ensure-mufflon: ensure-build-dir
|
||||||
|
@if [ ! -f "$(MUFFLON_DIR)/mufflon.c" ]; then \
|
||||||
|
echo "mufflon source not found; downloading from CSDb..."; \
|
||||||
|
mkdir -p "$(MUFFLON_DIR)"; \
|
||||||
|
curl -sL "https://csdb.dk/getinternalfile.php/180585/Mufflon1.0-source+GUI+Bonus.zip" -o "$(BUILD_DIR)/mufflon.zip"; \
|
||||||
|
cd "$(BUILD_DIR)" && unzip -qo mufflon.zip "Mufflon1.0-source+Bonus/mufflon-source+GUI/mufflon.c" "Mufflon1.0-source+Bonus/mufflon-source+GUI/mufflon.h"; \
|
||||||
|
cp "$(BUILD_DIR)/Mufflon1.0-source+Bonus/mufflon-source+GUI/mufflon.c" "$(MUFFLON_DIR)/"; \
|
||||||
|
cp "$(BUILD_DIR)/Mufflon1.0-source+Bonus/mufflon-source+GUI/mufflon.h" "$(MUFFLON_DIR)/"; \
|
||||||
|
rm -rf "$(BUILD_DIR)/Mufflon1.0-source+Bonus" "$(BUILD_DIR)/mufflon.zip"; \
|
||||||
|
fi
|
||||||
|
@if [ ! -f "$(MUFFLON_DIR)/mufflon.c" ]; then \
|
||||||
|
echo "error: mufflon source download failed" >&2; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
@if [ ! -x "$(MUFFLON_BIN)" ]; then \
|
||||||
|
echo "building mufflon -> $(MUFFLON_BIN)"; \
|
||||||
|
cd "$(MUFFLON_DIR)" && gcc -o "$(MUFFLON_BIN)" -lm -O5 -ffast-math mufflon.c; \
|
||||||
|
fi
|
||||||
|
@if [ ! -x "$(MUFFLON_BIN)" ]; then \
|
||||||
|
echo "error: $(MUFFLON_BIN) is still missing after build" >&2; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# NUFLI image pipeline
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
# Source screen images (PNG)
|
||||||
|
NUFLI_SCREENS := screen_title screen_waiting1 screen_waiting_2 screen_win_hare screen_win_scoot
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
NUFLI_NUF_FILES := $(addprefix $(NUFLI_OUT_DIR)/,$(addsuffix .nuf,$(NUFLI_SCREENS)))
|
||||||
|
NUFLI_ASM_FILES := $(addprefix $(NUFLI_OUT_DIR)/,$(addsuffix .asm,$(NUFLI_SCREENS)))
|
||||||
|
NUFLI_HDR_FILES := $(addprefix $(NUFLI_OUT_DIR)/,$(addsuffix .h,$(NUFLI_SCREENS)))
|
||||||
|
|
||||||
|
# Build all NUFLI images
|
||||||
|
nufli: $(NUFLI_ASM_FILES) $(NUFLI_HDR_FILES)
|
||||||
|
@echo "NUFLI images built in $(NUFLI_OUT_DIR)/"
|
||||||
|
|
||||||
|
# Clean NUFLI artifacts
|
||||||
|
nufli-clean:
|
||||||
|
@rm -f $(NUFLI_OUT_DIR)/*.nuf $(NUFLI_OUT_DIR)/*.asm $(NUFLI_OUT_DIR)/*.h
|
||||||
|
@rm -f $(NUFLI_OUT_DIR)/*.bmp
|
||||||
|
@echo "cleaned NUFLI images"
|
||||||
|
|
||||||
|
# PNG → BMP (Mufflon needs BMP input)
|
||||||
|
$(NUFLI_OUT_DIR)/%.bmp: $(NUFLI_SRC_DIR)/%.png | ensure-build-dir
|
||||||
|
@mkdir -p $(NUFLI_OUT_DIR)
|
||||||
|
@echo "converting $< -> $@"
|
||||||
|
@python3 -c "from PIL import Image; Image.open('$<').resize((320,200), Image.LANCZOS).save('$@')"
|
||||||
|
|
||||||
|
# BMP → NUFLI (.nuf)
|
||||||
|
$(NUFLI_OUT_DIR)/%.nuf: $(NUFLI_OUT_DIR)/%.bmp | ensure-mufflon
|
||||||
|
@echo "converting $< -> $@"
|
||||||
|
@$(MUFFLON_BIN) $< -o $@ --shutup
|
||||||
|
|
||||||
|
# NUFLI → Assembly (.asm + .h)
|
||||||
|
$(NUFLI_OUT_DIR)/%.asm $(NUFLI_OUT_DIR)/%.h: $(NUFLI_OUT_DIR)/%.nuf
|
||||||
|
@echo "converting $< -> $@ + $(basename $@).h"
|
||||||
|
@python3 $(TOOLS_DIR)/nuf_to_asm.py $< $(NUFLI_OUT_DIR)/$*
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# $(PRG) — compile main.c → nyuller.prg
|
# $(PRG) — compile main.c → nyuller.prg
|
||||||
# ============================================================
|
# ============================================================
|
||||||
$(PRG): $(SRC_FILES) $(HDR_FILES) | ensure-build-dir ensure-oscar64
|
$(PRG): $(SRC_FILES) $(HDR_FILES) $(NUFLI_ASM_FILES) | ensure-build-dir ensure-oscar64
|
||||||
@echo "compiling $(SRC) with $(OSCAR64_BIN) -> $(BUILD_DIR)/"
|
@echo "compiling $(SRC) with $(OSCAR64_BIN) -> $(BUILD_DIR)/"
|
||||||
cd "$(SRC_DIR)" && "$(OSCAR64_BIN)" -i="$(OSCAR64_DIR)/include" -o="$(PRG)" $(OPT_FLAGS) "$(SRC)"
|
cd "$(SRC_DIR)" && "$(OSCAR64_BIN)" -i="$(OSCAR64_DIR)/include" -o="$(PRG)" $(OPT_FLAGS) "$(SRC)"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/* NUFLI image data - generated by nuf_to_asm.py */
|
||||||
|
#ifndef SCREEN_TITLE_H
|
||||||
|
#define SCREEN_TITLE_H
|
||||||
|
|
||||||
|
/* NUFLI data size: 23040 bytes */
|
||||||
|
extern const unsigned char nufli_data[23040];
|
||||||
|
extern const unsigned int nufli_size;
|
||||||
|
|
||||||
|
/* Display NUFLI image */
|
||||||
|
void nufli_display(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/* NUFLI image data - generated by nuf_to_asm.py */
|
||||||
|
#ifndef SCREEN_WAITING1_H
|
||||||
|
#define SCREEN_WAITING1_H
|
||||||
|
|
||||||
|
/* NUFLI data size: 23040 bytes */
|
||||||
|
extern const unsigned char nufli_data[23040];
|
||||||
|
extern const unsigned int nufli_size;
|
||||||
|
|
||||||
|
/* Display NUFLI image */
|
||||||
|
void nufli_display(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/* NUFLI image data - generated by nuf_to_asm.py */
|
||||||
|
#ifndef SCREEN_WAITING_2_H
|
||||||
|
#define SCREEN_WAITING_2_H
|
||||||
|
|
||||||
|
/* NUFLI data size: 23040 bytes */
|
||||||
|
extern const unsigned char nufli_data[23040];
|
||||||
|
extern const unsigned int nufli_size;
|
||||||
|
|
||||||
|
/* Display NUFLI image */
|
||||||
|
void nufli_display(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/* NUFLI image data - generated by nuf_to_asm.py */
|
||||||
|
#ifndef SCREEN_WIN_HARE_H
|
||||||
|
#define SCREEN_WIN_HARE_H
|
||||||
|
|
||||||
|
/* NUFLI data size: 23040 bytes */
|
||||||
|
extern const unsigned char nufli_data[23040];
|
||||||
|
extern const unsigned int nufli_size;
|
||||||
|
|
||||||
|
/* Display NUFLI image */
|
||||||
|
void nufli_display(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/* NUFLI image data - generated by nuf_to_asm.py */
|
||||||
|
#ifndef SCREEN_WIN_SCOOT_H
|
||||||
|
#define SCREEN_WIN_SCOOT_H
|
||||||
|
|
||||||
|
/* NUFLI data size: 23040 bytes */
|
||||||
|
extern const unsigned char nufli_data[23040];
|
||||||
|
extern const unsigned int nufli_size;
|
||||||
|
|
||||||
|
/* Display NUFLI image */
|
||||||
|
void nufli_display(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -360,6 +360,73 @@ These items were done after Phase 8 was marked complete:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## NUFLI Integration Plan
|
||||||
|
|
||||||
|
**Goal:** Replace the current multicolor bitmap screens with NUFLI format
|
||||||
|
for static screens (title, win, gameover). NUFLI provides 320×200 resolution
|
||||||
|
with ~10+ colors per block vs the current 160×200 with 4 colors per cell.
|
||||||
|
|
||||||
|
### Architecture
|
||||||
|
|
||||||
|
**Hybrid approach:**
|
||||||
|
- **Static screens** (title, win_hare, win_scoot, gameover): NUFLI format
|
||||||
|
- **Animated screens** (waiting1, waiting2, draw): Keep current multicolor bitmap
|
||||||
|
|
||||||
|
**Why hybrid:**
|
||||||
|
- NUFLI consumes 100% CPU during display (no game logic possible)
|
||||||
|
- NUFLI uses all 8 sprites (no sprites for game objects)
|
||||||
|
- Static screens don't need game logic, so NUFLI is perfect
|
||||||
|
|
||||||
|
### Implementation Steps
|
||||||
|
|
||||||
|
#### Step 1: NUFLI display routine integration
|
||||||
|
- [ ] Create `src/nufli.h` with display function declarations
|
||||||
|
- [ ] Create `src/nufli.c` with display routine wrapper:
|
||||||
|
- `nufli_show(const unsigned char *data)` — loads data to $2000, calls SYS 12288
|
||||||
|
- `nufli_exit()` — restores normal video mode
|
||||||
|
- [ ] Add `nufli_display.asm` to `src/` (6502 assembly for bank switching + JSR $3000)
|
||||||
|
|
||||||
|
#### Step 2: Screen state machine updates
|
||||||
|
- [ ] Modify `game_enter_title()` to use NUFLI for title screen
|
||||||
|
- [ ] Modify `game_enter_win_p1/p2()` to use NUFLI for win screens
|
||||||
|
- [ ] Modify `game_enter_gameover()` to use NUFLI for gameover screen
|
||||||
|
- [ ] Keep existing multicolor for waiting/draw screens
|
||||||
|
|
||||||
|
#### Step 3: Memory management
|
||||||
|
- [ ] Ensure NUFLI data ($2000-$7FFF) doesn't conflict with game code
|
||||||
|
- [ ] Verify screen RAM at $C400 doesn't overlap with NUFLI bitmap
|
||||||
|
- [ ] Test sprite pointer setup (NUFLI uses bank 3 sprites)
|
||||||
|
|
||||||
|
#### Step 4: State transitions
|
||||||
|
- [ ] Implement `nufli_exit()` to restore VIC-II state before returning to game
|
||||||
|
- [ ] Ensure raster IRQ is re-enabled after NUFLI display
|
||||||
|
- [ ] Test TITLE→READY transition (NUFLI→multicolor)
|
||||||
|
|
||||||
|
#### Step 5: Build pipeline integration
|
||||||
|
- [x] ✅ `make nufli` generates .asm files from source PNGs
|
||||||
|
- [x] ✅ NUFLI .asm files are build dependencies
|
||||||
|
- [ ] Add `#pragma embed` or linker includes for NUFLI data
|
||||||
|
- [ ] Verify total binary size fits in C64 memory
|
||||||
|
|
||||||
|
### Memory Map (NUFLI mode)
|
||||||
|
```
|
||||||
|
$2000-$7FFF: NUFLI data (bitmap + sprites + color tables)
|
||||||
|
$8000-$9FFF: Game code (oscar64 default)
|
||||||
|
$C000-$C3FF: Screen RAM (for multicolor screens)
|
||||||
|
$C400-$C7FF: Screen RAM (for NUFLI underlays)
|
||||||
|
$E000-$FFFF: Bitmap RAM (for multicolor screens)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verify
|
||||||
|
- `make nufli` generates all 5 screen .asm files
|
||||||
|
- `make compile` builds successfully with NUFLI data included
|
||||||
|
- `make run`: title screen displays in NUFLI quality (320×200, many colors)
|
||||||
|
- Press fire → transitions to multicolor waiting screen
|
||||||
|
- Win → displays NUFLI win screen
|
||||||
|
- Gameover → displays NUFLI gameover screen
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Phase 10 — Code review fixes (pending)
|
## Phase 10 — Code review fixes (pending)
|
||||||
|
|
||||||
Findings from the second round of 4-agent parallel code review.
|
Findings from the second round of 4-agent parallel code review.
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Convert NUFLI .nuf binary to oscar64-compatible assembly/include files.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python3 nuf_to_asm.py input.nuf output_base
|
||||||
|
|
||||||
|
Output files:
|
||||||
|
output_base.asm - Assembly data file
|
||||||
|
output_base.h - C header with extern declarations
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def nuf_to_asm(nuf_data, output_base):
|
||||||
|
"""Convert NUFLI binary to assembly data."""
|
||||||
|
out_path = Path(output_base)
|
||||||
|
|
||||||
|
# Skip 2-byte load address header
|
||||||
|
data = nuf_data[2:]
|
||||||
|
|
||||||
|
# Assembly data file
|
||||||
|
with open(out_path.with_suffix('.asm'), 'w') as f:
|
||||||
|
f.write("; NUFLI image data - generated by nuf_to_asm.py\n")
|
||||||
|
f.write("; Load at $2000, display with SYS 12288 ($3000)\n")
|
||||||
|
f.write(f"; Total size: {len(data)} bytes\n\n")
|
||||||
|
f.write(".segment \"NUFLI_DATA\"\n\n")
|
||||||
|
|
||||||
|
# Export the data
|
||||||
|
f.write(".export _nufli_data\n")
|
||||||
|
f.write(".export _nufli_size\n\n")
|
||||||
|
|
||||||
|
f.write("_nufli_data:\n")
|
||||||
|
|
||||||
|
# Write data in rows of 16 bytes
|
||||||
|
for i in range(0, len(data), 16):
|
||||||
|
chunk = data[i:i+16]
|
||||||
|
hex_bytes = ', '.join(f'${b:02x}' for b in chunk)
|
||||||
|
f.write(f" .byte {hex_bytes}\n")
|
||||||
|
|
||||||
|
f.write(f"\n_nufli_size = {len(data)}\n")
|
||||||
|
|
||||||
|
# C header file
|
||||||
|
with open(out_path.with_suffix('.h'), 'w') as f:
|
||||||
|
f.write("/* NUFLI image data - generated by nuf_to_asm.py */\n")
|
||||||
|
f.write(f"#ifndef {out_path.name.upper().replace('.', '_')}_H\n")
|
||||||
|
f.write(f"#define {out_path.name.upper().replace('.', '_')}_H\n\n")
|
||||||
|
f.write(f"/* NUFLI data size: {len(data)} bytes */\n")
|
||||||
|
f.write(f"extern const unsigned char nufli_data[{len(data)}];\n")
|
||||||
|
f.write(f"extern const unsigned int nufli_size;\n\n")
|
||||||
|
f.write("/* Display NUFLI image */\n")
|
||||||
|
f.write("void nufli_display(void);\n\n")
|
||||||
|
f.write("#endif\n")
|
||||||
|
|
||||||
|
print(f"Wrote {out_path.with_suffix('.asm')} ({len(data)} bytes)")
|
||||||
|
print(f"Wrote {out_path.with_suffix('.h')}")
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv=None):
|
||||||
|
ap = argparse.ArgumentParser(
|
||||||
|
description="Convert NUFLI .nuf binary to oscar64 assembly"
|
||||||
|
)
|
||||||
|
ap.add_argument("input", help="Input .nuf file")
|
||||||
|
ap.add_argument("output_base", help="Output base path (no extension)")
|
||||||
|
args = ap.parse_args(argv)
|
||||||
|
|
||||||
|
in_path = Path(args.input)
|
||||||
|
if not in_path.exists():
|
||||||
|
print(f"Error: {in_path} not found", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
data = in_path.read_bytes()
|
||||||
|
nuf_to_asm(data, args.output_base)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user