Complete REGULAR_BATTLE immediately, fix stale saveslot tests
REGULAR_BATTLE previously never completed on its own, hanging any cutscene that reached it - START_BATTLE already waits out the whole encounter and returns to the overworld before this item ever runs, so it's now a completes-immediately hook point for future regular-battle- specific follow-up. Also updates test_saveslot.c for the mapName/partyMembers schema additions: the round-trip test now writes a valid map name for its in-use slot, "missingFieldsUseDefaults" is replaced with a test for the blank-slot short-circuit it was actually hitting, and a new regression test locks in that an in-use slot with no map name is treated as corrupt. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
@@ -11,5 +11,5 @@ bool_t cutsceneRegularBattleUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -9,12 +9,15 @@
|
||||
#include "rpg/cutscene/item/cutsceneitembase.h"
|
||||
|
||||
/**
|
||||
* Updates a regular-battle step. Currently a placeholder - behaves exactly
|
||||
* like IDLE (see cutsceneidle.h) and never completes on its own.
|
||||
* Updates a regular-battle step. By the time this item runs, the preceding
|
||||
* START_BATTLE item has already waited out the whole encounter, synced
|
||||
* party HP/MP, and returned to the overworld scene - so this is currently
|
||||
* just a completes-immediately hook point for whatever regular-battle-
|
||||
* specific follow-up (loot, post-battle dialogue, etc.) gets added later.
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
* @returns false always.
|
||||
* @returns true always.
|
||||
*/
|
||||
bool_t cutsceneRegularBattleUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
|
||||
@@ -143,6 +143,12 @@ static void test_saveSlotReadJSON_roundTrip(void **state) {
|
||||
written.version = 7;// deliberately non-default, not part of the JSON schema
|
||||
stringCopy(written.cachedData.name, "Hero", sizeof(written.cachedData.name));
|
||||
written.cachedData.playerLevel = 42;
|
||||
// An in-use slot (non-empty name) requires a non-empty map name too -
|
||||
// see saveSlotNewGame/requireStrMin("mapName", 1) in saveSlotVer1ReadJSON.
|
||||
stringCopy(
|
||||
written.cachedData.mapName, "overworld",
|
||||
sizeof(written.cachedData.mapName)
|
||||
);
|
||||
|
||||
char_t *json;
|
||||
size_t len;
|
||||
@@ -157,6 +163,7 @@ static void test_saveSlotReadJSON_roundTrip(void **state) {
|
||||
|
||||
assert_true(stringEquals(read.cachedData.name, "Hero"));
|
||||
assert_int_equal(read.cachedData.playerLevel, 42);
|
||||
assert_true(stringEquals(read.cachedData.mapName, "overworld"));
|
||||
assert_true(read.cachedData.time.time == written.cachedData.time.time);
|
||||
|
||||
// version/dataType are struct-only bookkeeping, never serialized - the
|
||||
@@ -167,21 +174,40 @@ static void test_saveSlotReadJSON_roundTrip(void **state) {
|
||||
free(json);
|
||||
}
|
||||
|
||||
static void test_saveSlotReadJSON_missingFieldsUseDefaults(void **state) {
|
||||
static void test_saveSlotReadJSON_blankSlot_shortCircuits(void **state) {
|
||||
saveslot_t slot;
|
||||
saveSlotInit(&slot);
|
||||
stringCopy(slot.cachedData.name, "sentinel", sizeof(slot.cachedData.name));
|
||||
slot.cachedData.playerLevel = 999;
|
||||
|
||||
errorret_t ret = slotFromJSON(
|
||||
&slot,
|
||||
"{\"version\":1,"
|
||||
"\"time\":{\"time\":1.0,\"timeZone\":0.0,\"offsetTime\":0.0}}"
|
||||
);
|
||||
// No "name" key (or an empty one) means a blank/never-used slot (see
|
||||
// saveSlotInUse) - the reader returns right after reading that, without
|
||||
// needing or validating time/mapName/partyMembers/partyOrder at all, so
|
||||
// this succeeds despite omitting fields that would otherwise be required.
|
||||
errorret_t ret = slotFromJSON(&slot, "{\"version\":1}");
|
||||
assert_true(errorIsOk(ret));
|
||||
|
||||
assert_int_equal(slot.cachedData.name[0], '\0');
|
||||
assert_int_equal(slot.cachedData.playerLevel, 1);
|
||||
assert_int_equal(slot.cachedData.mapName[0], '\0');
|
||||
// Not readInt32's own default of 1 - the early return happens before
|
||||
// playerLevel is ever read, leaving the zero-init saveSlotCurrentReadJSON
|
||||
// seeds ver1 with (see the memoryZero in saveSlotCurrentReadJSON).
|
||||
assert_int_equal(slot.cachedData.playerLevel, 0);
|
||||
}
|
||||
|
||||
static void test_saveSlotReadJSON_inUseMissingMapName_errors(void **state) {
|
||||
saveslot_t slot;
|
||||
saveSlotInit(&slot);
|
||||
|
||||
// "name" is present (in use) but "mapName" is missing - a real save
|
||||
// (via saveSlotNewGame) always has both, so this is corrupt.
|
||||
errorret_t ret = slotFromJSON(
|
||||
&slot,
|
||||
"{\"version\":1,\"name\":\"Hero\","
|
||||
"\"time\":{\"time\":1.0,\"timeZone\":0.0,\"offsetTime\":0.0}}"
|
||||
);
|
||||
assert_true(errorIsNotOk(ret));
|
||||
errorCatch(ret);
|
||||
}
|
||||
|
||||
static void test_saveSlotReadJSON_missingTimeKey_errors(void **state) {
|
||||
@@ -265,7 +291,8 @@ int main(void) {
|
||||
|
||||
cmocka_unit_test(test_saveSlotReadJSON_nullAsserts),
|
||||
cmocka_unit_test(test_saveSlotReadJSON_roundTrip),
|
||||
cmocka_unit_test(test_saveSlotReadJSON_missingFieldsUseDefaults),
|
||||
cmocka_unit_test(test_saveSlotReadJSON_blankSlot_shortCircuits),
|
||||
cmocka_unit_test(test_saveSlotReadJSON_inUseMissingMapName_errors),
|
||||
cmocka_unit_test(test_saveSlotReadJSON_missingTimeKey_errors),
|
||||
cmocka_unit_test(test_saveSlotReadJSON_nameTooLong_errors),
|
||||
cmocka_unit_test(test_saveSlotReadJSON_nonObjectRoot_errors),
|
||||
|
||||
Reference in New Issue
Block a user