PageSourceSearch

https://webcade.fun/rng.js?v=1025436e

js webcade.fun collected 2026-10-08 08:27:13 UTC 3,409 bytes, 77 lines download raw bytes

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.