{
  "spec": "docs/rng-wire-spec.md",
  "generatedBy": "the script in docs/rng-wire-spec.md, section \"Regenerating test-vectors.json\", run via pnpm exec tsx generate-test-vectors.mjs > packages/engine/src/rng/test-vectors.json",
  "fixtures": [
    {
      "label": "primary — general-purpose fixture",
      "serverSeed": "2f3c1a9e7b5d4082c6a1f0e3b7d95c4a1e8f2b6d0937c5a4e1f8b2d6907e5c31",
      "clientSeed": "u7-wire-spec-client-seed",
      "nonce": 0,
      "rawBytesHex": "a17dc913952ec7a4b52428599bee74857284efd375e45039c464e668993a140950f6bd1a40e6ba2c5d1a009a3b2807cc2838adb1b4c8bed165fb6f431614fe7f",
      "nextInt": {
        "maxExclusive": 100,
        "first16Values": [61, 25, 19, 49, 46, 99, 64, 81, 36, 40, 89, 55, 16, 33, 14, 32],
        "bytesConsumedByFirst16Draws": 18
      },
      "nextUint53First": 5681971727607256,
      "nextFloatFirst": 0.6308255837258754,
      "nextBitsFirst16": [1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1],
      "shuffle25": [
        18, 13, 7, 0, 22, 8, 1, 15, 16, 3, 21, 14, 12, 23, 10, 4, 24, 20, 9, 6, 2, 19, 17, 5, 11
      ],
      "shuffle25Consumed": 24,
      "shuffle52": [
        39, 2, 45, 43, 6, 8, 49, 30, 31, 13, 11, 12, 25, 34, 26, 28, 37, 50, 33, 21, 17, 9, 20, 10,
        42, 0, 14, 16, 22, 27, 32, 4, 18, 41, 44, 24, 3, 48, 38, 35, 7, 40, 36, 47, 29, 15, 46, 51,
        19, 1, 23, 5
      ],
      "shuffle52Consumed": 53,
      "shuffle312": [
        159, 82, 79, 311, 124, 42, 236, 273, 19, 101, 249, 205, 98, 279, 103, 228, 9, 248, 61, 224,
        68, 310, 7, 73, 127, 276, 49, 209, 233, 90, 219, 5, 36, 54, 74, 230, 107, 99, 29, 16, 86,
        293, 51, 92, 292, 85, 108, 290, 46, 143, 88, 121, 291, 232, 190, 199, 271, 181, 211, 179,
        193, 52, 235, 27, 116, 106, 34, 197, 131, 242, 221, 227, 17, 165, 105, 26, 139, 119, 110,
        287, 303, 1, 185, 170, 243, 298, 75, 281, 180, 129, 296, 278, 111, 299, 63, 280, 213, 43,
        59, 55, 113, 239, 302, 83, 47, 25, 253, 89, 168, 117, 37, 135, 266, 136, 255, 134, 41, 48,
        128, 3, 222, 174, 208, 141, 215, 120, 38, 58, 156, 283, 118, 137, 260, 173, 270, 225, 265,
        72, 94, 207, 256, 169, 122, 142, 28, 15, 277, 294, 155, 87, 148, 269, 272, 102, 301, 163,
        95, 304, 162, 309, 4, 140, 229, 210, 152, 295, 147, 214, 22, 166, 104, 175, 250, 100, 161,
        76, 126, 177, 195, 0, 70, 33, 145, 130, 238, 109, 10, 246, 40, 65, 252, 217, 35, 191, 200,
        171, 32, 192, 146, 212, 241, 285, 44, 257, 158, 31, 218, 201, 220, 305, 308, 71, 13, 223,
        202, 254, 14, 151, 300, 258, 286, 125, 187, 78, 21, 112, 206, 39, 149, 62, 115, 297, 264,
        67, 84, 56, 93, 261, 288, 167, 8, 97, 164, 274, 307, 45, 251, 237, 203, 186, 91, 11, 96,
        289, 66, 77, 275, 284, 245, 204, 50, 153, 24, 2, 184, 196, 178, 20, 231, 306, 247, 268, 259,
        18, 57, 226, 53, 144, 23, 133, 240, 12, 183, 263, 114, 234, 267, 216, 262, 64, 154, 182,
        194, 150, 30, 6, 80, 188, 81, 176, 69, 282, 189, 132, 244, 138, 198, 172, 123, 60, 160, 157
      ],
      "shuffle312Consumed": 430,
      "nextInt1": {
        "value": 0,
        "consumed": 0
      }
    },
    {
      "label": "rejection — a rejected draw still advances the cursor (k=1)",
      "serverSeed": "test-server-seed-for-u7-rejection-fixture",
      "clientSeed": "test-client-seed",
      "nonce": 11,
      "nextInt": {
        "maxExclusive": 7,
        "value": 4,
        "bytesConsumed": 2
      },
      "note": "block(nonce=11) byte 0 is 0xFF (255), which is >= nextInt(7)'s rejection limit (floor(256/7)*7 = 252); that draw is rejected and the cursor still advances past it. The accepted draw comes from byte 1 (165), giving 165 % 7 = 4 and a total of 2 bytes consumed for this single nextInt(7) call. A reimplementation that used byte 0 directly (without the rejection check) would compute 255 % 7 = 3 and consume only 1 byte — both observably wrong against this fixture. NOTE: at k=1, \"draw a fresh k bytes on rejection\" and \"slide the window forward by 1 byte and reread\" are the SAME operation, so this fixture alone cannot distinguish the correct algorithm from that specific wrong one — see the k2Rejection fixture below for the case that does."
    },
    {
      "label": "k2Rejection — nextInt rejects at k=2, not just k=1",
      "serverSeed": "k2-rejection-server-seed",
      "clientSeed": "k2-rejection-client-seed",
      "nonce": 3,
      "nextInt": {
        "maxExclusive": 40000,
        "value": 30088,
        "bytesConsumed": 4
      },
      "note": "k=2 for n=40000 (256^2=65536 >= 40000, 256^1=256 < 40000); limit = floor(65536/40000)*40000 = 40000. block(nonce=3) bytes 0-1 give u=48991 >= 40000 -> reject (2 bytes consumed for nothing). A FRESH 2 bytes -- block(nonce=3) bytes 2-3 -- give u=30088 < 40000 -> accept, value=30088 % 40000=30088, 4 bytes consumed in total. A sliding-window implementation of the redraw (advance by 1 byte, reread k=2 bytes) would instead read bytes 1-2 (u=24437, also < 40000, silently accepted), returning 24437 after consuming only 3 bytes -- both fields observably wrong against this fixture, though not against the k=1 rejection fixture above."
    },
    {
      "label": "boundaryRejection — u === limit must reject, not accept",
      "serverSeed": "boundary-rejection-server-seed",
      "clientSeed": "boundary-rejection-client-seed",
      "nonce": 172,
      "nextInt": {
        "maxExclusive": 10,
        "value": 6,
        "bytesConsumed": 2
      },
      "note": "n=10, k=1, limit=floor(256/10)*10=250. block(nonce=172) byte 0 is exactly 250 (0xFA) -- u === limit, the exact boundary. The rejection test is u < limit (never u <= limit), so this is rejected: 250 < 250 is false. The accepted draw comes from byte 1 (46, 46 < 250), value=46 % 10=6, 2 bytes consumed. A mutant testing u <= limit would instead accept at byte 0 directly: value=250 % 10=0, 1 byte consumed -- both fields differ from the correct answer above, and this is the only fixture in this file that exercises u === limit at all."
    },
    {
      "label": "mixedSequence — nextInt, nextUint53, nextFloat, nextBits, shuffle on one stream",
      "serverSeed": "mixed-sequence-server-seed",
      "clientSeed": "mixed-sequence-client-seed",
      "nonce": 3,
      "steps": [
        {
          "call": "nextInt(50)",
          "value": 23,
          "consumedAfter": 1
        },
        {
          "call": "nextUint53()",
          "value": 2395787511379310,
          "consumedAfter": 8
        },
        {
          "call": "nextFloat()",
          "value": 0.8109871620906888,
          "consumedAfter": 15
        },
        {
          "call": "nextBits(5)",
          "value": [0, 0, 1, 1, 1],
          "consumedAfter": 20
        },
        {
          "call": "shuffle(10 items)",
          "value": [1, 3, 2, 0, 7, 5, 6, 9, 4, 8],
          "consumedAfter": 29
        }
      ],
      "finalConsumed": 29
    }
  ],
  "weightedExample": {
    "label": "selectWeighted — ascending CDF walk",
    "table": [
      {
        "item": "common",
        "weight": 90
      },
      {
        "item": "rare",
        "weight": 9
      },
      {
        "item": "legendary",
        "weight": 1
      }
    ],
    "samples": [
      {
        "u53": 0,
        "result": {
          "item": "common",
          "index": 0
        }
      },
      {
        "u53": 4503599627370496,
        "result": {
          "item": "common",
          "index": 0
        }
      },
      {
        "u53": 9007199254740991,
        "result": {
          "item": "legendary",
          "index": 2
        }
      }
    ],
    "rngWeighted": {
      "serverSeed": "rng-weighted-server-seed",
      "clientSeed": "rng-weighted-client-seed",
      "nonce": 9,
      "item": "common",
      "consumed": 7,
      "note": "rng.weighted(table) is selectWeighted(table, rng.nextUint53()).item -- it costs exactly 7 bytes, the same as one nextUint53() call, never anything that scales with table.length."
    }
  }
}
