sablewaves-2.28.0.jar
Curse Maven Snippet
What's new
2.28.0 — the other half of the fps fix
and this one was costing you more the further you sailed from spawn.
before i took the wave cache from 70 milliseconds a frame down to 4.3. That answered one question and immediately asked a better one: so what's the slowest thing now?
It was Math.sin.
the number that gave it away
the sun glitter — the sparkle on the water — perturbs the surface with four sines and four cosines. per vertex. four vertices a square, nineteen thousand squares:
609,408 trigonometric calls a frame. plus whitecap shimmer, plus the surf rolling up the beach, just under a million all told.
but here's the part that took a minute to believe. i measured it twice, once near spawn and once twenty million blocks out:
| glitter cost | |
|---|---|
| near spawn | 11.3 ms/frame |
| far from spawn | 27.6 ms/frame |
same code. two and a half times slower. because the angle it's taking the sine of is your world coordinate times a number, so out at x=20,000,000 that angle is in the hundreds of millions, and Math.sin has to spend most of its effort just folding a huge number back into one turn before it can do any actual trigonometry.
so the mod's frame cost depended on where in the world you were sailing. which is a horrible thing for a frame cost to do, and completely invisible to anyone testing near spawn. which is everyone. including me, for fourteen releases.
what it is now
a sine table. 16,384 values, 64 kilobytes, looked up instead of computed. 3.6 ms, anywhere in the world.
the size wasn't a guess. i tried five of them, from 4 KB to 1 MB, and between 4 KB and 64 KB the speed is identical — the cost is the arithmetic around the lookup, not the lookup. so the biggest one that was still free won, and i took the accuracy for nothing.
and three more, in the same pass
| before | after | ||
|---|---|---|---|
| sun glitter | 27.6 ms | 3.6 ms | far from spawn |
pow(x, 0.25) in the shoaling maths |
3.66 ms | 0.43 ms | it's just two square roots |
hypot for the surf distance |
0.80 ms | 0.07 ms | |
| whitecap + surf shimmer | included above | ||
| all of it, measured together | 45.4 ms | 8.9 ms | 5.1x |
the pow one is almost funny. a quarter power is the square root of a square root, and a square root is one instruction that the hardware is required to get exactly right, whereas pow is a general routine that has to cope with any exponent you can name. same answer to one part in ten thousand trillion. eight and a half times faster.
so, both releases together
2.26.0 on a beach, far from spawn, was spending about 116 milliseconds a frame on this mod's own arithmetic. a whole frame at 60 fps is 16.7 milliseconds. so before Minecraft drew a single triangle, before any terrain or mobs or sky, the waves had set a ceiling of 8 fps — and were handing the garbage collector about four gigabytes a second on top.
it's now about 13 milliseconds. that is the entire "my fps is 5", start to finish, and neither half of it had anything to do with how many waves were on screen.
why the water looks exactly the same
a table is an approximation, so the only honest question is how wrong it is where the number ends up — not how wrong the sine is. so i measured each one separately, at its destination:
| ends up as | off by | |
|---|---|---|
| glitter | a 0-255 transparency byte | 0.096 of one byte |
| whitecap shimmer | same byte | 0.084 |
| beach foam | a brightness multiplier | 0.060 |
| surf height | actual water height, in blocks | 1/3,992 of a block |
the bytes are whole numbers, so being off by a tenth of one can't change a pixel. the surf one is different in kind — that's geometry, not colour, nothing rounds it — so it gets measured in blocks and also checked for ripple: whether neighbouring corners pick up errors in opposite directions, which would make the sea shimmer with a texture that isn't there. they don't.
and the sine table is not allowed anywhere near the actual waves. not the wave heights, not anything a boat floats on, not anything on the server, not anything that has to agree between two players' machines. sparkle, foam and shimmer only. that rule is written at the top of the file for whoever adds the next call site, including me.
three things i nearly got wrong
the one that would have been a nightmare. casting a decimal to a whole number in java doesn't wrap around, it sticks at the maximum. the table index passes that maximum once the angle passes 823,550 — which the glitter reaches at x = 74,395. so the obvious version of this code would have pinned the whole ocean to a single table entry past 74 thousand blocks out: every wave in sight glittering in perfect lockstep, and only a long way from spawn. i'd have shipped it, and then spent a day on the bug report.
my own accuracy test lying to me. my first version held the water surface at a fixed angle, so the sparkle it measured was always in the dark part of the highlight, where the number is about a billionth and any error is too. it printed a confident zero without ever once looking at a bright sparkle — which is the only place the error matters, because that maths raises things to the 128th power and multiplies any mistake by about that much. fixed, re-measured properly, and the real answer is still invisible. but it was luck that it was, not judgement.
my own checker lying to me. one of the tools that guards this mod prints how many classes do risky work at startup. it didn't budge when i added the new file, which looked like the checker had stopped seeing things. it hadn't — it counts classes that build objects at startup, and the new one just fills an array of numbers, which is a different instruction entirely. correct answer, misleading label. i fixed the label. a checker you can misread is a checker that will fool someone eventually.
boring random stuff
121 of the 124 things in the jar are untouched. one file added, one changed. no public anything altered, verified by comparing every method signature against the previous jar rather than by remembering.
there's one duplicated formula in here, which is normally a mistake: the shoaling maths now exists in two places, the original and the fast copy. it's a copy rather than an edit because that original file is the last one i'm still reading out of a decompiler, and re-deriving a wave-generation class to save three milliseconds is a bad trade. so instead there's a check that calls both of them across 160,801 inputs plus two million random ones and fails the build if they ever disagree by more than the last digit a computer can represent. 90.9% come out bit-for-bit identical. the copy can't drift without me finding out, which is better protection than editing the original would have given me.
196 checks across five test files now, plus the two that scan the finished jar.
This mod has no additional files

