/* A lemma's id — derived from what the word IS, not where it sorts. Ids used to be positions in the sorted build: the 12,000th entry got id 12000. Cards point at lemmas by id, so any change to the dictionary — one entry added near the top — silently moved every card below it onto a different word. And a custom word took max(id)+1 on the device that added it, so the same id meant different words on a phone and a laptop. Hashing (headword, pos) makes the id a property of the word: stable across rebuilds, and identical on every device that adds the same word. The build asserts the whole dictionary is collision-free. cyrb53 (bryc, public domain): 53 bits, so the result is always a safe JavaScript integer and fits SQLite's INTEGER PRIMARY KEY. It hashes UTF-16 code units, which is deterministic across engines. The separator is written as an escape, never a literal control byte. */ export function lemmaId(headword, pos) { const str = `${headword}\u0001${pos}`; let h1 = 0xdeadbeef; let h2 = 0x41c6ce57; for (let i = 0; i < str.length; i++) { const ch = str.charCodeAt(i); h1 = Math.imul(h1 ^ ch, 2654435761); h2 = Math.imul(h2 ^ ch, 1597334677); } h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507); h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909); h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507); h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909); // 0 is never a valid rowid in practice; keep it out of the id space. return 4294967296 * (2097151 & h2) + (h1 >>> 0) || 1; }