diff --git a/apps/micropolis/src/lib/cityLoadDeterminism.test.ts b/apps/micropolis/src/lib/cityLoadDeterminism.test.ts new file mode 100644 index 00000000..71f9c56c --- /dev/null +++ b/apps/micropolis/src/lib/cityLoadDeterminism.test.ts @@ -0,0 +1,36 @@ +import { describe, it, expect } from 'vitest'; +import { loadMicropolisMainModule } from './wasm/node'; +import { createNoopJsCallback } from './wasm/callbacks'; +import { createMapMopViews } from './wasm/views'; +import type { MainModule, Micropolis } from '../types/micropolisengine.d.js'; + +const CITY = '/cities/kobe.cty'; + +const loadWithSeed = (engine: MainModule, seed: number): Micropolis => { + const micropolis = new engine.Micropolis(); + micropolis.setCallback(createNoopJsCallback(engine), {}); + micropolis.init(); + micropolis.seedRandom(seed); + expect(micropolis.loadCity(CITY)).toBe(true); + return micropolis; +}; + +const mapHash = (engine: MainModule, micropolis: Micropolis): number => { + let hash = 0; + for (const tile of createMapMopViews(engine, micropolis)!.mapData) hash = (Math.imul(hash, 31) + tile) | 0; + return hash >>> 0; +}; + +describe('city load determinism', () => { + it('same seed gives the same map, a different seed gives another', async () => { + const engine = await loadMicropolisMainModule(); + const first = loadWithSeed(engine, 42); + const second = loadWithSeed(engine, 42); + const other = loadWithSeed(engine, 7); + + expect(mapHash(engine, second)).toBe(mapHash(engine, first)); + expect(mapHash(engine, other)).not.toBe(mapHash(engine, first)); + + for (const micropolis of [first, second, other]) micropolis.delete(); + }); +}); diff --git a/packages/micropolis-engine/src/fileio.cpp b/packages/micropolis-engine/src/fileio.cpp index 30e91a04..94cea582 100644 --- a/packages/micropolis-engine/src/fileio.cpp +++ b/packages/micropolis-engine/src/fileio.cpp @@ -592,8 +592,6 @@ bool Micropolis::loadCity(const std::string &filename) std::string newCityName = cityFileName.substr(pos, last - pos); setCityName(newCityName); - doSimInit(); - didLoadCity(filename); return true; diff --git a/packages/micropolis-engine/src/initialize.cpp b/packages/micropolis-engine/src/initialize.cpp index 9ff81d07..e7b50071 100644 --- a/packages/micropolis-engine/src/initialize.cpp +++ b/packages/micropolis-engine/src/initialize.cpp @@ -84,7 +84,6 @@ /** Reset many game state variables */ void Micropolis::initWillStuff() { - randomlySeedRandom(); initGraphMax(); destroyAllSprites(); diff --git a/packages/micropolis-engine/src/micropolis.cpp b/packages/micropolis-engine/src/micropolis.cpp index 5230c74e..ebf955b3 100644 --- a/packages/micropolis-engine/src/micropolis.cpp +++ b/packages/micropolis-engine/src/micropolis.cpp @@ -738,6 +738,7 @@ void Micropolis::simInit() resetMapState(); resetEditorState(); clearMap(); + randomlySeedRandom(); initWillStuff(); setFunds(5000); setGameLevelFunds(LEVEL_EASY);