DramaticShapeVoxelMod archival mirror · read-only
lib/Water.lua

Water.lua

lib/Water.lua66.7 KB1006441feef1dc7d
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440
-- Voxel world mode: water, and what it reflects.
--
-- Every other surface in this mode is opaque and is drawn once, inside the
-- terrain mesh, by the scene shader. Water is neither: it is a MIRROR, and
-- a mirror cannot be drawn until the thing it reflects already exists. So
-- the water surface is lifted out of the terrain mesh at build time
-- (ChunkMesher's water sink) and drawn as its own pass, after the world and
-- before the characters, by the shader below.
--
-- WHAT IT REFLECTS, in the order the shader resolves them:
--
--   the sky      the reflected direction is put through the SAME matrix the
--                frame is drawn with, as a point at infinity, and the canvas
--                row that lands on is looked up on Sky's own band ramp --
--                the identical texture, dither and display-mode transform
--                the painted sky uses. So the sky in the lake is the sky
--                over it: blue at noon, gold at dusk, navy under the moon,
--                and it meets the painted sky at the waterline with no seam.
--
--   the sun,     hung by ANGLE rather than by screen position, because a
--   the moon     reflected body is usually off the top of the frame and a
--                projected point is meaningless out there. The angular
--                radius is Sky.discRadius converted through the camera's own
--                field of view, so the disc on the water is exactly as big
--                as the disc in the sky -- craters, dithered rim, the
--                sunset's loom and all. This is also the specular: a low sun
--                lays a broken gold path across the water on its own, out of
--                the reflection rather than out of a highlight term.
--
--   the world    SCREEN SPACE. The reflected ray is walked forward in world
--                space, each step projected through the same matrix, looking
--                for where it passes behind what the depth buffer holds --
--                then binary-refined onto the contact and read out of a copy
--                of the frame as it stood before the water went down. Shore
--                trees, buildings, ledges and cliffs land in the water
--                because they are on screen; where the ray leaves the frame
--                or finds nothing, the sky above answers instead, which is
--                what makes the far half of a lake sky and the near half
--                scenery without a seam between them.
--
--   the cast     the walkers, the NPCs, the authored figures and a staged
--                battle's two Pokemon. Awkward, and settled by drawing them
--                twice: Gen 1 draws people OVER the world and water is
--                world, so a surfing player has to composite after the
--                water, and a reflection can only hold what came before it.
--                So they are painted into the reflection copy alone
--                (Voxel3D.beginWater), in the picture the water reflects and
--                not yet in the picture it is drawn into.
--
-- WHAT IT CANNOT REFLECT is what no screen-space reflection can: anything
-- that is not in the frame. A tree just off the top edge is not in the water
-- below it, and a ray that runs off the side of the screen fades into the
-- sky rather than ending on a line.
--
-- THE SURFACE ITSELF is not flat. It is a heightfield of one-world-pixel
-- columns, each standing a whole number of pixels tall and rising and
-- falling as waves, walked by the view ray in the pixel shader -- so the
-- bars occlude each other and show their sides without a single extra
-- vertex. See WAVE_HEIGHT and relief().
--
-- THE PASS ITSELF, and why it is shaped this way. The scene canvas carries a
-- READABLE depth canvas (Voxel3D), and a texture cannot be sampled while it
-- is bound as a render target -- so for the length of this pass the depth
-- buffer is DETACHED and the shader does the depth test itself, comparing
-- its own fragment depth against the texture it just stopped writing to.
-- That is the same test the hardware would have run, so a tree in front of a
-- pond still hides it; what it costs is depth WRITES, which water has no use
-- for anyway (it is flat, it never overlaps itself, and everything drawn
-- after it stands on top of it by construction).
--
-- Falls back all the way down. No readable depth canvas, a driver that will
-- not compile this, or the row set to OFF and the water mesh is simply drawn
-- by the ordinary scene shader -- flat animated water, exactly what the mode
-- drew before any of this existed.

-- the mod namespace (see main.lua): V.require loads a sibling module
local V = ...

local ModSetting = V.require("ModSetting")
local Sky = V.require("Sky")
local DayNight = V.require("DayNight")
local ShadowMap = V.require("ShadowMap")
local Mat4 = V.require("Mat4")

local Water = {}

-- ------- the row
--
-- Three rungs rather than a toggle, because the two halves of this cost
-- very different things. SKY is a handful of instructions per water pixel
-- and no extra buffers read; FULL adds the screen-space march, which is the
-- part that samples a depth texture twenty-odd times. A phone that wants the
-- sunset on the lake but not the ray march has somewhere to stand.
Water.KEY = "water"
Water.LABEL = "WATER"

Water.setting = ModSetting.new(Water.KEY, Water.LABEL,
                               { "full", "sky", "off" },
                               { "FULL", "SKY", "OFF" })

function Water.level()
  local v = Water.setting:get()
  if v == "off" then return 0 end
  if v == "sky" then return 1 end
  return 2
end

-- Whether the reflective pass should run at all (either rung above OFF).
function Water.enabled()
  return Water.level() > 0
end

-- ------- the look, in constants
--
-- FRESNEL. Water reflects almost nothing looked straight down at and almost
-- everything looked along, which is Schlick's curve -- and taken literally
-- it hands the top rung a mirror and the other four nothing at all. This
-- mode's rungs are named for the camera's tilt off VERTICAL, so 15 is a
-- near-overhead camera meeting the water at 15 degrees off its normal:
-- honest Schlick gives that about 2%, and even a generous floor of 0.14 was
-- invisible.
--
-- So the floor is lifted a long way above water's true 0.04 and the exponent
-- softened from 5 to 2: the SHAPE is still the honest one -- a low camera
-- still gets much more of it than a high one -- but the bottom of the curve
-- is a pond rather than a painted tile.
Water.FRESNEL_FLOOR = 0.34
Water.FRESNEL_CEIL = 0.92
Water.FRESNEL_POWER = 2.0

-- THE HORIZON LEAN, which is the other half of why the steeper rungs showed
-- nothing -- and the bigger half.
--
-- A reflection off flat water points as far ABOVE the horizon as the eye is
-- above the water. At the top rung that is 15 degrees: the reflected ray
-- grazes the sky's pale end, sweeps the sun's own path and travels far
-- enough across the screen for the march to find the shoreline. At the 15
-- rung it is 75 degrees -- straight up. Up there the sky's bands are at
-- their DARKEST (deep blue over blue water, which is no picture at all), the
-- sun and moon sit at about 6 degrees of squashed elevation and are nowhere
-- near it, and the screen-space ray leaves the top of the frame in two
-- steps. All three of those are correct, and together they are a lake with
-- nothing in it.
--
-- So the reflected direction LEANS toward the way this camera is looking, by
-- however far the camera is from having a horizon in frame. That is a
-- deliberate stylisation and it is worth being exact about what it costs and
-- what it does not:
--
--   at the rung where the horizon IS in frame the lean is ZERO, so the one
--   place the join can actually be seen -- the waterline, where the lake
--   meets the painted sky -- is still the exact reflection it was.
--
--   at the rungs where the horizon is above the top edge there is no join to
--   break, and what the lean buys is the whole of the effect: the pale bands,
--   the sunset, the moon's path, and a screen-space ray that travels ACROSS
--   the diorama instead of straight out of it.
--
-- It leans toward an ELEVATION rather than by a weight, and that matters.
-- Mixing the ray a fixed fraction of the way toward horizontal sounds like
-- the same thing and is not: the ray it starts from is different at every
-- rung, so a fixed fraction lands them all somewhere different, and the
-- middle rungs came out worst of all -- further from the sun than the
-- steepest one. Aimed at an elevation, every rung below the top one puts its
-- reflection where the TOP rung puts its own, which is the one place the
-- effect is known to work.
--
-- Measured off Voxel3D.descent -- the sine of how far below horizontal the
-- view runs -- so it answers for the battle's placed camera too, which has
-- no rung to be asked about.
Water.LEAN_FROM = 0.30         -- descent where the lean starts: the top rung's
Water.LEAN_FULL = 0.55         -- and where it is complete
-- the elevation it aims at: the one the top rung's own reflection sits at,
-- stated as that same descent so the two cannot drift apart
Water.LEAN_ELEV = math.asin(Water.LEAN_FROM)

function Water.lean(descent)
  local span = Water.LEAN_FULL - Water.LEAN_FROM
  local t = ((descent or 0) - Water.LEAN_FROM) / span
  if t <= 0 then return 0 end
  return t < 1 and t or 1
end

-- ------- the waves
--
-- Not a normal map. The surface is a HEIGHTFIELD of one-world-pixel columns
-- -- the same unit every other voxel in this mode is built from, and exactly
-- one texel of the water tile (a tile is 8 texels across 8 world pixels) --
-- and every column stands at a whole number of pixels. So the water is a
-- field of little square bars rising and falling on their own, which is what
-- water made of pixels should look like from a camera that can see it in 3D.
--
-- It is drawn without any extra geometry. The mesh is still one flat quad
-- per tile; the columns are found by walking the view ray down through the
-- slab in the pixel shader (relief mapping) and taking the first one it
-- meets. That is what makes them read as SOLID rather than as shading: a
-- tall bar hides the shorter ones behind it, you see the SIDE of the ones
-- facing you, and the whole field parallaxes against the plane as the camera
-- moves. The side faces wear the mesh's own direction shading
-- (Voxel3D.FACE_SHADE, sent in rather than restated) so a wave crest is lit
-- like every other voxel in the world.
--
-- HEIGHT is in world pixels: the tallest a column may stand above the plane
-- the quad is drawn on, and so both the amplitude and the number of rungs a
-- crest can climb through (five gives six).
--
-- It is well past the 2px recess TileShape sinks water into, which is a
-- deliberate look rather than an oversight: the crests are RELIEF, drawn
-- inside the water quad's own screen footprint, so a bar that reaches above
-- the shoreline cannot actually spill over the bank -- it is clipped at the
-- water's edge like everything else this pass draws. What it buys is a
-- surface with real swell in it instead of a two-rung terrace.
Water.WAVE_HEIGHT = 5

-- ------- the trains
--
-- Each is { fx, fz, speed, weight }. The vector is the train's DIRECTION and
-- its frequency in one -- the crest runs across it, and two pi over its
-- length is the wavelength in world pixels -- and `speed` is what walks it.
--
-- The first one dominates, and that weighting is the whole difference
-- between water and soup: a wave has a direction, and its crest is a line
-- running across it for as far as the surface goes. Three trains of equal
-- weight cancel and reinforce in patches instead, and the field comes out as
-- round islands of raised pixels with no travel to them.
--
-- Long, too: the dominant wavelength is about forty world pixels, five
-- tiles, so a crest is a run of hundreds of columns at one height with a
-- step down either side. Pitched anywhere near a pixel they stop being waves
-- and become static -- every column its own island.
--
-- Read into the shader source rather than sent as uniforms, so the rate
-- below can be derived from the same numbers the field is built out of.
Water.WAVE_TRAINS = {
  { 0.150, 0.062, 1.60, 0.60 },
  { 0.058, 0.132, -1.05, 0.29 },
  { -0.041, 0.033, 0.55, 0.11 },
}

-- ------- and what keeps them from reading as one pattern
--
-- Three fixed trains are still an exactly periodic field: every forty-odd
-- pixels of sea wears the same crest at the same height, and a lake's worth
-- of that reads as wallpaper. Real swell varies two ways a sum of sines
-- cannot: waves arrive in SETS -- a few tall ones, then a lull -- and a
-- crest line curves as it runs rather than ruling itself across the whole
-- surface. Both are put back with one long-wavelength field each, riding
-- the DOMINANT train only; the two lesser trains stay plain, because they
-- are texture rather than structure and three modulators is soup again.
--
-- Both wear the trains' own shape, { fx, fz, speed, x }: a direction whose
-- length is the spatial frequency, a phase rate, and what the field does.
-- Their wavelengths sit four to five times the carrier's, far enough apart
-- that neither reads as a wave itself -- the swell as slow weather over the
-- crests, the bend as the crests' own drift.
--
-- THE SWELL scales the dominant train's amplitude; `x` is the DEPTH of the
-- deepest lull, as the fraction of the train it takes away. It runs roughly
-- along the carrier's own direction and slower than it, which is a wave
-- group's honest habit (deep-water groups travel at about half the phase
-- speed) -- so sets of crests swell up, march a while, and hand over to a
-- calm patch that is itself moving.
Water.WAVE_SWELL = { 0.0325, 0.0134, 0.55, 0.35 }

-- THE BEND adds a slow wobble to the dominant train's phase; `x` is the
-- wobble's reach in RADIANS of carrier phase. 1.1 radians against a carrier
-- of about forty pixels bows a crest some seven pixels off its line over
-- the bend's own hundred-and-seventy-five -- a visible curve, not a
-- scribble -- and it runs ACROSS the carrier, which is the direction a
-- crest line actually wanders. What it costs is exactness in waveRate's
-- derivation: the carrier's local frequency now breathes around the number
-- the rate is derived from, so the one-pixel step is the average step
-- rather than every step's. The step CLOCK is untouched; only how far a
-- bowed stretch of crest moves on one tick varies, and by under a pixel.
Water.WAVE_BEND = { -0.0138, 0.0333, 0.35, 1.10 }

-- ------- and the beat they move on
--
-- The surface does not slide, it advances in STEPS, off the engine's own
-- frame counter -- the move that makes this read as art rather than as a
-- simulation someone forgot to stylise. A surface built out of whole pixels
-- that crawls between them smoothly gives away that the quantisation is
-- only skin deep.
--
-- 12 a second, a shade under the 15 hand-drawn pixel art is usually
-- animated at: the crests were hurrying, and a big wave is slower than a
-- sprite's walk cycle. Still a clean divisor of the engine's 60, so every
-- step spans the same whole number of frames.
Water.WAVE_FPS = 12

-- How far the dominant train advances each of those steps, in WORLD PIXELS.
-- One is the honest choice for a stepped surface: the whole field shifts by
-- exactly one pixel per frame, so nothing ever lands half-way between two.
-- The rate below is derived from it rather than tuned beside it, so changing
-- a wavelength moves the speed with it instead of quietly desynchronising.
Water.WAVE_PIXELS_PER_STEP = 1

-- Radians of wave phase per second. A train travels `speed / frequency`
-- world pixels per radian of phase, so the phase that moves the dominant one
-- a pixel is its frequency over its speed -- times the step rate.
function Water.waveRate()
  local t = Water.WAVE_TRAINS[1]
  local freq = math.sqrt(t[1] * t[1] + t[2] * t[2])
  local speed = math.abs(t[3])
  if not (freq > 0 and speed > 0) then return 0 end
  return Water.WAVE_PIXELS_PER_STEP * (freq / speed) * Water.WAVE_FPS
end
-- Relief samples down through the slab. With the stride pinned at one world
-- pixel (see WAVE_STRIDE) this is also how FAR the march can see: sixteen
-- samples, sixteen pixels of parallax, which covers the slab at every rung
-- but the very lowest and leaves the rest to fade out honestly.
--
-- The pass early-outs entirely (see relief) whenever the camera is steep
-- enough that the whole slab projects to under a pixel across, which is most
-- of the ladder -- so the cost of this only lands where it buys something.
Water.WAVE_STEPS = 16

-- The furthest one relief sample may travel ACROSS the surface, in world
-- pixels -- which is what bounds how far the march runs in total.
--
-- The march's reach is the slab's depth over the ray's descent, so it grows
-- without limit as the camera flattens: at the top rung, fragments near the
-- horizon look along the water at a few degrees and the reach runs to
-- hundreds of world pixels. Spread over a fixed number of samples that steps
-- clean over whole crests, and the surface comes apart into streaks running
-- away from the eye. Capping the span is what keeps a sample worth taking;
-- what it costs is parallax on the far water, where the columns are under a
-- pixel across and there was nothing left to see anyway.
--
-- This is the FLOOR on it. The stride the march actually takes is a SCREEN
-- pixel's worth of surface, which is the only rate that makes sense:
--
--   up close, a screen pixel is a fraction of a world pixel, so the stride
--   sits on this floor of one world pixel and the march visits every column
--   on its path. It has to: a column is one world pixel wide, a longer
--   stride steps over columns, and which ones it misses changes from
--   fragment to fragment -- neighbouring pixels landing on different columns
--   at different heights wearing different faces. That is peppery noise.
--
--   far away, a screen pixel already spans several world pixels, so a stride
--   that matches it skips columns the screen could not have resolved anyway.
--   Holding it at one world pixel out there does not buy detail, it just
--   runs out of samples -- and a march that runs out stops part-way down the
--   slab and reports the surface as flat, which is why the lowest rung lost
--   its waves entirely across the whole middle distance.
Water.WAVE_STRIDE = 1

-- How far the wave field's own gradient tilts the REFLECTION. A multiplier
-- on the SMOOTH surface's slope, not on the stepped one -- see waveNormal
-- for why that distinction is the whole difference between a moon on the
-- water and confetti. The field's gradient peaks around 0.06 per world
-- pixel, so this lands the steepest faces about twelve degrees off vertical:
-- enough to sweep a low sun or moon into a broken glitter path down the
-- lake, and not so much that the sky's own bands come apart.
Water.WAVE_SLOPE = 3.5
-- and how far the horizon lean is allowed to open that up, since it squashes
-- the same tilt on its way past (see LEAN_FROM)
Water.WAVE_SLOPE_LEAN = 1.5

-- THE MARCH. Steps are in world pixels and lengthen as they go: near the
-- surface the reflection needs precision (a shoreline is a few pixels), far
-- from it reach matters more than accuracy, and a geometric ramp gets both
-- out of one loop. RAY_STEPS is compiled in -- GLSL wants a constant bound.
Water.RAY_STEPS = 24
Water.RAY_REFINE = 5           -- halvings once a crossing is found
Water.RAY_STEP = 3.0           -- world pixels in the first step
-- and the ratio each step after it. 3 x (1.18^24 - 1) / 0.18 is about 930
-- world pixels of reach -- three view-heights, past which a reflection is
-- faded out anyway (see the tail fade in march) and the sky is the honest
-- answer: distant water reflects haze, which is what the bands already are.
Water.RAY_GROW = 1.18
-- How far behind the depth buffer a crossing may land and still count, as a
-- multiple of the depth the step itself covered. A ray that dives far past
-- what it crossed went BEHIND a thin thing rather than hitting it -- the
-- classic screen-space smear, where a tree between the camera and the pond
-- paints itself across the water -- and this is the test that drops it.
Water.RAY_THICK = 1.6
Water.EDGE_FADE = 0.14         -- reflection eased off over this much of the frame

-- ------- the shader
--
-- The scene shader's own vertex path, plus the world position the geometry
-- was actually DRAWN at -- after the world curve, because that is the space
-- the surface the eye MEETS lives in: which wave column a screen pixel is
-- looking at is a question about the geometry as drawn, and relief() answers
-- it there. (The curve only ever moves Y, so a fragment's world XZ is the
-- same on both sides of it and the ripple can be measured off this one too.)
--
-- WHAT IT REFLECTS is worked out on the other side of the bend, in the FLAT
-- world, and this is the same rule the rest of the mode keeps: the curve
-- tips the world away and the things standing on it do not lean with it (see
-- WorldCurve -- buildings stay upright, shadows are resolved before the bend
-- and ride along). A lake is one of those things. Reflect off the bowl the
-- bend has made instead and the far half of a pond is a mirror tilted twenty
-- degrees: it throws the ray past the vertical, where the sky ramp's own
-- measure -- a screen row, through the frame's matrix -- swings from one end
-- of the ramp to the other across a single column, and the pond comes out
-- with hard-edged patches of the wrong sky stamped into it -- the overhead
-- band and the horizon band abutting in the middle of a lake, which reads as
-- something other than water showing through. The same tilt sends the
-- screen-space march grazing along the bank instead of over it, which is the
-- other half: the dock and the roofs smeared across the harbour.
--
-- So the reflection is taken with the flat view ray about the flat normal,
-- exactly as it would be with the curve off -- and the MARCH still has to
-- walk the world as drawn, because that is what the depth buffer holds. Both
-- at once: the ray is straight in the flat world, and project() bends each
-- sample on its way to the screen, which is the same displacement the vertex
-- stage applies and therefore lands in the same place the geometry did.
local SHADER_SRC = [[
varying float vShade;
varying vec3 vSun;
// World position, as drawn -- and a varying that cannot ride GLSL ES's
// mediump fragment default: everything below floors it into columns and
// marches it through the frame's matrices, and a route's coordinates run
// to a few thousand, where fp16 has no fraction left at all. The same
// reasoning the scene shader's vGrid states at length.
varying LOVE_HIGHP_OR_MEDIUMP vec3 vBent;

#ifdef VERTEX
uniform mat4 vp;
uniform mat4 model;
uniform mat4 sunVP;
uniform vec3 curve;          // xy = the focus in world XZ, z = k; 0 = off
attribute float VertexShade;

vec4 position(mat4 transform_projection, vec4 vertex_position) {
  vShade = VertexShade;
  vec4 w = model * vertex_position;
  vSun = (sunVP * w).xyz;
  if (curve.z > 0.0) {
    vec2 cd = w.xz - curve.xy;
    w.y -= dot(cd, cd) * curve.z;
  }
  vBent = w.xyz;
  return vp * w;
}
#endif

#ifdef PIXEL
// Everything below works in WORLD units through the frame's own matrices,
// and GLSL ES defaults fragment floats to mediump -- fp16, out of fraction
// by a coordinate of two thousand and quantising a depth into steps the
// march falls straight through. Worse than wrong pictures: `vp` is
// declared by BOTH stages, the vertex side's default is highp, and GLSL ES
// refuses to LINK a uniform whose precision the two stages disagree on --
// which is not broken water but NO water shader at all, the flat fallback
// with nothing in the log. One statement lifts the whole stage; the guard
// keeps the odd GPU without fragment highp compiling, and such a driver
// falls back to flat water exactly as it did before this pass existed.
#ifdef GL_ES
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#endif
#endif
uniform mat4 vp;
uniform vec3 eye;
uniform vec2 screen;         // the canvas, in pixels
uniform float cell;          // one diorama pixel, in canvas pixels
uniform float pxAngle;       // radians of view one screen pixel subtends
// The same bend the vertex stage applied. This stage has to undo it to get
// back to the flat world it reflects in, and re-apply it on every marched
// sample to get back to the screen. Declared in both stages, like `vp`, and
// both are highp here.
uniform vec3 curve;          // xy = the focus in world XZ, z = k; 0 = off
// The viewport, exactly as the scene shader takes it: centre in world
// pixels, then half-size / one-over-fade / kind (0 off, 1 box, 2 ball, 3
// the staged fight's pillar), plus the box's two half-extents. Water is
// world like anything else, and a lake left lying outside the model would
// be the one thing floating in the sky.
uniform vec3 cullAt;
uniform vec3 cullShape;
uniform vec2 cullRect;

float dioramaCull(vec3 p) {
  if (cullShape.z <= 0.5) return 1.0;
  vec3 cd = p - cullAt;
  float inside;
  if (cullShape.z < 1.5) {
    inside = min(cullRect.x - abs(cd.x), cullRect.y - abs(cd.z));
  } else if (cullShape.z < 2.5) {
    inside = cullShape.x - length(cd);
  } else {
    inside = cullShape.x - length(cd.xz);
  }
  return clamp(inside * cullShape.y, 0.0, 1.0);
}

// How far the bend has pushed the world down at world XZ `q` -- the vertex
// stage's own displacement, as a number this stage can add and subtract.
// Zero when the curve is off, which is the shader's "skip it" everywhere.
float bendDrop(vec2 q) {
  if (curve.z <= 0.0) return 0.0;
  vec2 d = q - curve.xy;
  return dot(d, d) * curve.z;
}

// the sun's own pass, exactly as the scene shader reads it
uniform Image sunMap;
uniform float sunDark;
uniform float sunBias;
uniform vec2 sunTexel;
uniform vec3 dayTint;

// the frame as it stood before the water went down, and its depth. The
// depth sampler is qualified because GLSL ES defaults samplers to LOWP no
// matter what floats are set to, and eight bits of depth is a march with
// nothing to land on. The frame copy is honest 8-bit colour and can stay.
uniform Image reflectTex;
uniform LOVE_HIGHP_OR_MEDIUMP Image depthTex;

uniform float rays;          // 0 = sky only, 1 = march the screen too
uniform vec3 lookFlat;       // the way the horizon lies from this camera
uniform float lean;          // and how far the reflection tilts toward it
uniform float leanElev;      // the elevation it aims at, in radians
uniform float waveHeight;    // the tallest column, in whole world pixels
uniform float waveSlope;     // how far a column's neighbours tilt its normal
uniform float waveSlopeLean; // and how far the horizon lean may open that up
uniform float waveT;
uniform vec4 faceShade;      // the mesh's own direction shading: E, W, S, N
uniform vec2 atlasSize;      // the tileset atlas, in texels
uniform float fresnelFloor;
uniform float fresnelCeil;
uniform float fresnelPower;
uniform float rayStep;
uniform float rayGrow;
uniform float rayThick;
uniform float edgeFade;

// the sky, as Sky paints it
uniform Image skyRamp;
uniform float skyCount;
uniform float skyEdge;       // the sky's bottom, in canvas pixels
uniform float skyStart;      // where the checker begins inside a band
uniform float skyOn;         // 0 indoors: there is no sky to reflect

// and what hangs in it
uniform vec3 bodyDir;
uniform float bodyOn;
uniform float bodyMoon;
uniform float bodyAng;       // the disc's angular radius, in radians
uniform vec3 bodyCore;
uniform vec3 bodyMain;
uniform vec3 bodyDark;
uniform float glowAmt;
uniform float glowReach;     // in radians, like bodyAng
uniform vec3 glowColor;

#ifdef VOXEL_GRID
uniform float gridDark;
uniform float gridWidth;
#endif

// ------- the sun's pass (the scene shader's, verbatim)

// One shadow tap: 1 where the sun reaches, 0 where something blocks it --
// EXCEPT that water declines one kind of blocker.
//
// The sun pass marks the cast in the blue channel (ShadowMap.sprites), and
// water ignores those. A character standing at a lake's edge laid a hard
// cut-out of its own sprite across the surface, and on something that is
// already showing the sky, the shoreline and the trees behind it, a
// silhouette of somebody reads as a sticker on the water rather than as a
// shadow in it. Everything the WORLD casts -- trees, buildings, cliffs,
// ledges -- still shades it, which is the half that was worth having.
float sunLit(vec2 uv, float z) {
  vec4 c = Texel(sunMap, uv);
  return max(step(z, c.r + c.g * (1.0 / 255.0)), c.b);
}

float sunlight(vec3 p) {
  if (sunDark <= 0.0) return 1.0;
  if (p.x < 0.0 || p.x > 1.0 || p.y < 0.0 || p.y > 1.0 || p.z > 1.0) {
    return 1.0;
  }
  vec2 e = min(p.xy, 1.0 - p.xy);
  float edge = smoothstep(0.0, 0.06, min(e.x, e.y));
  if (edge <= 0.0) return 1.0;
  float z = p.z - sunBias;
  float lit = sunLit(p.xy + sunTexel * vec2(-0.5, -0.5), z)
            + sunLit(p.xy + sunTexel * vec2( 0.5, -0.5), z)
            + sunLit(p.xy + sunTexel * vec2(-0.5,  0.5), z)
            + sunLit(p.xy + sunTexel * vec2( 0.5,  0.5), z);
  return 1.0 - sunDark * edge * (1.0 - lit * 0.25);
}

#ifdef VOXEL_GRID
// The wireframe, ruled on the COLUMNS rather than on the flat sheet they
// stand on.
//
// The scene shader reads a mesh's own model space, and for water that is the
// base plane -- so it would draw a grid across a flat sheet and ignore the
// bars entirely, which is the one thing that would give away that they are
// bars. What has to be outlined is what is actually SEEN: the column the ray
// landed on, at the height it landed at, so every voxel of water reads as
// its own block with its own edges.
//
// `p` is that hit; `base` is the smooth plane under it, and the derivative
// comes from THERE. `p` jumps a whole column between neighbouring fragments,
// so fwidth() of it reports a step rather than a scale and every column edge
// would blow out into a band. The plane underneath is smooth, and is the
// same scale in x and z that the columns are built on.
//
// `axis` is the direction the face does not vary along -- the top's own y,
// or a side's x or z. Its distance to the nearest plane is a constant zero,
// and taken at face value it floods the whole face solid; pushed out of
// reach it simply drops out, exactly as the scene shader's own seam handles
// the axis a face's normal points along.
float columnSeam(vec3 p, vec3 base, float axis) {
  vec3 w = fwidth(base);
  float wide = max(w.x, w.z);
  // the plane has no vertical extent of its own to measure, so y borrows
  // the horizontal scale -- it sets a line's THICKNESS and nothing else
  w.y = wide;
  vec3 d = abs(fract(p + 0.5) - 0.5);
  vec3 px = d / max(w, vec3(1e-6));
  if (axis < 0.5) { px.y += 1e6; }
  else if (axis < 1.5) { px.x += 1e6; }
  else { px.z += 1e6; }
  float near = min(min(px.x, px.y), px.z);
  // Fade out where a column is too small on screen to hold a line at all,
  // or the far water turns into a flat wash of seams rather than a grid.
  //
  // It holds on further than the scene shader's own does. That one is ruling
  // seams across whole 16px walls and roofs; these are one world pixel
  // apart, so the fade starts biting while the water is still perfectly
  // readable -- and at the lowest rung, where the middle distance is most of
  // the frame, it took the grid off nearly all of it. Full lines by a pixel
  // and a half of screen space, gone under three quarters of one.
  float span = 1.0 / max(wide, 1e-6);
  float fade = clamp((span - 0.75) * 1.35, 0.0, 1.0);
  return fade * clamp(gridWidth * 0.5 + 0.5 - near, 0.0, 1.0);
}
#endif

// ------- the sky, by direction

vec3 bandAt(float i) {
  return Texel(skyRamp,
               vec2((clamp(i, 0.0, skyCount - 1.0) + 0.5) / skyCount, 0.5)).rgb;
}

// Where a DIRECTION lands on the sky's own gradient, as a band coordinate
// in [0, count]: 0 is straight overhead, count the horizon.
//
// Measured by putting the direction through the very matrix the frame is
// drawn with, as a point at infinity -- which is how Voxel3D finds both the
// vanishing line and the sun's place on the canvas. So the reflected sky and
// the painted sky are answering the same question with the same arithmetic,
// and they agree at the waterline for free at any pitch, fov or zoom.
//
// A direction whose w comes out negative is BEHIND the camera plane, which
// for an upward reflection means near-vertical: the top band, overhead.
float skyPos(vec3 d) {
  vec4 c = vp * vec4(d, 0.0);
  if (c.w <= 1e-6) return 0.0;
  float py = (c.y / c.w * 0.5 + 0.5) * screen.y;
  float row = floor(py / cell) * cell;
  return clamp(row / max(skyEdge, 1.0), 0.0, 1.0) * skyCount;
}

// `parity` is the diorama checkerboard this fragment sits on -- the same
// one Sky's own dither is cut from, so the reflected gradient breaks up in
// the same 8-bit way rather than being the one smooth thing in the frame.
vec3 skyAt(vec3 d, float parity) {
  float pos = skyPos(d);
  float base = min(floor(pos), skyCount - 1.0);
  vec3 c = bandAt(base);
  if (base < skyCount - 1.0 && (pos - base) > skyStart && parity < 0.5) {
    c = bandAt(base + 1.0);
  }
  return c;
}

float crater(vec2 p, vec2 c, float r) {
  vec2 dd = p - c;
  return step(dot(dd, dd), r * r);
}

// The sun or moon, and the twilight warmth around it, laid over the bands.
//
// By ANGLE, not by screen position: the reflected direction usually
// projects off the top of the frame entirely, where screen distances stop
// meaning anything. bodyAng is Sky.discRadius run back through the camera's
// field of view, so this disc is the same size as the painted one.
vec3 bodyAt(vec3 d, vec3 c, float parity) {
  if (bodyOn <= 0.0) return c;
  float ang = acos(clamp(dot(d, bodyDir), -1.0, 1.0));
  if (glowAmt > 0.0) {
    float g = glowAmt * pow(clamp(1.0 - ang / glowReach, 0.0, 1.0), 2.0);
    float lvl = floor(g * 4.0);
    if (g * 4.0 - lvl > 0.5 && parity < 0.5) { lvl += 1.0; }
    c = mix(c, glowColor, min(lvl / 3.0, 1.0) * 0.65);
  }
  if (ang > bodyAng) return c;
  float t = ang / bodyAng;
  // the dithered rim, exactly as the painted disc keeps one parity of its
  // outer ring of cells
  if (t > 0.86 && parity < 0.5) return c;
  vec3 disc = (t <= 0.5) ? bodyCore : bodyMain;
  if (bodyMoon > 0.5) {
    // disc-local coordinates: a frame built off world up, so the craters
    // sit on the moon the same way round every night
    vec3 t1 = normalize(cross(vec3(0.0, 1.0, 0.0), bodyDir));
    vec2 dc = vec2(dot(d, t1), dot(d, cross(bodyDir, t1))) / bodyAng;
    float k = 0.0;
//@CRATERS
    if (k > 0.0) { disc = bodyDark; }
  }
  return disc;
}

// ------- the screen-space march

// A point as (uv, depth, valid), through the very matrix the frame was drawn
// with. The uv and the depth are the same numbers the hardware wrote -- the
// clip-space Y flip is already baked into `vp`, and a canvas texture's v runs
// the same way its pixel rows do, so one 0.5x+0.5 answers for both.
//
// The point arrives in the FLAT world -- the space the ray is straight in --
// and is bent here, by the same displacement the vertex stage applied, so it
// lands exactly where the geometry it is being compared against landed. That
// split is the whole trick: the reflection is worked out in a world that has
// not been tipped, and every sample of it is tipped on the way to the screen,
// so the march reads the depth buffer it actually has.
vec4 project(vec3 p) {
  p.y -= bendDrop(p.xz);
  vec4 c = vp * vec4(p, 1.0);
  if (c.w <= 1e-6) return vec4(0.0, 0.0, 0.0, 0.0);
  return vec4(c.xy / c.w * 0.5 + 0.5, c.z / c.w * 0.5 + 0.5, 1.0);
}

// Walk the reflected ray until it passes behind the depth buffer. Returns
// the colour found in .rgb and how much of it to believe in .a -- 0 for a
// ray that left the frame, ran out of steps, or crossed something it went
// straight through rather than landed on.
vec4 march(vec3 origin, vec3 dir) {
  vec4 miss = vec4(0.0, 0.0, 0.0, 0.0);
  vec3 a = origin;
  vec4 pa = project(a);
  if (pa.w < 0.5) return miss;
  float len = rayStep;
  for (int i = 0; i < RAY_STEPS; i++) {
    vec3 b = a + dir * len;
    vec4 pb = project(b);
    if (pb.w < 0.5) return miss;
    if (pb.x < 0.0 || pb.x > 1.0 || pb.y < 0.0 || pb.y > 1.0) return miss;
    float scene = Texel(depthTex, pb.xy).r;
    if (pb.z > scene) {
      // how much depth this one step covered: the yardstick for whether
      // the crossing is a surface or a thin thing the ray shot past
      float span = max(abs(pb.z - pa.z), 1e-7);
      if (pb.z - scene > span * rayThick) return miss;
      // binary-refine onto the contact
      vec3 lo = a;
      vec3 hi = b;
      for (int k = 0; k < RAY_REFINE; k++) {
        vec3 m = (lo + hi) * 0.5;
        vec4 pm = project(m);
        if (pm.z > Texel(depthTex, pm.xy).r) { hi = m; } else { lo = m; }
      }
      vec4 hit = project(hi);
      if (hit.w < 0.5) return miss;
      // Ease out at the frame's rim, where the reflection is about to run
      // off the only evidence there is -- and with distance travelled, so a
      // long ray hands back to the sky instead of ending on a hard edge.
      //
      // The distance term is doing two jobs. It hides the march's own tail,
      // where the steps are longest and a grazing crossing is least likely
      // to be a real surface -- and it is also true: distant water reflects
      // haze rather than detail, and the haze is what the bands underneath
      // already are. The small floor keeps a genuine far hit as a trace
      // rather than deleting it.
      vec2 e = min(hit.xy, 1.0 - hit.xy);
      float edge = smoothstep(0.0, edgeFade, min(e.x, e.y));
      float far = 1.0 - clamp(float(i) / float(RAY_STEPS), 0.0, 1.0);
      return vec4(Texel(reflectTex, hit.xy).rgb, edge * (0.15 + 0.85 * far));
    }
    a = b;
    pa = pb;
    len *= rayGrow;
  }
  return miss;
}

// ------- the surface, as a field of pixel-tall columns

// How high the column at world pixel `q` stands, in WHOLE world pixels.
//
// Whole, because that is what makes them BARS: a column is a voxel like
// every other voxel in this mode, one unit on a side, and a surface that
// stepped in fractions would just be a smooth wave with extra arithmetic.
// Three crossing wave trains, so the field has no readable repeat inside a
// lake's worth of pixels.
//
// The SMOOTH surface underneath, 0 to 1 -- the thing the columns are a
// quantisation of. Summed from Water.WAVE_TRAINS, which is where the trains
// and the reasoning behind their weights live; pasted in rather than sent,
// so the speed derived from those same numbers cannot drift from the field
// they describe.
float waveRaw(vec2 q) {
  float h = 0.0;
//@TRAINS
  return h * 0.5 + 0.5;
}

// and the voxel surface: that field, in whole world pixels.
float waveAt(vec2 q) {
  if (waveHeight <= 0.0) return 0.0;
  return floor(waveRaw(q) * waveHeight + 0.5);
}

// The tilt this column reflects with -- taken from the SMOOTH field, not
// from the stepped one, and this is the difference between a moon on the
// water and confetti.
//
// Floored heights are integers, so their differences are integers too: a
// column's neighbours are level with it or a whole pixel off, and nothing in
// between. Build the normal out of THOSE and the reflected ray can only ever
// point in about five directions -- straight up, or rotated by twice the
// arctangent of one step, or of two. A flat sky does not mind; the sun and
// the moon are discs barely two degrees across, and a ray that jumps in
// eighteen-degree increments simply steps over them. The lake goes dark and
// the odd column that happens to land dead on flares -- which is exactly
// what "the moon doesn't reflect right" looks like.
//
// The columns are an approximation of a real surface, and light reflects off
// the surface being approximated. So the SHAPE stays quantised -- it is what
// you see, and it is the whole point -- while the normal is read off the
// smooth field the shape is made from. Still one answer per column, because
// `q` is an integer: pixel-quantised in space, continuous in value, which
// puts the glitter path back without softening a single edge.
//
// Forward differences over one pixel: three samples, and the answer only has
// to say which way this piece of the surface leans.
vec3 waveNormal(vec2 q, float tilt) {
  float h = waveRaw(q);
  float e = waveRaw(q + vec2(1.0, 0.0)) - h;
  float s = waveRaw(q + vec2(0.0, 1.0)) - h;
  return normalize(vec3(-e * tilt, 1.0, -s * tilt));
}


// Walk the view ray down through the wave slab and return the column it
// actually meets -- RELIEF MAPPING, and the whole reason the bars read as
// solid rather than as a pattern painted on a flat sheet.
//
// The mesh is still one flat quad per tile, so what gets rasterised is the
// point where the ray crosses the BASE plane. The visible surface is
// somewhere above that, and the two differ by more the lower the camera
// sits. So the ray is walked BACKWARD to the top of the slab and then
// stepped down: the first column whose top it falls below is what the eye is
// looking at, and everything shorter behind that column is hidden by it for
// free, because the march simply never reaches it.
//
// A step that lands below a column's top having just ARRIVED in that column
// is looking at its side; one that was already there and fell through is
// looking at its top. That is the whole of the face test, and it is what
// gives a crest a lit face and a shaded one.
//
// `axis` names which way the face it found points -- 0 top, 1 east/west, 2
// north/south -- because the wireframe needs to know the one direction the
// face does not vary along (see columnSeam).
void relief(vec3 base, vec3 dir, out vec3 hit, out vec2 col, out float face,
            out float axis) {
  col = floor(base.xz);
  hit = base;
  face = 1.0;
  axis = 0.0;
  float dy = -dir.y;
  // a ray running level along the surface has no slab to walk through, and
  // dividing by its descent would send the start point to infinity
  if (waveHeight <= 0.0 || dy < 0.02) return;
  float across = length(dir.xz);
  float reach = waveHeight / dy;
  // How far across the surface the whole slab displaces the answer. Under
  // half a pixel it cannot pick a different column than the one already
  // under the fragment, so the march would spend its samples arriving where
  // it started -- which is exactly the case at the steep rungs, where the
  // camera looks nearly straight down the columns and there is no side of a
  // bar to see anyway.
  float span = reach * across;
  if (span < 0.5) {
    hit.y = base.y + waveAt(col);
    return;
  }
  // and the other end: `reach` grows as one over the descent, so a grazing
  // ray asks for hundreds of world pixels of march from a fixed number of
  // samples.
  //
  // What one sample is worth is a SCREEN pixel of surface, so that is the
  // stride (see WAVE_STRIDE). A screen pixel covers this much of the water:
  // the distance to the eye times the angle one pixel subtends, opened out
  // by the obliquity -- a surface seen edge-on runs away far faster per
  // pixel than one seen face-on. Floored at a world pixel, because up close
  // a finer stride than the columns themselves buys nothing and skipping
  // them costs everything.
  float dist = length(base - eye);
  float stride = max(WAVE_STRIDE, dist * pxAngle / dy);
  float maxSpan = float(WAVE_STEPS) * stride;
  if (span > maxSpan) { reach = maxSpan / max(across, 1e-4); }
  vec3 top = base - dir * reach;
  vec2 wasCol = floor(top.xz);
  for (int i = 1; i <= WAVE_STEPS; i++) {
    vec3 p = mix(top, base, float(i) / float(WAVE_STEPS));
    vec2 q = floor(p.xz);
    float y = base.y + waveAt(q);
    if (p.y <= y) {
      col = q;
      hit = vec3(p.x, y, p.z);
      vec2 d = q - wasCol;
      if (abs(d.x) + abs(d.y) < 0.5) {
        face = 1.0;                                  // fell through the top
        axis = 0.0;
      } else if (abs(d.x) > abs(d.y)) {
        face = (d.x > 0.0) ? faceShade.y : faceShade.x;   // west : east
        axis = 1.0;
      } else {
        face = (d.y > 0.0) ? faceShade.w : faceShade.z;   // north : south
        axis = 2.0;
      }
      return;
    }
    wasCol = q;
  }
}

// The water's own art, read at the column the ray landed on rather than at
// the fragment's own place on the flat quad -- otherwise the bars parallax
// away and the pixels they are made of stay behind on the plane.
//
// Read off the COLUMN, not off the fragment.
//
// One world pixel is one atlas texel exactly, and the mesher lays a tile's
// eight texels across its eight world pixels -- so the column at world
// (cx, cz) wears texel (cx mod 8, cz mod 8) and nothing else. That makes the
// lookup exact, and far more importantly STABLE: the art a column shows
// depends only on where that column stands in the world, so it cannot swim
// as the camera moves and two fragments that landed on the same column
// cannot disagree about it.
//
// Offsetting the fragment's own uv by the parallax instead makes the art
// depend on how far the march happened to travel -- and wherever the march
// skipped a column, neighbouring fragments picked texels several apart. That
// is what peppered the surface with noise, and why it cleared up in patches:
// the patches are where the march was not skipping.
//
// The tile origin is the FRAGMENT's, so the lookup can never leave the tile
// this quad was built to sample -- the same bleed the mesher's INSET stops.
vec2 waveUV(vec2 tc, vec2 col) {
  vec2 texel = 1.0 / atlasSize;
  vec2 tile = 8.0 * texel;
  vec2 org = floor(tc / tile) * tile;
  return org + (mod(col, 8.0) + 0.5) * texel;
}

// The float parameters are pinned to mediump BECAUSE the stage default is
// not: LOVE's own header forward-declares effect() under its default, and
// at least one mobile compiler (Samsung's Xclipse, in so many words) holds
// that a definition whose parameter precisions differ from its prototype's
// is a second function of the same name, and refuses the pair. The params
// can afford it -- the colour is a colour, and tc/sc arrived through
// LOVE's mediump plumbing whatever this signature says -- and the maths
// below runs on the stage default the moment the values touch a local.
//
// Which precision that has to BE is not ours to know: LOVE 12 forward-
// declares effect() under a different one, and pins that matched 11's
// prototype are the mismatch there -- the same refusal, from the other
// side, with the water falling back to flat. So the qualifier is a define
// the Lua side fills in, and Water.shader compiles the pinned form first
// and the bare one only if that is refused. Whichever prototype a runtime
// brought, one of the two agrees with it.
vec4 effect(EFFECT_PREC vec4 color, Image tex, EFFECT_PREC vec2 tc,
            EFFECT_PREC vec2 sc) {
  // THE DEPTH TEST, done here because the buffer that would have done it is
  // detached for the length of this pass so it can be READ (see the header).
  // Same comparison, same buffer, same result: a building in front of a pond
  // still hides it.
  //
  // Normalised by LOVE's own screen size, not by the `screen` uniform: `sc`
  // arrives in canvas PIXELS, and on a highdpi surface (Android's density
  // is routinely 2.625) a canvas holds that many pixels per canvas UNIT,
  // which is what `screen` counts. Divided by units, uv runs to 2.6 and
  // clamps, and the test reads edge texels for two thirds of the frame --
  // discarding water in blocks and letting the haze backdrop through, which
  // on a phone looked like lakes with pieces missing. love_ScreenSize.xy is
  // the bound canvas's own pixel size, the same units sc is measured in, on
  // every display. (`screen` stays in units: skyPos reads it against cell
  // and skyEdge, which are unit-measured with it.)
  //
  // The buffer now holds THIS SURFACE too (VoxelScene draws the water flat
  // before the pass that reflects it), which is what makes one lake able to
  // hide another -- and it means every fragment here is testing against its
  // own depth. That raises the bar on the fragment's own z: gl_FragCoord is
  // allowed to be MEDIUMP on GLES (and is, on Adreno), and fp16 near the far
  // end of the range steps by about half a thousandth -- which the old test
  // against the terrain far behind the surface never felt, and a comparison
  // of the surface against itself loses outright. Every fragment failed, the
  // pass discarded the whole lake, and Android showed the flat draw
  // underneath. So the depth is recomputed HERE, in highp, from the same
  // vBent and vp the vertex stage used -- full precision on every driver.
  //
  // The slack is sized to what remains after that, which is not rounding:
  // the buffer holds depth interpolated LINEARLY IN SCREEN SPACE, while the
  // recomputation projects the perspective-interpolated vBent -- the exact
  // answer. The two agree at the vertices and drift apart across a quad's
  // interior, by more the bigger the quad stands on screen; on a phone
  // (fit scale 6, water quads hundreds of pixels tall) the drift crosses
  // 1e-5 mid-quad, which discarded the middle of every tile row and looked
  // like flat water with reflective seams. Anything GENUINELY in front of a
  // water pixel is whole world units nearer -- upward of 1e-3 in depth --
  // so 2e-4 clears the drift with room while still catching every occluder.
  vec2 uv = sc / love_ScreenSize.xy;
  vec4 selfC = vp * vec4(vBent, 1.0);
  float selfZ = selfC.z / selfC.w * 0.5 + 0.5;
  if (selfZ > Texel(depthTex, uv).r + 2e-4) discard;

  // THE COLUMN THIS FRAGMENT IS LOOKING AT. Every water pixel is a bar of
  // its own standing a whole number of pixels tall, and the ray decides
  // which one it meets -- so what follows is answered per COLUMN and not per
  // screen pixel: one colour to a bar, at the resolution the water art is
  // drawn at, with no smooth shading anywhere across it. (The depth test
  // above is the one thing that stays per fragment: that is the hardware's
  // own question and it is asked in screen space.)
  //
  // Answered on the FLAT sheet, which is where the bars are a slab of even
  // thickness over a level plane -- the one thing relief() is built on. The
  // bend translates every bar straight down by its own column's drop, so the
  // field keeps its shape and only its height moves; undo that here and the
  // walk is the walk it was written for. Try it in the world as DRAWN
  // instead and the slab is a bowl: the backward step up the ray climbs the
  // bowl's near side as fast as it climbs out of the water, the walk starts
  // inside the sheet, and it hands back a column a pixel or three off -- per
  // fragment, differently, which is a patch of noise rather than parallax.
  vec3 sheet = vec3(vBent.x, vBent.y + bendDrop(vBent.xz), vBent.z);
  vec3 view = normalize(sheet - eye);
  vec3 hit;
  vec2 col;
  float face;
  float axis;
  relief(sheet, view, hit, col, face, axis);
  // and the bar's centre, so a column is sampled and reflected from one
  // place rather than from wherever inside it the fragment happened to land
  vec3 surf = vec3(col.x + 0.5, hit.y, col.y + 0.5);

  vec4 p = Texel(tex, waveUV(tc, col));
  if (p.a < 0.5) discard;
  // `face` is the column's own side shading, which is what makes a crest
  // read as a solid thing with a lit flank rather than as a bright patch
  vec3 base = p.rgb * vShade * face * sunlight(vSun) * dayTint;

  // the reflection follows the WAVES' own shape -- the tilt this column
  // takes from the neighbours it stands beside -- rather than an invented
  // wobble, so the sky and the sun break along the bars instead of across
  // them. Opened up by the lean, which is about to squash it (see below).
  vec3 n = waveNormal(col, waveSlope * (1.0 + lean * waveSlopeLean));
  vec3 r = reflect(view, n);
  // the same reflection off a LEVEL surface, which is what the lean below
  // moves: the difference between the two is this column's own contribution
  vec3 rFlat = reflect(view, vec3(0.0, 1.0, 0.0));
  // The horizon lean (see LEAN_FROM in Water.lua): zero at the rung whose
  // horizon is in frame, so the waterline join is untouched; taking the ray
  // down to the elevation THAT rung reflects at as the camera tips over,
  // where there is no join to break and a straight-up reflection has nothing
  // in it. Applied to the sky, the body AND the march, because the same
  // seventy-five-degree ray that misses the sun also leaves the frame.
  //
  // What is leaned is the LEVEL reflection, with this column's own deflection
  // put back on top afterwards. Leaning the perturbed ray instead sets its
  // elevation outright, which overwrites the very variation the waves are
  // there to provide: at full lean every column on the lake reflects the
  // same elevation, the sky comes out one flat band and the moon -- a disc
  // two degrees wide that the ray now never sweeps past -- vanishes
  // completely. Which is exactly what it did.
  //
  // The ray keeps its own BEARING and is only tipped in elevation, so a
  // reflection still points where the water is pointing it -- and a ray so
  // near vertical that it has no bearing left borrows the camera's.
  if (lean > 0.0) {
    float fl = length(rFlat.xz);
    vec3 bearing = (fl > 1e-3) ? vec3(rFlat.x / fl, 0.0, rFlat.z / fl)
                               : lookFlat;
    float e = mix(asin(clamp(rFlat.y, -1.0, 1.0)), leanElev, lean);
    r = normalize(bearing * cos(e) + vec3(0.0, sin(e), 0.0) + (r - rFlat));
  }

  // The checkerboard the sky's bands and the sunset's glow are dithered on,
  // cut from the WATER's own columns rather than from the screen. Same
  // reasoning the window glint follows in the scene shader: a pattern
  // anchored to the screen has the world sliding through it at zoom speed
  // whenever the camera pans, which strobes. Anchored to the surface,
  // panning moves nothing and only the waves do.
  float parity = mod(col.x + col.y, 2.0);
  vec3 refl = base;
  if (skyOn > 0.5) {
    refl = bodyAt(r, skyAt(r, parity), parity);
  }
  if (rays > 0.5) {
    vec4 hit = march(surf, r);
    refl = mix(refl, hit.rgb, hit.a);
  }

  // Schlick, floored and softened (see FRESNEL_* in Water.lua): the angle
  // still decides, a grazing camera still gets a mirror, and a steep one
  // still gets a pond rather than a flat sticker.
  float ct = clamp(dot(-view, n), 0.0, 1.0);
  float f = fresnelFloor
            + (fresnelCeil - fresnelFloor) * pow(1.0 - ct, fresnelPower);
  vec3 rgb = mix(base, refl, clamp(f, 0.0, 1.0));

#ifdef VOXEL_GRID
  rgb *= 1.0 - gridDark * columnSeam(hit, sheet, axis);
#endif
  // and the diorama's rim, over the finished surface. Per FRAGMENT here,
  // where the scene shader answers per vertex: this stage already carries
  // the world position it marched with, so the exact answer is free --
  // and measured on the FLAT world, which is what bendDrop puts back.
  float cull = dioramaCull(vec3(vBent.x, vBent.y + bendDrop(vBent.xz),
                                vBent.z));
  if (cull <= 0.0) discard;
  return vec4(rgb, cull) * color;
}
#endif
]]

-- The crater list is Sky's (Sky.MOON_CRATERS), pasted in as source rather
-- than sent as a uniform array: GLSL ES has no array constructors worth
-- relying on, and the driver bug that cost the sky its bands is exactly
-- what a uniform array of vectors buys. Built from the one list, so the
-- moon on the water can never grow craters the moon in the sky has not.
local function craterSource()
  local out = {}
  for _, c in ipairs(Sky.MOON_CRATERS) do
    out[#out + 1] = ("    k += crater(dc, vec2(%.4f, %.4f), %.4f);")
      :format(c[1], c[2], Sky.CRATER_FRAC)
  end
  return table.concat(out, "\n")
end

Water._craterSource = craterSource     -- named for the suite

-- and the wave trains, pasted in for the same reason: the rate is derived
-- from this table (Water.waveRate), so the field the shader sums has to be
-- the one that table describes rather than a copy of it kept in step by hand.
--
-- The dominant train carries the swell and the bend (see WAVE_SWELL): its
-- phase wobbles by the bend field and its amplitude breathes with the swell
-- envelope, both off the same tables the constants above document. One
-- statement per train either way, which is what the suite counts.
local function trainSource()
  local out = {}
  for i, t in ipairs(Water.WAVE_TRAINS) do
    if i == 1 then
      local s = Water.WAVE_SWELL
      local b = Water.WAVE_BEND
      out[#out + 1] = (
        "  h += sin(dot(q, vec2(%.4f, %.4f)) + waveT * %.4f\n"
        .. "           + %.4f * sin(dot(q, vec2(%.4f, %.4f)) + waveT * %.4f))\n"
        .. "       * %.4f * (1.0 - %.4f * (0.5 + 0.5 *\n"
        .. "           sin(dot(q, vec2(%.4f, %.4f)) + waveT * %.4f)));")
        :format(t[1], t[2], t[3],
                b[4], b[1], b[2], b[3],
                t[4], s[4], s[1], s[2], s[3])
    else
      out[#out + 1] = ("  h += sin(dot(q, vec2(%.4f, %.4f)) + waveT * %.4f)"
                       .. " * %.4f;"):format(t[1], t[2], t[3], t[4])
    end
  end
  return table.concat(out, "\n")
end

Water._trainSource = trainSource       -- named for the suite

local function source(grid, bare)
  local src = SHADER_SRC:gsub("//@CRATERS", (craterSource():gsub("%%", "%%%%")))
  src = src:gsub("//@TRAINS", (trainSource():gsub("%%", "%%%%")))
  local head = ("#define RAY_STEPS %d\n#define RAY_REFINE %d\n"
                .. "#define WAVE_STEPS %d\n#define WAVE_STRIDE %.1f\n")
    :format(Water.RAY_STEPS, Water.RAY_REFINE, Water.WAVE_STEPS,
            Water.WAVE_STRIDE)
  if grid then head = head .. "#define VOXEL_GRID 1\n" end
  -- effect()'s parameter precision -- see the signature for why it cannot
  -- simply be spelled there. Empty is a define all the same: the params
  -- then carry the stage default, which is what a prototype declared
  -- without qualifiers wants.
  head = head .. (bare and "#define EFFECT_PREC\n"
                        or "#define EFFECT_PREC mediump\n")
  return head .. src
end

Water._source = source                 -- named for the suite

-- Two compilations, exactly as Voxel3D keeps two of the scene shader: the
-- wireframe variant needs derivatives, the one thing a driver can refuse,
-- and a refusal must cost the seams on the water and nothing else.
-- nil = untried, false = unavailable.
local shaders = { [false] = nil, [true] = nil }

function Water.shader(grid)
  grid = grid and true or false
  if shaders[grid] == nil then
    if not (love.graphics and love.graphics.newShader) then
      shaders[grid] = false
    else
      local ok, sh = pcall(love.graphics.newShader, source(grid))
      if not ok then
        -- the pinned prototype was the wrong one for this runtime; the bare
        -- one is the only other shape there is, and a driver that refuses
        -- both was never going to draw this water anyway
        local bareOk, bareSh = pcall(love.graphics.newShader, source(grid, true))
        if bareOk then ok, sh = bareOk, bareSh end
      end
      if not ok and V and V.mod and V.mod.log then
        -- once, where it can be read: the fallback is flat water, which is
        -- easy to look at and impossible to diagnose without this line
        V.mod.log:warn("water shader did not compile: %s -- lakes draw flat",
                       tostring(sh))
      end
      shaders[grid] = (ok and sh) or false
    end
  end
  return shaders[grid] or nil
end

-- ------- the pass

local active = nil        -- the shader this pass bound, or nil

-- The ripple phase. Driven by the ENGINE's tile-animation clock, the same
-- 60Hz counter the water tiles rotate on, so the ripple and the art it
-- ripples move off one number rather than drifting against each other.
local function waveTime()
  -- lazily, and through the mod namespace: TerrainAtlas reaches into the
  -- engine's renderer at load time, and nothing about a settings row should
  -- depend on that having happened yet
  local ok, frame = pcall(function()
    return V.require("TerrainAtlas")._animFrame()
  end)
  if not (ok and type(frame) == "number") then return 0 end
  -- floored to the wave beat (see WAVE_FPS). The engine's counter runs at
  -- 60, so this is the frame that step began on.
  local period = 60 / math.max(1, Water.WAVE_FPS)
  return (math.floor(frame / period) * period / 60) * Water.waveRate()
end

Water._waveTime = waveTime

-- Begin the reflective pass.
--
-- `ctx` is everything the pass cannot work out for itself, all of it already
-- computed by whoever set the camera up this frame:
--
--   reflect   the frame so far, as a texture (Voxel3D.beginWater)
--   depth     its depth, likewise
--   vp, eye, curve, screen, cell   the camera, as beginScene sent it
--   skyEdge   where the sky's bottom is, or nil indoors / with no bands
--   grid      whether the voxel wireframe is compiled into this frame
--
-- Returns false when the pass cannot run, in which case the caller draws the
-- water mesh through the ordinary scene shader instead.
function Water.begin(ctx)
  if not (ctx and ctx.reflect and ctx.depth) then return false end
  local level = Water.level()
  if level <= 0 then return false end
  local sh = ctx.grid and Water.shader(true) or nil
  if not sh then sh = Water.shader(false) end
  if not sh then return false end

  love.graphics.setShader(sh)
  love.graphics.setColor(1, 1, 1, 1)
  local function send(name, ...)
    pcall(sh.send, sh, name, ...)
  end

  send("vp", "row", ctx.vp)
  send("eye", ctx.eye)
  send("curve", ctx.curve)
  -- the viewport, as beginScene sent it to the scene shader; kind 0 --
  -- every frame neither the diorama nor the orbit's box has cut -- is
  -- "no cut"
  local cull = V.require("Voxel3D").cull
  send("cullAt", cull and { cull.x, cull.y, cull.z } or { 0, 0, 0 })
  send("cullShape", cull and { cull.r, cull.invFade, cull.kind }
                    or { 0, 0, 0 })
  send("cullRect", cull and { cull.rx or cull.r, cull.rz or cull.r }
                   or { 0, 0 })
  send("screen", { ctx.screen[1], ctx.screen[2] })
  send("cell", math.max(1, ctx.cell or 1))
  -- how much of the view one screen pixel is worth: what sets the relief
  -- march's stride, so a sample is always about a pixel of surface
  send("pxAngle", (ctx.fov or 1) / math.max(1, ctx.screen[2]))
  send("reflectTex", ctx.reflect)
  send("depthTex", ctx.depth)

  -- the sun's pass, sent the same way and for the same reason the scene
  -- shader sends it: the sampler is declared either way, and leaving one
  -- unbound is a driver-dependent crash rather than a fallback
  local map = ShadowMap.active()
  send("sunVP", "row", map and ShadowMap.uvVP or Mat4.identity())
  local tex = ShadowMap.texture()
  if tex then send("sunMap", tex) end
  local Voxel3D = V.require("Voxel3D")
  send("sunDark", map and Voxel3D.SHADOW_ALPHA or 0)
  send("sunBias", ShadowMap.bias)
  local texel = 1 / ShadowMap.res
  send("sunTexel", { texel, texel })
  send("dayTint", Voxel3D.tint or { 1, 1, 1 })

  send("rays", level >= 2 and 1 or 0)
  -- the horizon lean, and the direction it leans toward (see Water.lean)
  send("lookFlat", ctx.lookFlat or { 0, 0, -1 })
  send("lean", Water.lean(ctx.descent))
  send("leanElev", Water.LEAN_ELEV)
  send("waveHeight", Water.WAVE_HEIGHT)
  send("waveSlope", Water.WAVE_SLOPE)
  send("waveSlopeLean", Water.WAVE_SLOPE_LEAN)
  send("waveT", waveTime())
  -- the columns' side faces wear the MESH's own direction shading, sent in
  -- rather than restated, so a wave crest is lit like every other voxel
  local fs = Voxel3D.FACE_SHADE
  send("faceShade", { fs[1], fs[2], fs[5], fs[6] })   -- east, west, south, north
  send("fresnelFloor", Water.FRESNEL_FLOOR)
  send("fresnelCeil", Water.FRESNEL_CEIL)
  send("fresnelPower", Water.FRESNEL_POWER)
  send("rayStep", Water.RAY_STEP)
  send("rayGrow", Water.RAY_GROW)
  send("rayThick", Water.RAY_THICK)
  send("edgeFade", Water.EDGE_FADE)
  if ctx.grid then
    local VoxelGrid = V.require("VoxelGrid")
    send("gridDark", VoxelGrid.DARK)
    send("gridWidth", VoxelGrid.width())
  end

  Water.sendSky(sh, ctx)
  active = sh
  return true
end

-- The sky half of the uniforms: the band ramp, and whatever hangs in it.
--
-- Split out because it is the part with a "there is none" answer -- indoors,
-- and on any frame whose ramp could not be built -- and that answer has to
-- leave every sampler bound anyway. skyOn 0 reflects the water's own colour
-- back at itself, which is what a pond in a cave does.
function Water.sendSky(sh, ctx)
  local function send(name, ...)
    pcall(sh.send, sh, name, ...)
  end
  local ramp, count = Sky.ramp()
  local edge = ctx.skyEdge
  if not (ramp and count and edge and edge > 0) then
    -- the sampler still has to hold something; the ramp is the only image
    -- this shader has for the job, so bind the frame copy and switch it off
    send("skyRamp", ctx.reflect)
    send("skyCount", 1)
    send("skyEdge", 1)
    send("skyStart", 2)
    send("skyOn", 0)
    send("bodyOn", 0)
    send("glowAmt", 0)
    return
  end
  send("skyRamp", ramp)
  send("skyCount", count)
  send("skyEdge", edge)
  send("skyStart", Sky.DITHER and Sky.DITHER_START or 2)
  send("skyOn", 1)

  local body = DayNight.body()
  if not body then
    send("bodyOn", 0)
    send("glowAmt", 0)
    return
  end
  local amt, glowColor = DayNight.glow()
  local h = ctx.screen[2]
  local cell = math.max(1, ctx.cell or 1)
  -- Sky sizes the disc in canvas pixels; out here the reflected body is
  -- usually off the top of the frame, where a pixel is not a unit any more
  -- -- so it is converted to the ANGLE it subtends through this camera's
  -- own field of view, which is the same number wherever it is looked at.
  local perRadian = h / math.max(1e-4, ctx.fov or 1)
  local rpx = Sky.discRadius(h, cell,
                             { moon = body.moon, glowAmt = amt })
  local shades = Sky.discShades(body.moon)
  local function shade(i)
    local c = shades[i] or shades[#shades] or { 255, 255, 255 }
    return { c[1] / 255, c[2] / 255, c[3] / 255 }
  end
  local twilight = (amt or 0) > 0.25 and not body.moon
  send("bodyOn", 1)
  send("bodyMoon", body.moon and 1 or 0)
  send("bodyDir", { body.dx, body.dy, body.dz })
  send("bodyAng", rpx / perRadian)
  send("bodyCore", shade(1))
  send("bodyMain", shade(twilight and 3 or 2))
  send("bodyDark", shade(3))
  send("glowAmt", (not body.moon) and (amt or 0) or 0)
  send("glowColor", glowColor
       and { glowColor[1] / 255, glowColor[2] / 255, glowColor[3] / 255 }
       or { 1, 0.88, 0.66 })
  send("glowReach", (ctx.screen[1] * Sky.GLOW_REACH) / perRadian)
end

-- Draw one water mesh with `model` applied. Mirrors Voxel3D.draw, minus the
-- camera-ward pull (a flat sheet has nothing to lean over) and the separate
-- sun transform (water is terrain: the sun saw the same matrix).
function Water.draw(mesh, texture, model)
  if not (active and mesh) then return end
  if texture then mesh:setTexture(texture) end
  pcall(active.send, active, "model", "row", model or Mat4.identity())
  -- Per draw rather than per pass, and read off the TEXTURE rather than
  -- assumed: it is what converts a world pixel of the columns' parallax into
  -- the texel of art standing on it, and two maps in one frame can be drawn
  -- from atlases of different sizes.
  if texture and texture.getDimensions then
    local ok, w, h = pcall(texture.getDimensions, texture)
    if ok and w and h then
      pcall(active.send, active, "atlasSize", { w, h })
    end
  end
  love.graphics.draw(mesh)
end

function Water.finish()
  active = nil
end

-- Drop the compiled shaders (window resize, hot reload): they are GPU
-- objects on a context that may not exist any more.
function Water.invalidate()
  for k, sh in pairs(shaders) do
    if sh and sh.release then pcall(sh.release, sh) end
    shaders[k] = nil
  end
  active = nil
end

return Water