Quick answer: For values above 2^31, use buffer_f64 (full double precision) or split into two buffer_u16 writes. JavaScript’s 32-bit signed bitwise ops can’t represent the full uint32 range.
A leaderboard score of 3,000,000,000 (3 billion) saves correctly on Windows. On HTML5 export it loads back as a negative number. The buffer roundtrip went through JavaScript’s int32 bitwise ops, sign-extending the high bit.
The JavaScript Trap
JavaScript Numbers are IEEE 754 doubles (53-bit safe integer range). But bitwise operators (|, &, >>) coerce to int32 signed. GameMaker HTML5’s implementation of buffer_write_uint32 uses these ops; values above 2^31 - 1 (~2.1 billion) round-trip wrongly.
Fix 1: Use f64
/// Write
buffer_write(buf, buffer_f64, score);
/// Read
var score = buffer_read(buf, buffer_f64);
f64 (double) handles integers up to 2^53 (~9 quadrillion) exactly. 4 extra bytes per value; trivial for save files. No bitwise truncation.
Fix 2: Split into Two uint16
/// Write big value as high16:low16
buffer_write(buf, buffer_u16, (score >> 16) & 0xFFFF);
buffer_write(buf, buffer_u16, score & 0xFFFF);
/// Read
var high = buffer_read(buf, buffer_u16);
var low = buffer_read(buf, buffer_u16);
var score = high * 65536 + low;
Two 16-bit writes avoid the int32 path. Use multiplication (not shift) on read to reconstruct, since shift would re-engage int32 semantics.
Watch Out for Integer Math
Even outside buffer ops, GML code on HTML5 silently uses int32 math when you use bitwise operators. (big_value << 1) truncates. Stick to * 2 for safe multiplication.
Verifying
Test round-trip with values: 1, 1000, 1000000, 2000000000, 3000000000, 4294967295 (uint32 max). On HTML5, the last three should round-trip correctly after fix; without, they read back as negative or zero.
“HTML5’s integer math is 32-bit signed under the hood. For big numbers, use f64 or split into smaller chunks.”
Any cross-platform save file should use f64 for large integers as a defensive default — sidesteps JS’s int32 quirks entirely.