Add runtime-loaded cutscene files, convert initial/main menu to use them

New ASSET_LOADER_TYPE_CUTSCENE (assetcutsceneloader.c) reads a versioned
binary .cts format decoded into a heap-allocated cutsceneitem_t array + a
string/data pool, both sized to the file's actual content rather than a
fixed capacity, so the shared assetloaderoutput_t union doesn't bloat for
every asset slot regardless of type. Authoring pipeline mirrors the chunk
asset pattern: assetsraw/cutscenes/*.jsonc (JSON plus // and /* */
comments) -> tools/asset/cutscene -> assets/cutscenes/*.cts.

cutsceneSystemSetOnComplete() lets the caller arm a native callback that
fires when a cutscene finishes normally, so a file (which can't store a
function pointer) can end plainly and still hand off to native code -
cutsceneRestart() preserves it across a retry loop rather than clearing it,
since a restart is the same logical run trying again.

The initial and main-menu start-game cutscenes are now loaded from files
instead of compiled in via the CUTSCENE(...) macro.

Fixed a real bug found while converting these: the sync loader read the
file's total size from assetfile_t.size to locate the trailing pool
region, but assetFileDispose() (called at the end of the async phase)
zeroes that whole struct first, so the size was always 0 and the pool
offset computation underflowed into an out-of-bounds read - intermittent
depending on heap layout. Fixed by saving the size before disposal; also
fixed the read-completeness assert being checked after that same zeroing
(a no-op 0 == 0 check).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-25 09:51:26 -05:00
parent 106d9b0fc0
commit e630827b34
15 changed files with 1261 additions and 73 deletions
+69
View File
@@ -0,0 +1,69 @@
{
"items": [
// Boot check: make sure a save device is available before handing off
// to the main menu.
{
"type": "MODAL",
"title": "initial.checking_save.title",
"message": "initial.checking_save.message"
},
{
"type": "WAIT",
"seconds": 0.2
},
{
"type": "SAVE_DEVICE_CHECK",
"successMarker": "CONTINUE",
"failureMarker": "NO_DEVICE"
},
// No save device found - offer to retry or continue without saving.
{
"type": "MARKER",
"name": "NO_DEVICE"
},
{
"type": "MODAL_CLOSE"
},
{
"type": "MODAL_OPTIONS_MARKERS",
"title": "initial.no_device.title",
"message": "initial.no_device.message",
"options": [
{
"text": "initial.no_device.retry",
"marker": "RETRY"
},
{
"text": "initial.no_device.continue",
"marker": "CONTINUE"
}
]
},
{
"type": "MARKER",
"name": "RETRY"
},
{
"type": "MODAL_CLOSE"
},
{
"type": "RESTART"
},
// Save device found (or continuing without one) - hand off to the
// main menu scene.
{
"type": "MARKER",
"name": "CONTINUE"
},
{
"type": "MODAL_CLOSE"
},
{
"type": "SCENE",
"sceneType": "MAIN_MENU"
}
]
}
@@ -0,0 +1,92 @@
{
"items": [
{
"type": "MODAL",
"title": "main_menu.checking_save.title",
"message": "main_menu.checking_save.message"
},
{
"type": "WAIT",
"seconds": 0.2
},
{
"type": "SAVE_DEVICE_CHECK",
"successMarker": "CONTINUE",
"failureMarker": "NO_DEVICE"
},
// No save device found - offer to retry or continue without saving.
{
"type": "MARKER",
"name": "NO_DEVICE"
},
{
"type": "MODAL_CLOSE"
},
{
"type": "MODAL_OPTIONS_MARKERS",
"title": "main_menu.no_device.title",
"message": "main_menu.no_device.message",
"options": [
{
"text": "main_menu.no_device.retry",
"marker": "RETRY"
},
{
"text": "main_menu.no_device.continue",
"marker": "CONTINUE"
}
]
},
// Retry
{
"type": "MARKER",
"name": "RETRY"
},
{
"type": "MODAL_CLOSE"
},
{
"type": "RESTART"
},
// Save device found - attempt to load all save slots.
{
"type": "MARKER",
"name": "CONTINUE"
},
{
"type": "MODAL_CLOSE"
},
{
"type": "SAVE_LOAD_ALL_SLOTS",
"successMarker": "LOADED",
"failureMarker": "LOAD_ERROR"
},
// Save data failed to load (e.g. corrupt/unreadable) - only option is
// to retry, no "continue without saving" here since we already know a
// device is present.
{
"type": "MARKER",
"name": "LOAD_ERROR"
},
{
"type": "MODAL_OPTIONS_MARKERS",
"title": "main_menu.save_load_error.title",
"message": "main_menu.save_load_error.message",
"options": [
{
"text": "main_menu.save_load_error.retry",
"marker": "RETRY"
}
]
},
{
"type": "MARKER",
"name": "LOADED"
}
]
}