1// Seeded randomness for the cabinets. 2// 3// Every roll a game makes â where a mushroom sprouts, which lane a flipper 4// climbs, where the next tile appears â has to come from a stream the server 5// can reproduce, or a run can never be replayed and verified off-device. 6// Math.random() cannot be reproduced, so games call ARNG() instead. 7// 8// The seed is issued by the server with the play credit, which also closes 9// seed-shopping: a client cannot re-roll until it gets a favourable board, 10// because it does not choose the seed. 11// 12// mulberry32 â small, fast, and identical in the browser and in Node, which 13// is the property that matters here. Its statistical quality is far beyond 14// what an arcade game needs. 15(() => { 16 // The counter mulberry32 walks, held out here rather than inside the closure 17 // so mix() can reach it. next() is byte-for-byte the same arithmetic it always 18 // was; only where the state lives has changed. 19 let a = null; 20 21 function draw() { 22 a = (a + 0x6D2B79F5) | 0; 23 let t = Math.imul(a ^ (a >>> 15), 1 | a); 24 t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; 25 return ((t ^ (t >>> 14)) >>> 0) / 4294967296; 26 } 27 28 window.ARCADE_RNG = { 29 // called once per run, before the game starts 30 seed(s) { 31 const n = (Number(s) >>> 0) || 1; 32 a = n >>> 0; 33 window.ARCADE_SEED_USED = n; 34 return n; 35 }, 36 37 // Fold a value the client could not have known into the stream. 38 // 39 // WHY THIS EXISTS. The seed is issued before the run and the cabinet is 40 // public JavaScript, so at the moment play starts the client holds every 41 // roll the run will ever make. That is what lets a solver compute a perfect 42 // run offline and post it as a recording â the replay verifies it, because 43 // the inputs really do produce that score. See docs/security/01-entropy.md. 44 // 45 // The server issues values it does not reveal in advance and hands them over 46 // during the run. From the first draw after one lands, a stream computed 47 // ahead of time is the wrong stream, and the forged run stops reproducing. 48 // 49 // XOR into the counter rather than reseeding. Reseeding would throw away 50 // everything the run has already drawn and make the stream a function of the 51 // last value alone â so two runs that received the same value would share a 52 // future regardless of what came before. XOR keeps the whole history in the 53 // state, which is the property that makes each run's stream its own. 54 mix(value) { 55 if (a === null) this.seed((Math.random() * 0x7FFFFFFF) | 0); 56 a = (a ^ ((Number(value) >>> 0) || 0)) | 0; 57 return a >>> 0; 58 }, 59 60 next() { 61 // No seed issued (dev, or an older server): fall back to an unreproducible 62 // stream so play still works. Such a run simply cannot be replayed. 63 if (a === null) this.seed((Math.random() * 0x7FFFFFFF) | 0); 64 return draw(); 65 }, 66 seeded() { return a !== null; }, 67 }; 68 69 window.ARNG = () => window.ARCADE_RNG.next(); 70 71 // Updates completed in the current run, incremented by every cabinet's frame 72 // loop. Input is stamped with this rather than a millisecond reading: a 73 // 60Hz frame is 16.667ms, so a rounded millisecond stamp cannot recover the 74 // update index â one in three comes back off by one, and replay then applies 75 // the input a frame early or late. 76 window.ARCADE_FRAME = 0; 77})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.