1import { describe, it, expect } from "vitest";2import {3 MAYAN_MAGIC,4 MAYAN_VERSION,5 MAX_PAYLOAD_BYTES,6 RUNE_ZONE,7 QUIET_ZONE,8 calculateSymbolSize,9 encodeMayanMatrix,10 decodeMatrix,11 isReserved,12} from "./mayan";131415function mulberry32(seed: number) {16 let state = seed >>> 0;17 return () => {18 state = (state + 0x6d2b79f5) >>> 0;19 let t = state;20 t = Math.imul(t ^ (t >>> 15), t | 1);21 t ^= t + Math.imul(t ^ (t >>> 7), t | 61);22 return ((t ^ (t >>> 14)) >>> 0) / 0x100000000;23 };24}2526function randomPayload(n: number, seed = 1): Uint8Array {27 const rng = mulberry32(seed);28 const out = new Uint8Array(n);29 for (let i = 0; i < n; i++) out[i] = (rng() * 256) | 0;30 return out;31}3233function flipRandomDataBits(34 matrix: boolean[][],35 size: number,36 fraction: number,37 seed = 1,38): boolean[][] {39 const center = Math.floor(size / 2);40 const dataPositions: { r: number; c: number }[] = [];41 for (let r = QUIET_ZONE; r < size - QUIET_ZONE; r++) {42 for (let c = QUIET_ZONE; c < size - QUIET_ZONE; c++) {43 if (!isReserved(r, c, center, size)) dataPositions.push({ r, c });44 }45 }46 const rng = mulberry32(seed);47 const out = matrix.map((row) => row.slice());48 const toFlip = Math.floor(dataPositions.length * fraction);49 for (let i = 0; i < toFlip; i++) {50 const j = i + Math.floor(rng() * (dataPositions.length - i));51 [dataPositions[i], dataPositions[j]] = [dataPositions[j], dataPositions[i]];52 const { r, c } = dataPositions[i];53 out[r][c] = !out[r][c];54 }55 return out;56}5758describe("Mayan format", () => {59 it("constants are coherent", () => {60 expect(MAYAN_MAGIC).toBe(0x4d);61 expect(MAYAN_VERSION).toBe(0x01);62 expect(RUNE_ZONE).toBe(27);63 expect(QUIET_ZONE).toBe(2);64 });6566 it("calculateSymbolSize returns >=64 for tiny payloads", () => {67 expect(calculateSymbolSize(0)).toBe(64);68 expect(calculateSymbolSize(50)).toBeGreaterThanOrEqual(64);69 });7071 it("calculateSymbolSize grows with payload", () => {72 const small = calculateSymbolSize(100);73 const big = calculateSymbolSize(5000);74 expect(big).toBeGreaterThan(small);75 });7677 it("rejects payloads larger than MAX_PAYLOAD_BYTES", () => {78 const oversize = new Uint8Array(MAX_PAYLOAD_BYTES + 1);79 expect(() => encodeMayanMatrix(oversize)).toThrow(/payload too large/);80 });81});8283describe("Mayan round-trip", () => {84 it("round-trips a 5-byte payload", () => {85 const payload = new Uint8Array([1, 2, 3, 4, 5]);86 const { matrix, size } = encodeMayanMatrix(payload);87 const decoded = decodeMatrix(matrix, size);88 expect(decoded).not.toBeNull();89 expect(Array.from(decoded!)).toEqual(Array.from(payload));90 });9192 it("round-trips a 100-byte random payload", () => {93 const payload = randomPayload(100);94 const { matrix, size } = encodeMayanMatrix(payload);95 const decoded = decodeMatrix(matrix, size);96 expect(decoded).not.toBeNull();97 expect(Array.from(decoded!)).toEqual(Array.from(payload));98 });99100 it("round-trips a 500-byte payload (multi-block RS)", () => {101 const payload = randomPayload(500);102 const { matrix, size } = encodeMayanMatrix(payload);103 const decoded = decodeMatrix(matrix, size);104 expect(decoded).not.toBeNull();105 expect(Array.from(decoded!)).toEqual(Array.from(payload));106 });107108 it("round-trips a 2000-byte payload", () => {109 const payload = randomPayload(2000);110 const { matrix, size } = encodeMayanMatrix(payload);111 const decoded = decodeMatrix(matrix, size);112 expect(decoded).not.toBeNull();113 expect(Array.from(decoded!)).toEqual(Array.from(payload));114 });115116 it("rejects a matrix with no Mayan magic byte", () => {117 const fake: boolean[][] = Array.from({ length: 64 }, () =>118 new Array<boolean>(64).fill(false),119 );120 const decoded = decodeMatrix(fake, 64);121 expect(decoded).toBeNull();122 });123});124125describe("Mayan RS recovery", () => {126 127 128 129 130 it("recovers from 0.5% random bit flips in data area", () => {131 const payload = randomPayload(50);132 const { matrix, size } = encodeMayanMatrix(payload);133 const corrupted = flipRandomDataBits(matrix, size, 0.005, 42);134 const decoded = decodeMatrix(corrupted, size);135 expect(decoded).not.toBeNull();136 expect(Array.from(decoded!)).toEqual(Array.from(payload));137 });138});