Move chunk assets from a single flat assetsraw/chunks (and assets/chunks)
into per-map assetsraw/maps/<map>/chunks (assets/maps/<map>/chunks), so
terrain mesh/model output no longer collides across maps sharing chunk
coordinates. map_t gains a name[MAP_NAME_MAX] field plus mapSetMap()/
mapIsLoaded() (checks name[0] == '\0') in place of the old bool loaded
flag; mapInit() now just resets state and mapSetMap() does the chunk
grid load. Updated the chunk asset tool and the map editor's dev
server/client to match.
Cutscenes now parse their authored .jsonc straight into the runtime
cutsceneitem_t/pool representation via yyjson, instead of going through a
separate Python-compiled DCTS binary format - removes the whole
build/compile step and the byte-format contract between the Python
encoder and the C decoder, at the cost of a (still tiny, one-time)
parse per cutscene load.
Also fixes dusk.dsk going stale after a build: the old custom_command
depended on a CMake-configure-time file glob, which only re-detects
added/removed assets on the next configure and could miss edits
entirely. tools.asset.pack now always runs and decides for itself
(via a small manifest) whether anything actually needs repacking, so
asset changes are never missed regardless of add/edit/remove.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Previously the main menu UI opened directly with no cutscene, and only
pressing "Start Game" spun up a brand new cutscene
(main_menu_start_game.jsonc) for the save-device check/load-slots flow.
Now a single cutscene (main_menu.jsonc) starts the moment the main menu
scene becomes active, sitting idle (new CUTSCENE_ITEM_TYPE_IDLE item -
no existing item type blocked forever with no side effect) while the
menu is shown, so it can own menu-wide state going forward.
Pressing "Start Game" now jumps the already-running cutscene to a
NEW_GAME marker via cutsceneGoTo instead of starting a second cutscene -
this already-existing mechanism needed no engine changes. Both retry
options (no device / load error) also now jump straight back to
NEW_GAME instead of RESTARTing the whole cutscene, since RESTART would
otherwise strand the player on the idle block. sceneMainMenuStartGame
also handles the case where the cutscene already ran to completion once
(e.g. backing out of the save picker and pressing Start Game again) by
restarting it landing straight on NEW_GAME, avoiding a crash from
jumping a marker with no cutscene running.
Built and verified on Linux (boots/idles without crashing, cutscene
tests pass), PSP, GameCube and Wii (Docker).
Co-Authored-By: Claude Sonnet 5 <[email protected]>
- Fixed PSP pause taking up to ~1.1s to actually go silent: its output
thread ran independently of stream->state, draining its whole software
ring regardless. audiostream_t.state is now volatile and the output
thread checks AUDIO_STREAM_STATE_PLAYING before each hardware chunk,
skipping output (without consuming the ring) while paused - pause now
goes silent within about one chunk (~23ms) and resume has no gap.
- Added audioMixerPause/Resume/SetPan/SetLoop/FadeTo/IsFading to the
mixer - immediate, channel-indexed primitives for cutscenes to drive.
Fade transitions (fadeFrom/To/Duration/Time/Easing) live on
audiomixerchannelstate_t and advance every frame in
audioMixerChannelApplyVolume(), reusing the same easingApply()
interpolation uifullbox_t already uses for screen fades.
- New src/dusk/rpg/cutscene/item/audio/ with 9 cutscene item types:
AUDIO_PLAY (+ AUDIO_PLAY_SIMPLE/AUDIO_PLAY_LOOPED shorthands),
AUDIO_STOP, AUDIO_PAUSE, AUDIO_RESUME, AUDIO_FADE (+ FADE_OUT/FADE_IN
shorthands), AUDIO_FADE_WAIT, AUDIO_SET_PAN, AUDIO_SET_LOOP, and the
combined AUDIO_SET - registered through the same enum/union/callback
table/macro mechanism every other item type uses.
- Wired the same 9 types into the JSON-based (offline JSONC -> binary
.cts) cutscene asset pipeline: tools/asset/cutscene/__main__.py's
encoder and assetcutsceneloader.c's decoder. Verified round-trip by
hand-encoding/decoding a test file covering all 9 types, and confirmed
the two existing real cutscene files re-encode byte-identical.
Built and verified on Linux, PSP (Docker), GameCube (Docker) and Wii
(Docker).
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Splits the asset archive into a compressed (DEFLATE) zip and an
uncompressed (STORED) zip back to back behind a small header, instead of
one plain zip. DEFLATE-compressed zip entries aren't reliably seekable in
libzip, which caused locale string lookups (repeated rewind/reopen of the
same entry) to silently skip content on Dolphin specifically. Uncompressed
entries don't have that problem, so locale files now go in the stored
archive without needing to buffer the whole thing in memory.
Adds tools/asset/pack as the new packer (replacing the plain
`tar --format=zip`), a shared assetdsk.h/.c opener used by all platforms,
and a second ASSET.zipStored handle with fallback lookup in assetfile.c.
Confirmed working: Linux, PSP (PPSSPP), and Dolphin-FAT (Wii DOL under
-a LLE) - the original Dolphin locale lookup failure no longer reproduces.
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 <[email protected]>
Adds three new pieces of save-file state, all following the same shape:
the save file is the single source of truth, not a separate live runtime
copy that gets synced in/out.
- globalitemstore.h/.c: per-global-entity-ID "collected" flags
(savefile_t.globalItemCollected), so a global item entity's init
callback can check whether it was already picked up in a prior session
without needing to keep the entity itself alive to remember that.
- Gamepad deadzone: removed input_t.deadzone entirely. The setting UI and
every platform's actual deadzone-applying code (inputGetDeadzoneDolphin/
SDL2, previously hardcoded per-platform literals that the settings menu
didn't actually affect) now read savefile_t.deadzone directly via
saveGet(SAVE_ACTIVE_SLOT). Default lives in savefile.h
(SAVE_DEADZONE_DEFAULT), stamped onto every slot in saveInit().
- Story flags: STORY_FLAG_VALUES (a live, codegen-initialized array) is
replaced by savefile_t.storyFlags, read/written via the existing
storyFlagGet()/storyFlagSet() call sites (now macros/functions over the
active save file instead of a separate array). tools/story.py now
generates STORY_FLAG_DEFAULTS (const) instead; storyFlagInitDefaults()
stamps those onto a save the first time it's used (file->exists false),
called from rpgInit().
Added SAVE_ACTIVE_SLOT (0) to savefile.h as the one shared "which slot is
actually being played" constant, replacing three different local/implicit
0s (uigamemenu.c, rpg.c, and now the settings/input call sites).
Verified round-trip on Linux (all three together in one save/load cycle);
both Linux and PSP build clean.
- Allow 2 chunks to be mid-load concurrently instead of 1 (MAP_CHUNK_LOAD_CONCURRENCY).
- Fix entitySetChunk silently losing track of an entity when its target chunk's
entity slots are full - it now stays detached (and retries later) instead of
claiming a chunk that never actually registered it.
- DCF format bumped to v5: chunks can now declare entity spawns (global/NPC via
the existing entityglobal registry, or one-shot item pickups) and map area
triggers, resolved via a new callback-ID registry (mapareagloballist.h)
mirroring the entity one. rpg.c's hardcoded TEST entity/item/area spawns are
gone - chunk_0_0_0.json now carries that data instead. The player is still
bootstrapped in code since it isn't map-authored content.