This commit is contained in:
33333-33333 2026-07-18 13:06:02 +09:00
commit f657b6a4a4
94 changed files with 3970 additions and 1241 deletions

View file

@ -602,7 +602,8 @@
function writeWorld(writer, w = []) {
writeBitFields(writer, [w[0] || 0, w[1] || 0, w[2] || 0, w[4] || 0], [2, 3, 4, 3]);
writer.u(w[3] || 0); // worldTick10
writer.u(w[3] || 0); // total elapsed worldTick10
writer.u(Math.max(0, Number(w[18] ?? 0) || 0)); // explicit elapsed completed days
let mask = 0;
if (valuesDiffer(w[5], -9990)) mask |= 1;
if (valuesDiffer(w[6], 1)) mask |= 2;
@ -610,7 +611,8 @@
if (valuesDiffer(w[9], 0)) mask |= 8;
if (valuesDiffer(w[10], 0)) mask |= 16;
if (valuesDiffer(w[11], 0)) mask |= 32;
writeBitFields(writer, [mask], [6]);
if (valuesDiffer(w[19], 0)) mask |= 64;
writeBitFields(writer, [mask], [7]);
if (mask & 1) writer.s(w[5] || 0); // lastBirthAt10 can be negative
if (mask & 2) writer.u(w[6] || 1); // generation
if (mask & 4) writer.u(w[7] || 0); // dead count
@ -620,11 +622,13 @@
if (mask & 8) writer.u(w[9] || 0); // birthSerial
if (mask & 16) writer.u(w[10] || 0); // tarinai population limit
if (mask & 32) writer.u(w[11] || 0); // object limit
if (mask & 64) writer.u(w[19] || 0); // manual Tarinai additions
}
function readWorld(reader) {
const [field, ground, mood, weather] = readBitFields(reader, [2, 3, 4, 3]);
const tick = reader.u();
const [mask] = readBitFields(reader, [6]);
const elapsedDays = reader.u();
const [mask] = readBitFields(reader, [7]);
const lastBirthAt = (mask & 1) ? reader.s() : -9990;
const generation = (mask & 2) ? reader.u() : 1;
const deadCount = (mask & 4) ? reader.u() : 0;
@ -632,7 +636,12 @@
const birthSerial = (mask & 8) ? reader.u() : 0;
const tarinaiPopulationLimit = (mask & 16) ? reader.u() : 0;
const objectLimit = (mask & 32) ? reader.u() : 0;
return [field, ground, mood, tick, weather, lastBirthAt, generation, deadCount, seed, birthSerial, tarinaiPopulationLimit, objectLimit];
const manualTarinaiAddedCount = (mask & 64) ? reader.u() : 0;
const row = [field, ground, mood, tick, weather, lastBirthAt, generation, deadCount, seed, birthSerial, tarinaiPopulationLimit, objectLimit];
row[18] = elapsedDays;
row[19] = manualTarinaiAddedCount;
row[20] = BINARY_SCHEMA_VERSION;
return row;
}
const birthHash32 = global.TarinaiCoreHelpers?.birthHash32 || (seed => {
@ -1465,10 +1474,10 @@
}
const ACHIEVEMENT_METADATA_BINARY_VERSION = 1;
const ACHIEVEMENT_SPELL_BINARY_VERSION = 5;
const ACHIEVEMENT_MASK_BYTES = 8;
const ACHIEVEMENT_PROGRESS_BITS = Object.freeze([7, 4, 7, 12, 5, 5, 5, 11, 6, 10, 9, 10, 3, 17]);
const ACHIEVEMENT_PROGRESS_MAX = Object.freeze([100, 15, 100, 3333, 30, 20, 20, 2047, 50, 666, 333, 721, 7, 131071]);
const ACHIEVEMENT_SPELL_BINARY_VERSION = 8;
const ACHIEVEMENT_MASK_BYTES = 10;
const ACHIEVEMENT_PROGRESS_BITS = Object.freeze([7, 4, 7, 12, 5, 5, 5, 11, 6, 10, 9, 10, 3, 17, 5, 14, 7]);
const ACHIEVEMENT_PROGRESS_MAX = Object.freeze([100, 15, 100, 3333, 30, 20, 20, 2047, 50, 666, 333, 721, 7, 131071, 30, 10000, 100]);
function finiteNumberOrNull(value) {
const n = Number(value);
@ -1493,14 +1502,18 @@
function achievementMaskBytesFromSpell(spell = null) {
const bytes = new Uint8Array(ACHIEVEMENT_MASK_BYTES);
if (!Array.isArray(spell)) return { bytes, baseMinute: 0, deltas: [], progress: [] };
if (Number(spell[0]) !== ACHIEVEMENT_SPELL_BINARY_VERSION) throw new Error("unsupported achievement spell version");
const version = Number(spell[0]);
if (version !== ACHIEVEMENT_SPELL_BINARY_VERSION) throw new Error("unsupported achievement spell version");
const low = Math.max(0, Math.min(0xffffffff, Math.floor(Number(spell[1]) || 0)));
const high = Math.max(0, Math.min(0xffffffff, Math.floor(Number(spell[2]) || 0)));
const extra = Math.max(0, Math.min(0xffff, Math.floor(Number(spell[3]) || 0)));
for (let i = 0; i < 4; i++) bytes[i] = Math.floor(low / (2 ** (i * 8))) & 255;
for (let i = 0; i < 4; i++) bytes[i + 4] = Math.floor(high / (2 ** (i * 8))) & 255;
const baseMinute = toSafeUInt(spell[3]);
const deltas = Array.isArray(spell[4]) ? spell[4].map(toSafeUInt) : [];
const progress = Array.isArray(spell[5]) ? spell[5] : [];
bytes[8] = extra & 255;
bytes[9] = (extra >>> 8) & 255;
const baseMinute = toSafeUInt(spell[4]);
const deltas = Array.isArray(spell[5]) ? spell[5].map(toSafeUInt) : [];
const progress = Array.isArray(spell[6]) ? spell[6] : [];
let unlockedCount = 0;
for (const byte of bytes) {
let value = byte;
@ -1515,7 +1528,7 @@
let high = 0;
for (let i = 0; i < 4; i++) low += (Number(bytes[i]) || 0) * (2 ** (i * 8));
for (let i = 0; i < 4; i++) high += (Number(bytes[i + 4]) || 0) * (2 ** (i * 8));
return [ACHIEVEMENT_SPELL_BINARY_VERSION, low >>> 0, high >>> 0, toSafeUInt(baseMinute), deltas.map(toSafeUInt), progress];
return [ACHIEVEMENT_SPELL_BINARY_VERSION, low >>> 0, high >>> 0, (Number(bytes[8]) || 0) + (Number(bytes[9]) || 0) * 256, toSafeUInt(baseMinute), deltas.map(toSafeUInt), progress];
}
function writeAchievementProgress(writer, values = [], bits = ACHIEVEMENT_PROGRESS_BITS, maxima = ACHIEVEMENT_PROGRESS_MAX) {
const normalized = maxima.map((max, index) => Math.max(0, Math.min(max, toSafeUInt(values?.[index]))));
@ -1559,7 +1572,7 @@
const baseMinute = reader.u();
const deltas = [];
for (let i = 0; i < unlockedCount; i++) deltas.push(reader.u());
const progress = readAchievementProgress(reader);
const progress = readAchievementProgress(reader, ACHIEVEMENT_PROGRESS_BITS, ACHIEVEMENT_PROGRESS_MAX);
return spellFromAchievementMaskBytes(bytes, baseMinute, deltas, progress);
}
function writeAchievementMetadata(writer, metadata = null, tarinaiCount = 0, itemCount = 0) {
@ -1630,7 +1643,7 @@
if (Array.isArray(value.s)) writeAchievementSpell(writer, value.s);
}
function readAchievementMetadata(reader, tarinaiCount = 0, itemCount = 0, schema = BINARY_SCHEMA_VERSION) {
function readAchievementMetadata(reader, tarinaiCount = 0, itemCount = 0) {
const version = reader.b();
if (version !== ACHIEVEMENT_METADATA_BINARY_VERSION) throw new Error("unsupported achievement metadata schema");
const [flags] = readBitFields(reader, [1]);
@ -1678,7 +1691,7 @@
i[index] = [placed, placedAt];
});
const metadata = { w, t, i };
if (flags & 1) metadata.s = readAchievementSpell(reader, schema);
if (flags & 1) metadata.s = readAchievementSpell(reader);
return metadata;
}
@ -1686,9 +1699,12 @@
function summaryFromPayload(w = [], tarinaiCount = 0) {
const config = typeof CONFIG !== "undefined" ? CONFIG : (global.CONFIG || {});
const dayLength = Math.max(1, Number(config.dayLength || 120));
const totalTime = (Number(w[3]) || 0) / 10;
const day = Math.max(1, Math.floor(totalTime / dayLength) + 1);
return [day, tarinaiCount, fieldValue(w[0])];
const totalTime = Math.max(0, (Number(w[3]) || 0) / 10);
const explicitDays = Number(w[18]);
const elapsedDays = Number.isFinite(explicitDays)
? Math.max(0, Math.floor(explicitDays))
: Math.max(0, Math.floor(totalTime / dayLength));
return [elapsedDays + 1, tarinaiCount, fieldValue(w[0]), elapsedDays];
}
function encodeBinarySnapshot(snapshot, stats = null) {
@ -1738,7 +1754,7 @@
const items = readItemBlocks(reader);
let g = null;
try {
g = readAchievementMetadata(reader, tCount, items.length, schema);
g = readAchievementMetadata(reader, tCount, items.length);
} catch (_) {
throw new Error("invalid achievement metadata in binary save");
}
@ -1826,6 +1842,8 @@
global.TarinaiSaveCodec = {
encodeSnapshot,
decodeSnapshot,
encodeBinarySnapshot,
decodeBinarySnapshot,
getLastEncodeStats,
getLastDecodeStats,
};