New Features:
- Added comprehensive flag audit tool to scan scripts for flag usage and compare against flags sheet
- GraalVM Native Image support via GluonFX for macOS (ARM/Intel) and Windows builds
- Build scripts and documentation for native executable creation
Bug Fixes:
- Fixed script editor WebView not resizing properly when window size changes
- Fixed duplicate script editor windows opening on first script open
- Fixed spreadsheet cell editor retaining previous cell values
Native Image:
- Agent-generated reflection configuration for JavaFX FXML
- Complete JNI, serialization, and resource configuration
- DMG installer generation for macOS distributions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add versions-maven-plugin to pom.xml (configured to not create backup poms)
- Add bump-minor.sh script to automatically increment minor version (e.g., 3.5 -> 3.6)
- Add bump-major.sh script to automatically increment major version (e.g., 3.5 -> 4.0)
Usage:
./bump-minor.sh # Increment minor: 3.5 -> 3.6
./bump-major.sh # Increment major: 3.5 -> 4.0
Scripts automatically parse current version and update pom.xml using mvn versions:set.
Co-Authored-By: Claude <noreply@anthropic.com>
- Update canonical version to 3.5 in pom.xml
- Enable Maven resource filtering for version.properties
- Update VersionInfo to dynamically read game data version from packaged disk
- Combine app version (from pom.xml) with game data version (from disk)
- Version display now shows: {app-version}-{game-data-version} (e.g., "3.5-6119q.99")
- Add maven.build.timestamp.format for consistent build date formatting
- Remove hardcoded version from version.properties (now uses ${project.version})
This ensures the info modal always displays the true canonical version from
pom.xml combined with the actual game data version extracted from the game disk.
Co-Authored-By: Claude <noreply@anthropic.com>
This commit corrects test assertions to validate the fixes implemented
in commit e2d6af71 rather than expecting the bugs to be present.
Changes:
1. Apple2e.warmStart() - Added documentation clarifying video switch
reset behavior per Sather 4-15 (Apple IIe Technical Reference)
2. RebootStabilityStressTest - Updated from reproduction mode to
validation mode:
- test3_StateResetVerification: Now expects callbacks to be cleared
(assertEquals 0) instead of expecting them to persist (bug behavior)
- test4_FullRebootCycleStress: Now expects high success rate (>=90%)
instead of expecting failures (bug behavior)
Test Results:
- All 6 WarmStartVideoResetTest cases pass (video switches properly reset)
- All 4 RebootStabilityStressTest cases pass (100% success rate)
- Total: 330 tests, 329 passing (1 unrelated ConcurrentModification error
in DeveloperBypassModeTest)
Co-Authored-By: Claude <noreply@anthropic.com>
Addresses GitHub issue #11 by implementing comprehensive fixes for
race conditions and state management issues causing unstable reboots
after game upgrades.
Fixes implemented:
1. Atomic boot watchdog guard - Prevents concurrent watchdog instances
using AtomicBoolean to avoid death spiral scenarios
2. Upgrade/boot synchronization - Added CountDownLatch to ensure boot
waits for upgrade completion before proceeding
3. VBL callback clearing - Clear vblCallbacks list in warmStart() to
prevent callback accumulation across reboots
4. Worker thread synchronization - Added CountDownLatch in
IndependentTimedDevice.resume() to ensure worker threads fully
start before isRunning() returns true
5. Defensive VBL checks with timeout - Enhanced waitForVBL() with
worker thread liveness checks and 2-second timeout to prevent
infinite hangs on VBL semaphore deadlock
6. Disk indicator crash fix - Changed DiskIIDrive to use Optional
ifPresent() pattern instead of unsafe get() for headless operation
7. Memory cache invalidation - Added cache clearing in
RAM128k.resetState() to prevent stale memory configuration after
upgrades
8. Complete coldStart state cleanup - Restructured coldStart() to
perform ALL cleanup and reinitialization inside whileSuspended()
before worker threads resume, eliminating race windows
9. Boot watchdog coverage for upgrades - Changed LawlessImageTool
reboot paths to use bootWatchdog() instead of coldStart() for
consistent failure detection and recovery
10. Boot watchdog debounce with BASIC detection - Implemented
exponential backoff retry mechanism (500ms→1s→2s→4s→8s) with
BASIC prompt detection to automatically recover from boot failures
11. Watchdog retry timing optimization - Reduced initial retry delay
from 2s to 500ms for faster recovery
Test coverage: Added comprehensive stress test suite with 4 test
scenarios covering upgrade timing races, concurrent boot watchdog,
state reset verification, and full reboot cycles.
Co-Authored-By: Claude <noreply@anthropic.com>
The previous buffer size of 2048 was causing choppy audio playback on
newer systems. Increasing to 4096 provides smoother audio performance.
Co-Authored-By: Claude <noreply@anthropic.com>
Added check to unmount any existing "Lawless Legends" volumes before
running hdiutil create. This prevents "resource busy" errors when a
previous DMG is still mounted or when rebuilding.
Changes:
- Detach /Volumes/Lawless Legends before creating DMG
- Add 1 second delay to ensure unmount completes
- Suppress error if no volume is mounted (|| true)
This fixes the issue where running the build script multiple times
would fail with "hdiutil: create failed - Resource busy".
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Applied same improvements as ARM build script to build_mac_intel.sh:
- Creates proper .app bundle structure (no terminal window)
- Generates Info.plist with bundle metadata
- Packages into LawlessLegends-3.1-macOS-intel.dmg
- Includes README.txt with installation instructions
- Includes Applications folder symlink for drag-and-drop install
- Uses /tmp for staging to avoid resource conflicts
- Copies final DMG to Desktop
Now both ARM and Intel builds produce professional DMG installers
with the same user experience and installation instructions.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Added comprehensive README.txt with:
- macOS installation instructions for unsigned apps (right-click > Open)
- Complete game description inspired by box art
- California Gold Rush 1856 wild west RPG setting
- Feature list (20+ skills, 130 foes, 25 locations, ray-casted 3D, etc.)
- System requirements (macOS 11.0+, Apple Silicon)
Updated build_mac_arm.sh to create distributable DMG:
- Creates LawlessLegends-3.1-macOS-arm64.dmg
- Includes lawlesslegends.app bundle
- Includes README.txt with installation instructions
- Includes Applications folder symlink for easy drag-and-drop install
- Uses /tmp for DMG staging to avoid resource conflicts
- Copies final DMG to Desktop
The DMG provides a professional distribution format that:
- Shows users exactly how to install (drag to Applications)
- Explains macOS Gatekeeper workaround for unsigned apps
- Presents the game with proper context and features
- Follows standard macOS app distribution conventions
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Modified build_mac_arm.sh to automatically create a proper .app bundle
structure instead of copying the raw executable to Desktop.
Changes:
- Creates lawlesslegends.app/Contents/MacOS/ directory structure
- Copies executable into Contents/MacOS/
- Copies icon.icns into Contents/Resources/
- Generates Info.plist with proper bundle metadata
- Copies complete .app bundle to Desktop
Benefits:
- No terminal window when launching (GUI app, not CLI tool)
- Appears in Dock properly
- Has correct icon
- Follows macOS app bundle conventions
This eliminates the issue where double-clicking the raw executable
would open a terminal window.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The native image was crashing because game.2mg wasn't registered in the
resource-config.json file. When GraalVM native image tries to access an
unregistered resource, it results in a segfault.
Changes:
- Added jace/data/game.2mg to resource-config.json includes
- Improved error handling with try-finally to ensure stream closure
- Added better error messages for debugging native image issues
- Added catch for Throwable to prevent silent crashes
This fixes the SIGKILL (exit code 137) crash when launching the native
compiled version.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The copyResource() method was checking if the storage file exists and
copying from it (effectively copying storage onto itself). This meant
version upgrades never actually replaced the file with the newer version.
Now we bypass copyResource() and copy directly from the packaged resource
when performing version upgrades, ensuring the new version is actually
written to storage.
This fixes the issue where the log showed "Packaged: 5723p.99, Storage: 4n23k.99"
and "Packaged game is newer - replacing storage file", but the storage file
never actually got the new version.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
After removing the properties file tracking, tests needed updates:
- Create .lkg backups before upgrade scenarios (matching getGamePath() behavior)
- Pass wasJustReplaced=true for actual upgrade scenarios
- Remove assertions about properties file tracking (size, timestamp)
- Focus on verifying actual upgrade behavior (save game preservation)
All 295 tests now passing.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Problem: GameVersionTracker was maintaining a properties file that duplicated
information already in the game files. This caused synchronization issues -
after copying a newer packaged game, the properties file still had the new
version from a previous run, so no upgrade was triggered.
Solution: Eliminate the properties file entirely and use direct comparison:
1. getGamePath() compares packaged vs storage game versions
2. If packaged > storage: backs up old as .lkg, copies new, sets flag
3. UpgradeHandler.checkAndHandleUpgrade(file, wasReplaced) checks flag
4. If wasReplaced=true: performs silent upgrade (extract save from .lkg)
5. If wasReplaced=false: just updates .lkg backup
Benefits:
- No synchronization issues between files and properties
- Simpler logic - single source of truth (the game files themselves)
- No redundant version tracking
- Easier to understand and maintain
Changes:
- UpgradeHandler.checkAndHandleUpgrade() now takes boolean wasJustReplaced
- Removed all GameVersionTracker saveVersionInfo() calls
- LawlessImageTool tracks storageFileWasJustReplaced flag
- Tests updated to match new behavior (9 integration tests still need fixes)
Note: Some integration tests still failing - they expect .lkg to pre-exist
before upgrade runs, which matches real flow but needs test setup adjustments.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Problem 1: After detecting that packaged game is newer and copying it to
storage, the upgrade handler would see "Game is current" because the
version.properties file still had the new version from a previous run.
Problem 2: Native image compilation would crash during version extraction,
likely due to temp file creation or missing error handling.
Problem 3: Excessive logging from PartitionParser cluttered the output.
Solutions:
1. **Force upgrade detection after copy**:
- Before copying packaged to storage, back up old storage as .lkg
- After copying, delete version.properties to force UNKNOWN status
- UpgradeHandler will then detect and perform silent upgrade
2. **Robust error handling for native mode**:
- Wrap all version extraction in try-catch blocks
- Better temp directory handling with /tmp fallback
- Immediate cleanup of temp files (deleteOnExit may not work in native)
- Graceful degradation if version extraction fails
3. **Reduce log spam**:
- Changed PartitionParser INFO logs to FINE
- Removed "Parsed X chunks" and "Found resourceIndex" noise
Flow now:
1. Detect packaged > storage
2. Back up old storage as .lkg (preserves saves)
3. Copy packaged to storage
4. Delete version.properties
5. UpgradeHandler detects UNKNOWN, records new version
6. Next boot: detects as UPGRADED, extracts save from .lkg
All 295 tests pass.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Problem: When a new build ships with an updated packaged game.2mg in
resources, the system never checks it because the storage file already
exists. The upgrade only compared storage file to lastKnownVersion (from
properties), which are the same thing.
Solution: On startup, always extract and compare versions from BOTH:
- Packaged game (in /jace/data/game.2mg resources)
- Storage game (in ~/lawless-legends/game.2mg)
If packaged > storage: Replace storage file and trigger silent upgrade
If storage > packaged: User manually upgraded, keep storage file
If versions match: No copy needed
Changes:
- GameVersionReader: Added extractVersion(InputStream) to read from
packaged game resources
- LawlessImageTool.getGamePath(): Always compare packaged vs storage
versions and copy if packaged is newer
- Added clear logging: "Game versions - Packaged: X, Storage: Y"
- Removed verbose logging: bitmap operations, partition reads, decompression
Benefits:
- New builds with updated games automatically trigger upgrades
- Clear visibility into which version is running
- Manual upgrades are detected and preserved
- Cleaner logs focused on relevant version info
All 295 tests pass.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Problem: When users manually drag a newer game.2mg, the system would
detect it as an "upgrade" on every boot and try to restore the older
packaged version, creating an infinite loop.
Solution: Use canonical version string comparison (e.g., "5823p.99" vs
"5723p.99") to properly detect:
- UPGRADED: Packaged version is newer than saved (perform silent upgrade)
- DOWNGRADED: Saved version is newer than packaged (user manually upgraded,
accept it and update tracking)
- CURRENT: Versions match (no action needed)
The version string format from PackPartitions is already canonical and
string comparison works directly without parsing.
Changes:
- GameVersionTracker: Use compareTo() for version string comparison to
properly distinguish upgrades from downgrades
- UpgradeHandler: Handle DOWNGRADED status by accepting the newer version
and updating version tracking (no restoration)
All 295 tests pass.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
When users drag a new game.2mg file to manually upgrade, the system now:
1. Uses the same fast silent upgrade logic (ProDOS file copy) instead of
the old orchestrated approach
2. Automatically reboots after successful upgrade to complete the process
3. Preserves save games by extracting from .lkg backup and writing to new disk
This change provides a consistent upgrade experience between automatic and
manual upgrades, and allows users to easily test the upgrade process.
Benefits:
- Consistent: Both auto and manual upgrades use the same proven logic
- Fast: Silent upgrade completes in milliseconds vs seconds for orchestrated
- Testable: Users can now drag new game.2mg and verify save preservation
- Seamless: Automatic reboot completes the upgrade without extra steps
Changes:
- LawlessImageTool.performGameUpgrade(): Replaced orchestrated upgrade with
silent upgrade logic and added automatic reboot
- Added performSilentUpgradeViaHandler() to handle save game extraction and
transfer using ProDOS file operations
- All 295 tests pass
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
CRITICAL FIX: Resolve save game loss during silent upgrades.
Root Causes Fixed:
1. False positive upgrades: File size changed during normal gameplay,
triggering unnecessary upgrades every boot
2. Save game loss: .lkg backup created once on first boot and never
updated, causing upgrades to restore stale state without saves
Solution:
- Implement version-based upgrade detection using actual game version
strings extracted from partition files (e.g., "5723p.99")
- Update .lkg backup on every boot to preserve latest save game state
- Fall back to size-based detection if version extraction fails
New Components:
- GameVersionReader: Extracts version from GAME.PART.1 partition file
- PartitionParser: Parses partition format to locate resourceIndex chunk
- Lx47Algorithm: Decompresses partition data using Lx47 compression
- Enhanced GameVersionTracker: Version string comparison with fallback
- Enhanced UpgradeHandler: Uses version-based detection, updates .lkg
Testing:
- 295 tests pass (9 new classes with comprehensive test coverage)
- Version extraction validated with real game files
- Integration tests verify complete upgrade flow
Impact:
- Users will no longer lose map data and game progress during upgrades
- Upgrades only trigger when version actually changes
- Backward compatible with existing installations
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Remove AWT Toolkit usage in checkForDeveloperBypass (not available in native images)
- Add MacAccessibleExceptionHandler to suppress benign MacAccessible errors
- Update game initialization and image tool
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>