beasty-save-system / reference / results-and-errors.md

Resultados y errores

Cada llamada de guardado y carga devuelve un resultado tipado que contiene un BeastySaveError. Esta página lista los miembros del resultado y los trece valores de error, con qué causa cada uno y qué debería hacer tu juego al respecto.

El principio de diseño

La API nunca te lanza excepciones. Un guardado que falló lo dice, y nunca parece un guardado que funcionó. No hay excepción que capturar, ningún archivo parcialmente escrito que detectar, ningún éxito silencioso. Comprueba Success, y si es falso, lee Error.

Los únicos métodos que lanzan excepciones son los de registro (RegisterMigration, RegisterConverter, RegisterModule): un registro incorrecto es un error en tu código en el arranque, no un hecho sobre un archivo, y debe detenerte de inmediato.

SaveResult

Devuelto por Save, SaveAsync, RestoreBackup, BeastySaveManager.SaveAllNow y BeastySaveManager.CaptureGroupNode.

MiembroTipoSignificado
SuccessboolVerdadero cuando el archivo está en disco.
ErrorBeastySaveErrorNone en caso de éxito.
MessagestringDetalle legible por humanos. Nulo en caso de éxito.
SaveResult.Ok()static SaveResultConstruye un resultado de éxito.
SaveResult.Fail(error, message)static SaveResultConstruye un resultado de fallo.
ToString()string"OK", o "{Error}: {Message}".

LoadResult

Devuelto por LoadInto, LoadIntoAsync, BeastySaveManager.LoadAllNow y BeastySaveManager.ApplyGroupNode.

MiembroTipoSignificado
SuccessboolVerdadero cuando los datos fueron aplicados.
ErrorBeastySaveErrorNone en caso de éxito.
MessagestringDetalle legible por humanos. Nulo en caso de éxito.
BackupAvailableboolExiste un .bak para este slot. Se completa en cada resultado de carga, éxito o fallo.
WarningsIReadOnlyList<string>Campos y entradas omitidos por una carga tolerante. Nunca nulo; vacío cuando no hay nada que reportar.
LoadResult.Ok(warnings = null)static LoadResultConstruye un resultado de éxito.
LoadResult.Fail(error, message, backupAvailable = false)static LoadResultConstruye un resultado de fallo.
ToString()string"OK", o "{Error}: {Message}".

BackupAvailable es el indicador sobre el que decidir después de un Corrupt o ParseError: te dice si ofrecer al jugador un botón de “restaurar el guardado anterior” servirá de algo.

LoadResult<T>

Devuelto por Load<T>, LoadAsync<T> y ReadMeta. Deriva de LoadResult y añade el valor.

MiembroTipoSignificado
ValueTEl objeto cargado. Indefinido cuando Success es falso — no lo leas.
LoadResult<T>.Ok(value, warnings = null)static LoadResult<T>Construye un resultado de éxito.
LoadResult<T>.Fail(error, message, backupAvailable = false)static LoadResult<T>Construye un resultado de fallo.

Los códigos de error

Beasty_SaveSystemCore.BeastySaveError, en orden de declaración.

ValorLo lanzaCausa
NoneÉxito. Success es verdadero.
InvalidArgumentguardar, cargar, todos los métodos de slotDatos nulos, objetivo nulo, settings nulo, o un nombre de slot inválido.
SerializationFailedguardarEl objeto no pudo convertirse en JSON.
IoErrorguardar, cargarEl sistema de archivos rechazó la operación.
FileNotFoundcargar, ReadMeta, RestoreBackupNo hay archivo en la ruta del slot (o no hay .bak).
ParseErrorcargar, ReadMetaEl archivo no es JSON válido.
Corruptcargar, ReadMetaNo es un sobre válido, o el checksum no coincide.
DecryptFailedcargarLa configuración de cifrado no coincide con el archivo, o la clave es incorrecta.
TypeMismatchcargarEl archivo contiene un tipo raíz distinto al que solicitaste.
TypeUnavailableguardado de escena, carga de escenaUn componente no tiene convertidor registrado.
VersionTooNewcargarEl archivo fue escrito por un contenedor más nuevo o una versión de datos más nueva.
MigrationFailedcargarEl archivo es más antiguo y la cadena de migraciones registradas no pudo salvar la brecha.
FieldMapFailedcargarLos datos no pudieron mapearse sobre el objeto.

Las secciones siguientes dan el diagnóstico y la solución para cada uno.

InvalidArgument

Pasaste algo con lo que la llamada no puede trabajar: data es nulo en Save, target es nulo en LoadInto, settings es nulo, o el nombre de slot es rechazado. Un slot es un nombre de archivo desnudo; se rechaza cuando está vacío o solo contiene espacios, contiene / o \, contiene .., es una ruta enraizada, contiene caracteres inválidos en un nombre de archivo, o es un nombre de dispositivo reservado de Windows (CON, PRN, AUX, NUL, COM1-COM9, LPT1-LPT9).

Qué hacer: corrige el código que llama. Si el nombre de slot viene del jugador (un guardado con nombre), valídalo antes de guardar y muéstrale por qué fue rechazado. No se escribió nada.

SerializationFailed

El grafo de objetos no pudo convertirse en JSON. Message nombra la ruta ($.inventory.items[3].owner). Las causas, todas ellas:

  • Un ciclo de referencias. Los datos de guardado deben ser acíclicos.
  • Un float NaN o Infinity. No son números JSON válidos.
  • Una clave de diccionario que no es un string, primitivo o enum, o una clave nula.
  • Un ulong mayor que long.MaxValue.
  • Una referencia a UnityEngine.Object (un Sprite, un GameObject, otro componente) en una clase C# plana. En un MonoBehaviour ese campo se omite en su lugar; en una clase plana es un fallo total.
  • En un guardado de escena, un convertidor que lanzó una excepción.

Qué hacer: corrige los datos. Rompe el ciclo, sanea el float, guarda un identificador (un string id) en lugar de la referencia al objeto de Unity. No se escribió nada. Consulta Qué se guarda.

IoError

El sistema de archivos rechazó la operación. El disco está lleno, la carpeta no puede crearse, el archivo está bloqueado por otro proceso, la plataforma denegó el permiso. Message lleva el texto de la excepción subyacente y la ruta.

Qué hacer: dile al jugador que el guardado falló y deja que reintente. No reintentes en un bucle cerrado. El archivo de guardado anterior, si lo había, queda intacto: la escritura es atómica, así que una escritura fallida no puede dejar un archivo a medias.

FileNotFound

No hay archivo en la ruta del slot. Desde RestoreBackup, no hay .bak para ese slot.

Esta es una condición esperada y consultable — una pantalla de slots la sondea constantemente — así que se registra como advertencia, no como error.

Qué hacer: trátalo como un slot vacío. Usa BeastySave.Exists si quieres preguntar sin producir un resultado.

ParseError

El archivo está en disco pero no es JSON válido. Message lleva la línea y columna del carácter conflictivo. Causas: el archivo fue truncado (una build antigua que escribía de forma no atómica, un disco que se llenó a mitad de la escritura), fue editado a mano de forma incorrecta, o directamente no es un guardado de Beasty.

Qué hacer: comprueba BackupAvailable y ofrece BeastySave.RestoreBackup. Consulta Copias de seguridad y corrupción.

Corrupt

Dos comprobaciones distintas reportan esto:

  • La forma del sobre es incorrecta. El archivo se parseó como JSON pero la raíz no es un objeto, o falta beasty, dataVersion, type, checksum o data, o tiene el tipo incorrecto. Message nombra el campo.
  • El checksum no coincide. El SHA-256 del payload no es el registrado en el archivo. El archivo fue modificado, o los bytes se corrompieron.

Qué hacer: lo mismo que ParseError: comprueba BackupAvailable y ofrece RestoreBackup. Un jugador que editó su guardado se topará con la comprobación de checksum; esa comprobación está funcionando como debe.

Nota Un slot que falla su propio checksum nunca se rota al .bak en el siguiente guardado, así que la última copia buena permanece restaurable.

DecryptFailed

Tres comprobaciones reportan esto:

  • Encrypted = true pero el archivo está en texto plano. Un juego que cifra se niega a cargar un guardado sin cifrar. El mensaje es “This save is not encrypted, but this game only loads encrypted saves.” Esto es deliberado: el checksum no lleva ningún secreto, así que sin esta comprobación cualquiera podría escribir un guardado a mano.
  • El descifrado lanzó una excepción. La clave es incorrecta, o el texto cifrado fue manipulado.
  • El texto descifrado no es JSON válido. Casi siempre una clave incorrecta.

El caso inverso — una configuración en texto plano leyendo un archivo cifrado — vuelve como Corrupt, porque el checksum del texto cifrado no coincide con un hash del texto JSON.

Qué hacer: haz que BeastySaveSettings.Encrypted y EncryptionKey coincidan con cómo se escribió el archivo. Si activaste el cifrado en una actualización, los guardados antiguos no se pueden leer: migralos antes de publicar, o mantén dos objetos de configuración. Consulta Cifrado.

TypeMismatch

El campo type del sobre no es igual al nombre completo del tipo que solicitaste. Llamaste a Load<T> con el T equivocado, llamaste a LoadInto con un objeto de otra clase, o renombraste o moviste la clase desde que se escribió el guardado (el nombre del tipo incluye el namespace).

Qué hacer: carga el tipo que fue guardado. Si renombraste la clase, los archivos antiguos no pueden coincidir por nombre; mantén el nombre anterior, o lee el archivo con ReadMeta más una estrategia de migración propia.

TypeUnavailable

Un componente en un guardado de escena no tiene convertidor registrado.

  • Al guardar: el mensaje es “<Type> on ‘<object>’ has no registered converter; enable its converter module or register a custom IBeastyConverter.” No se escribió nada.
  • Al cargar: el archivo de guardado registra qué módulo escribió cada componente, así que el mensaje lo nombra: “The save was written by module ‘<id>’ — enable that converter module (or its package) in this project.” Una carga estricta falla por esto; una carga tolerante advierte y omite la entrada.

La causa más común es que el paquete de Unity que el módulo necesita no está en el proyecto: el assembly del módulo no compila, así que sus convertidores no existen.

Qué hacer: instala el paquete que el módulo necesita, o registra un convertidor personalizado para el tipo. Consulta Módulos de convertidores y Convertidores personalizados.

VersionTooNew

Dos comprobaciones reportan esto:

  • Versión del contenedor. El campo beasty del archivo no es la versión de contenedor que esta build entiende (actualmente 2). El archivo viene de un Beasty Save System más nuevo.
  • Versión de datos. El dataVersion del archivo es mayor que BeastySaveSettings.DataVersion. El guardado fue escrito por una build más nueva de tu juego.

Qué hacer: esto es un downgrade, no una corrupción. Dile al jugador que su guardado es de una versión más nueva del juego y que necesita actualizar. No ofrezcas cargarlo: los datos no encajarían.

MigrationFailed

El dataVersion del archivo es más antiguo que BeastySaveSettings.DataVersion, y la cadena de migraciones registradas no pudo salvar la brecha. Tres causas, cada una nombrada en Message:

  • “No migration registered from data version <n>.” — falta un paso.
  • “Migration <n> -> <m> threw: …” — tu función de migración lanzó una excepción.
  • “Migration chain overshot the target: a step landed on version <n> but version <m> was requested.” — un paso salta más allá de la versión objetivo.

Qué hacer: registra el paso faltante o corregido con BeastySave.RegisterMigration, desde un [RuntimeInitializeOnLoadMethod] (Play Mode reinicia el registro). Consulta Versionado y migraciones.

FieldMapFailed

El archivo fue leído, verificado y descifrado; los datos simplemente no encajan en el objeto. Causas:

  • Un campo falló al convertir en modo estricto. El mensaje es Field '<Type>.<field>' failed to load: ... — normalmente un campo cuyo tipo cambió (un string se convirtió en int), o un campo que ahora falta en el JSON como tipo de valor.
  • Un tipo de colección no soportado. El escritor convierte cualquier IEnumerable en un array, pero una colección sin Add, Enqueue o Push no se puede volver a leer.
  • Un convertidor lanzó una excepción al rellenar un componente.
  • En una carga de escena (solo estricta): un id saveable en el archivo no está presente en la escena — “Saveable id ‘<id>’ is not present in the scene.”

En modo estricto no se aplica nada: LoadAll toma una instantánea de cada componente antes de escribirlo y revierte los ya aplicados, y el mensaje lo indica (“Nothing was loaded: the N component(s) already applied were restored.”).

Qué hacer: si la forma de tus datos cambió entre versiones, para eso están las migraciones. Si estás en plena producción renombrando campos, pon Strict = false para que el campo incorrecto se omita y se reporte en Warnings en lugar de fallar la carga. Consulta Carga estricta vs. tolerante.

Ver también