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.