// mesh.odin greedy-meshes one chunk into byte buffers laid out exactly as the
// pinned Godot 4.8-dev5 RenderingServer surface contract consumes them, so a
// chunk crosses the boundary as three whole-buffer byte arrays and one
// mesh_add_surface call with no repacking anywhere.
//
// The layout is earned from the pinned source, not assumed:
//
//   - `servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp`,
//     `MeshStorage::mesh_add_surface` DEBUG validation (line 263): with
//     format = ARRAY_FORMAT_VERTEX | ARRAY_FORMAT_NORMAL | ARRAY_FORMAT_COLOR
//     and no compression flags, vertex_data.size() must equal
//     (3*4 + 2*2) * vertex_count = 16 * vertex_count exactly, and the
//     attribute buffer is 4 bytes (color, one uint32) per vertex.
//   - THE VERTEX DATA IS TWO SEQUENTIAL SECTIONS, NOT INTERLEAVED. The GPU
//     wiring is `_mesh_surface_generate_vertex_format`
//     (mesh_storage.cpp:1296; position stride 12 for the uncompressed 3D
//     case at line 1352) and `_mesh_surface_generate_version_for_input_mask`
//     (mesh_storage.cpp:1489), which binds the normal attribute to the SAME
//     vertex buffer at `offset = position_stride * vertex_count` with stride
//     4 (line 1507). So vertex_data = [positions: 12*vertex_count
//     bytes][normals: 4*vertex_count bytes]. (The DEBUG validation above
//     only checks the 16*vertex_count TOTAL, which both layouts satisfy —
//     an interleaved buffer validates cleanly and renders as garbage. This
//     cost one rendered debugging cycle; it is written down so it costs no
//     more.)
//   - `servers/rendering/rendering_server.cpp`, `_surface_set_data`
//     (ARRAY_NORMAL case, line 611): the uncompressed normal is
//     `Vector3::octahedron_encode()` scaled by 65535 and clamped into two
//     little-endian uint16 (`core/math/vector3.cpp`, octahedron_encode,
//     line 90). Because the format carries normals without tangents, the
//     ENGINE appends the 4-byte tail pad itself (mesh_storage.cpp line 392);
//     the submitted buffer stays exactly 16 * vertex_count bytes.
//   - `rendering_server.cpp`, `_surface_set_data`, case ARRAY_COLOR (line
//     708): the attribute byte per channel is `CLAMP(c * 255, 0, 255)` in
//     r, g, b, a order — this mesher writes the palette color itself as the
//     albedo; every shading decision (sun, ambient, shadows, fog) belongs to
//     the engine's lit material on top of it.
//   - `mesh_storage.cpp` line 418: the index buffer is uint16 when
//     vertex_count <= 65536 and uint32 otherwise. Fragmented chunks can
//     exceed that bound: this example refuses them before writing beyond its
//     uint16 buffers, rather than switching layouts silently.
//
// Faces between opaque solids are hidden regardless of material. Each chunk
// emits only faces owned by its own solids; aprons supply culling information,
// never geometry belonging to a neighbor. The LOD interior is sampled at
// stride 1, 2 or 4, with an exact one-voxel shell on the four horizontal borders.
// Greedy meshing runs on this effective unit-cell field. Adjacent chunks thus
// agree on border occupancy even at different LODs; the exact shell also closes
// the transition to each chunk's sampled interior. Positions are world-space.
package voxel

import "core:mem"

// LOD_STEPS are the supported mesh strides. LOD 0 renders every voxel
// boundary; LOD 1 and 2 sample the interior every second and fourth cell.
// The horizontal border shell remains exact at every LOD.
LOD_STEPS :: [3]i32{1, 2, 4}

// lod_step maps a LOD index (0..2) onto its voxel stride. Anything else is a
// caller bug and returns 0, which mesh_chunk refuses.
lod_step :: proc(lod: i32) -> i32 {
	steps := LOD_STEPS
	if lod < 0 || lod >= i32(len(steps)) {
		return 0
	}
	return steps[lod]
}

// MESH_VERTEX_BYTES is the per-vertex size of the vertex buffer: three f32
// position components plus a 4-byte octahedral normal.
MESH_VERTEX_BYTES :: 16

// MESH_ATTRIBUTE_BYTES is the per-vertex size of the attribute buffer: one
// RGBA8 color.
MESH_ATTRIBUTE_BYTES :: 4

// MAX_MESH_VERTICES is a multiple of four below the uint16 index ceiling.
// Fragmented inputs can exceed it and are refused before any out-of-range write.
MAX_MESH_VERTICES :: 65532
MAX_MESH_QUADS :: MAX_MESH_VERTICES / 4

// Pinned ARRAY_FORMAT flag values (bindings/raw,
// raw_godot_class_renderingserver_enum_arrayformat_*): vertex bit 0, normal
// bit 1, color bit 3, index bit 12, and the current surface-format version tag.
SURFACE_FORMAT_VERTEX :: u64(1)
SURFACE_FORMAT_NORMAL :: u64(2)
SURFACE_FORMAT_COLOR :: u64(8)
SURFACE_FORMAT_INDEX :: u64(4096)
SURFACE_FORMAT_CURRENT_VERSION :: u64(34359738368)

// SURFACE_FORMAT is the exact format word every chunk mesh reports.
SURFACE_FORMAT :: u64(SURFACE_FORMAT_CURRENT_VERSION |
	SURFACE_FORMAT_VERTEX |
	SURFACE_FORMAT_NORMAL |
	SURFACE_FORMAT_COLOR |
	SURFACE_FORMAT_INDEX)

// Mesh_Data describes one mesh in the exact engine byte layout. mesh_chunk
// returns owned buffers; mesh_chunk_into returns a borrow of a Mesh_Workspace.
// A borrowed mesh expires when that workspace is reused or destroyed.
Mesh_Data :: struct {
	owned_buffers: bool,
	vertices:     []u8,
	attributes:   []u8,
	indices:      []u8,
	vertex_count: u32,
	index_count:  u32,
	// aabb is min.xyz then max.xyz in world coordinates, the exact six f32
	// values the surface Dictionary's "aabb" key needs.
	aabb:         [6]f32,
	quads:        i64,
}

// mesh_data_destroy frees the three owned buffers.
mesh_data_destroy :: proc(data: ^Mesh_Data, allocator: mem.Allocator) {
	if data == nil {
		return
	}
	if data.owned_buffers && len(data.vertices) > 0 {
		delete(data.vertices, allocator)
	}
	if data.owned_buffers && len(data.attributes) > 0 {
		delete(data.attributes, allocator)
	}
	if data.owned_buffers && len(data.indices) > 0 {
		delete(data.indices, allocator)
	}
	data^ = {}
}

// Mesh_Workspace owns fixed-capacity buffers and scratch storage. A worker
// retains one per result slot, so a published result remains stable until the
// consumer releases that slot. No mesh_chunk_into path allocates or resizes.
Mesh_Workspace :: struct {
	vertices, attributes, indices: []u8,
	mask: [CHUNK * CHUNK]u32,
	field: Chunk,
	padded: [PADDED_LEN]u8,
}

// mesh_workspace_create requires a zeroed owner. On failure it frees every
// partial allocation and leaves the owner zeroed.
mesh_workspace_create :: proc(allocator: mem.Allocator, workspace: ^Mesh_Workspace) -> bool {
	if workspace == nil || workspace.vertices != nil || workspace.attributes != nil || workspace.indices != nil {
		return false
	}
	workspace.vertices = make([]u8, MAX_MESH_VERTICES * MESH_VERTEX_BYTES, allocator)
	workspace.attributes = make([]u8, MAX_MESH_VERTICES * MESH_ATTRIBUTE_BYTES, allocator)
	workspace.indices = make([]u8, MAX_MESH_QUADS * 6 * 2, allocator)
	if workspace.vertices == nil || workspace.attributes == nil || workspace.indices == nil {
		mesh_workspace_destroy(workspace, allocator)
		return false
	}
	return true
}

// mesh_workspace_destroy invalidates every mesh borrowed from this owner.
mesh_workspace_destroy :: proc(workspace: ^Mesh_Workspace, allocator: mem.Allocator) {
	if workspace == nil do return
	if workspace.vertices != nil do delete(workspace.vertices, allocator)
	if workspace.attributes != nil do delete(workspace.attributes, allocator)
	if workspace.indices != nil do delete(workspace.indices, allocator)
	workspace^ = {}
}

// oct16_encode is the pinned engine's own normal encoder
// (`core/math/vector3.cpp` octahedron_encode plus the CLAMP(res * 65535)
// store): divide by the L1 norm, fold negative z by octahedral mirroring,
// remap into [0, 1], and store two little-endian uint16.
oct16_encode :: proc(nx, ny, nz: f32) -> (u16, u16) {
	l1 := abs_f32(nx) + abs_f32(ny) + abs_f32(nz)
	if l1 == 0.0 {
		l1 = 1.0
	}
	x := nx / l1
	y := ny / l1
	z := nz / l1
	ox: f32
	oy: f32
	if z >= 0.0 {
		ox = x
		oy = y
	} else {
		ox = (1.0 - abs_f32(y)) * (1.0 if x >= 0.0 else -1.0)
		oy = (1.0 - abs_f32(x)) * (1.0 if y >= 0.0 else -1.0)
	}
	ox = ox * 0.5 + 0.5
	oy = oy * 0.5 + 0.5
	return f32_to_u16_clamped(ox * 65535.0), f32_to_u16_clamped(oy * 65535.0)
}

abs_f32 :: proc(value: f32) -> f32 {
	return value if value >= 0.0 else -value
}

f32_to_u16_clamped :: proc(value: f32) -> u16 {
	if value <= 0.0 {
		return 0
	}
	if value >= 65535.0 {
		return 65535
	}
	return u16(value)
}

// FACE_NORMALS holds the six face directions in the face_id order:
// -x, +x, -y, +y, -z, +z.
FACE_NORMALS :: [6][3]f32{
	{-1, 0, 0}, {1, 0, 0},
	{0, -1, 0}, {0, 1, 0},
	{0, 0, -1}, {0, 0, 1},
}

// face_id maps an axis and a direction onto the six-face index above:
// axis 0 = x, 1 = y, 2 = z; a negative direction precedes its positive twin.
face_id :: proc(axis: int, positive: bool) -> int {
	return axis * 2 + (1 if positive else 0)
}

// albedo_color is the palette row for one voxel id, written to the RGBA8
// attribute bytes unchanged. The example used to bake a per-face shade into
// these bytes as a cheap depth cue for the old unshaded material; with the
// engine's lit material doing real sun/shadow work, baked shading double-
// darkened faces, so the mesher ships pure albedo.
albedo_color :: proc(id: u8) -> Palette_Entry {
	palette := PALETTE
	return palette[id]
}

// chunk_is_all_air reports whether the chunk owns any solid cells. Aprons and
// the below-layer half-space never contribute geometry owned by this chunk,
// so an all-air chunk has an empty mesh regardless of its neighbors.
chunk_is_all_air :: proc(chunk: ^Chunk) -> bool {
	if chunk == nil {
		return true
	}
	for &byte in chunk^ {
		if byte != VOXEL_AIR {
			return false
		}
	}
	return true
}

// PADDED is the chunk edge plus one border cell on each side, and PADDED_LEN
// the full padded field. fill_padded materializes it once per mesh job so the
// greedy scans read raw bytes with no bounds or apron branching: y below the
// layer is stone, y at or above CHUNK is air, and the four horizontal borders
// carry the neighbor aprons. Corner cells of the border ring are never read
// (a mask cell is only ever one axis-step outside the chunk) and stay zero.
PADDED :: CHUNK + 2
PADDED_LEN :: PADDED * PADDED * PADDED

padded_index :: proc(x, y, z: int) -> int {
	return ((y+1)*PADDED + (z+1))*PADDED + (x+1)
}

// fill_padded builds the padded field from a Mesher_View. Interior rows are
// memcpy'd whole (32 contiguous bytes each); only the border planes are
// gathered per cell.
fill_padded :: proc(field: []u8, view: Mesher_View) -> bool {
	if len(field) < PADDED_LEN || view.chunk == nil || view.aprons == nil {
		return false
	}
	stone_plane := PADDED * PADDED
	for i in 0..<stone_plane {
		field[i] = VOXEL_STONE
		field[stone_plane*(CHUNK+1) + i] = VOXEL_AIR
	}
	aprons := view.aprons
	for y in 0..<CHUNK {
		ybase := (y+1) * PADDED * PADDED
		for z in 0..<CHUNK {
			row := ybase + (z+1)*PADDED
			mem.copy(raw_data(field[row+1:]), raw_data(view.chunk[(y*CHUNK + z)*CHUNK:][:CHUNK]), CHUNK)
			field[row] = aprons[0][y*CHUNK + z]
			field[row + CHUNK + 1] = aprons[1][y*CHUNK + z]
		}
		low_row := ybase
		high_row := ybase + (CHUNK+1)*PADDED
		for x in 0..<CHUNK {
			field[low_row + 1 + x] = aprons[2][y*CHUNK + x]
			field[high_row + 1 + x] = aprons[3][y*CHUNK + x]
		}
	}
	return true
}

// Mesher_View bounds one mesher's world lookups: the center chunk plus the
// four horizontal neighbor aprons. Positions outside the chunk route through
// the aprons on x/z; y below the layer is solid stone and y above it is air.
// Bedrock occludes bottom faces but contributes no geometry of its own.
Mesher_View :: struct {
	chunk:  ^Chunk,
	aprons: ^Aprons,
}

// MASK_POSITIVE flags the face whose normal points along the positive axis.
// A boundary between a solid cell and an air cell always faces AWAY from the
// solid cell: solid-below-air reads positive, air-below-solid negative.
MASK_POSITIVE :: u32(0x8000_0000)

// mask_value returns a face only when exactly one side is opaque and that
// solid belongs to the center chunk. Material boundaries inside solids are
// hidden. Outside solids merely occlude the center's faces. `field` is the
// padded field fill_padded produced: every lookup here is in range by
// construction and costs one indexed byte read.
mask_value :: proc(field: []u8, axis, slice, b, c, step: int) -> u32 {
	u := (axis + 1) % 3
	v := (axis + 2) % 3
	pa, pb: [3]int
	pa[axis] = slice - step
	pa[u], pa[v] = b, c
	pb[axis] = slice
	pb[u], pb[v] = b, c
	va := field[padded_index(pa[0], pa[1], pa[2])]
	vb := field[padded_index(pb[0], pb[1], pb[2])]
	if (va == VOXEL_AIR) == (vb == VOXEL_AIR) do return 0
	if va != VOXEL_AIR {
		if slice == 0 do return 0
		return u32(va) | MASK_POSITIVE
	}
	if slice == CHUNK do return 0
	return u32(vb)
}

// mesh_chunk is the convenience allocating API for isolated callers. Streaming
// uses mesh_chunk_into with retained workspaces. The returned buffers are owned
// by data and released with mesh_data_destroy using this same allocator.
mesh_chunk :: proc(
	view: Mesher_View,
	coords: [3]i32,
	params: Params,
	step: i32,
	allocator: mem.Allocator,
	data: ^Mesh_Data,
) -> bool {
	if data == nil do return false
	data^ = {}
	workspace: Mesh_Workspace
	if !mesh_workspace_create(allocator, &workspace) do return false
	defer mesh_workspace_destroy(&workspace, allocator)
	borrow: Mesh_Data
	if !mesh_chunk_into(view, coords, params, step, &workspace, &borrow) do return false
	data^ = borrow
	data.vertices = nil
	data.attributes = nil
	data.indices = nil
	data.owned_buffers = true
	if len(borrow.vertices) > 0 {
		data.vertices = make([]u8, len(borrow.vertices), allocator)
		data.attributes = make([]u8, len(borrow.attributes), allocator)
		data.indices = make([]u8, len(borrow.indices), allocator)
		if data.vertices == nil || data.attributes == nil || data.indices == nil {
			mesh_data_destroy(data, allocator)
			return false
		}
		copy(data.vertices, borrow.vertices)
		copy(data.attributes, borrow.attributes)
		copy(data.indices, borrow.indices)
	}
	return true
}

// mesh_chunk_into borrows fixed buffers from workspace, with zero allocation.
// Output is cleared on every refusal, including fragmented meshes exceeding
// MAX_MESH_VERTICES. Reusing workspace invalidates the prior output. Each
// border cell remains exact; only cells with x/z in 1..CHUNK-2 use the sampled
// interior. Greedy merging coalesces repeated samples without losing the exact
// shell, at the cost of scanning a unit-cell mask for every LOD.
mesh_chunk_into :: proc(
	view: Mesher_View,
	coords: [3]i32,
	params: Params,
	step: i32,
	workspace: ^Mesh_Workspace,
	data: ^Mesh_Data,
) -> bool {
	if data == nil do return false
	data^ = {}
	if view.chunk == nil || view.aprons == nil || workspace == nil {
		return false
	}
	if params_valid(params) != .None {
		return false
	}
	if coords[1] != 0 {
		return false
	}
	if step != 1 && step != 2 && step != 4 {
		return false
	}
	data^ = {}

	origin_x := f32(coords[0] * CHUNK)
	origin_z := f32(coords[2] * CHUNK)

	if len(workspace.vertices) != MAX_MESH_VERTICES * MESH_VERTEX_BYTES ||
		len(workspace.attributes) != MAX_MESH_VERTICES * MESH_ATTRIBUTE_BYTES ||
		len(workspace.indices) != MAX_MESH_QUADS * 6 * 2 {
		return false
	}
	// An empty center owns no geometry, even beside solid apron cells.
	// Keep this fast path in the canonical mesher so every caller agrees.
	if chunk_is_all_air(view.chunk) {
		data.aabb = {origin_x, 0, origin_z, origin_x + CHUNK, 0, origin_z + CHUNK}
		return true
	}
	// Sampling is confined to the interior. The border shell is authoritative
	// at unit resolution and closes the interior-to-border transitions.
	field_view := view
	if step > 1 {
		stride := int(step)
		for y in 0..<CHUNK {
			for z in 0..<CHUNK {
				for x in 0..<CHUNK {
					sx, sy, sz := x, y, z
					if x > 0 && x < CHUNK-1 && z > 0 && z < CHUNK-1 {
						sx, sy, sz = x / stride * stride, y / stride * stride, z / stride * stride
					}
					workspace.field[chunk_index(x, y, z)] = view.chunk[chunk_index(sx, sy, sz)]
				}
			}
		}
		field_view.chunk = &workspace.field
	}
	field := workspace.padded[:]
	if !fill_padded(field, field_view) {
		return false
	}
	data.vertices = workspace.vertices
	data.attributes = workspace.attributes
	data.indices = workspace.indices
	position_bytes := MESH_VERTEX_BYTES - 4
	normal_section := MAX_MESH_VERTICES * position_bytes
	mask := workspace.mask[:]

	vertex_count: i32 = 0
	index_count: i32 = 0
	quad_count: i64 = 0

	aabb_min := [3]f32{max(f32), max(f32), max(f32)}
	aabb_max := [3]f32{min(f32), min(f32), min(f32)}

	for axis in 0..<3 {
		u := (axis + 1) % 3
		v := (axis + 2) % 3
		face_normals := FACE_NORMALS
		slice := 0
		for slice <= CHUNK {
			// Build the unit-cell mask from the sampled interior plus exact
			// shell. All LODs use the same topology and ownership rules.
			for c in 0..<CHUNK {
				for b in 0..<CHUNK {
					mask[c*CHUNK + b] = mask_value(field, axis, slice, b, c, 1)
				}
			}

			// Greedy-merge the mask into maximal rectangles.
			for c in 0..<CHUNK {
				for b in 0..<CHUNK {
					value := mask[c*CHUNK + b]
					if value == 0 {
						continue
					}
					// Width along u, in unit cells.
					width := 1
					for b + width < CHUNK && mask[c*CHUNK + (b + width)] == value {
						width += 1
					}
					// Height along v, in unit cells.
					height := 1
					height_done := false
					for !height_done && (c + height) < CHUNK {
						row_ok := true
						for w in 0..<width {
							if mask[(c + height)*CHUNK + b + w] != value {
								row_ok = false
								break
							}
						}
						if row_ok {
							height += 1
						} else {
							height_done = true
						}
					}

					positive := (value & MASK_POSITIVE) != 0
					voxel_id := u8(value & 0xFF)
					face := face_id(axis, positive)
					normal := face_normals[face]
					color := albedo_color(voxel_id)

					// Emit one quad on the slice plane: the cell offset (b
					// along u, c along v) positions the rectangle inside the
					// plane, du spans its merged width and dv its height.
					base: [3]f32
					base[axis] = f32(slice)
					base[u] = f32(b)
					base[v] = f32(c)
					du: [3]f32
					du[u] = f32(width)
					dv: [3]f32
					dv[v] = f32(height)

					corners := [4][3]f32{
						base,
						{base[0] + du[0], base[1] + du[1], base[2] + du[2]},
						{base[0] + du[0] + dv[0], base[1] + du[1] + dv[1], base[2] + du[2] + dv[2]},
						{base[0] + dv[0], base[1] + dv[1], base[2] + dv[2]},
					}

					nu, nv := oct16_encode(normal[0], normal[1], normal[2])

					if vertex_count + 4 > MAX_MESH_VERTICES {
						data^ = {}
						return false
					}
					first := vertex_count
					for corner in corners {
						world := [3]f32{
							corner[0] + origin_x,
							corner[1],
							corner[2] + origin_z,
						}
						write_vertex(
							data.vertices[int(vertex_count)*position_bytes:][:position_bytes],
							data.vertices[normal_section + int(vertex_count)*4:][:4],
							world, nu, nv,
						)
						write_color(
							data.attributes[int(vertex_count)*MESH_ATTRIBUTE_BYTES:][:MESH_ATTRIBUTE_BYTES],
							color,
						)
						vertex_count += 1
						for k in 0..<3 {
							aabb_min[k] = min(aabb_min[k], world[k])
							aabb_max[k] = max(aabb_max[k], world[k])
						}
					}

					// Godot front faces wind clockwise viewed from outside.
					// du cross dv is always +axis, so reverse the triangles
					// only for the positive face; negative faces use 0,1,2.
					triangles := [6]i32{0, 1, 2, 0, 2, 3}
					if positive do triangles = {0, 2, 1, 0, 3, 2}
					for offset, i in triangles {
						write_index(data.indices, index_count + i32(i), u16(first + offset))
					}
					index_count += 6
					quad_count += 1

					// Clear the merged rectangle.
					for h in 0..<height {
						for w in 0..<width {
							mask[(c + h)*CHUNK + b + w] = 0
						}
					}
				}
			}
			slice += 1
		}
	}

	// Compact normals in place after the positions section. mem.copy permits
	// overlap; the workspace retains the original full allocations for reuse.
	position_section := int(vertex_count) * position_bytes
	normal_bytes := int(vertex_count) * 4
	if normal_bytes > 0 {
		mem.copy(raw_data(data.vertices[position_section:]), raw_data(data.vertices[normal_section:]), normal_bytes)
	}
	data.vertices = data.vertices[:position_section + normal_bytes]
	data.attributes = data.attributes[:int(vertex_count)*MESH_ATTRIBUTE_BYTES]
	data.indices = data.indices[:int(index_count)*2]

	data.vertex_count = u32(vertex_count)
	data.index_count = u32(index_count)
	data.quads = quad_count
	if vertex_count > 0 {
		data.aabb = [6]f32{
			aabb_min[0], aabb_min[1], aabb_min[2],
			aabb_max[0], aabb_max[1], aabb_max[2],
		}
	} else {
		// An empty mesh reports the chunk's own footprint so the surface
		// Dictionary's required "aabb" key never carries a degenerate box.
		data.aabb = [6]f32{
			origin_x, 0.0, origin_z,
			origin_x + CHUNK, 0.0, origin_z + CHUNK,
		}
	}
	return true
}


// write_vertex stores one vertex across the engine's two sequential vertex
// sections: the f32 x3 position into the position section (stride 12) and the
// octahedral normal as two little-endian uint16 into the normal section
// (stride 4, at offset position_stride * vertex_count).
write_vertex :: proc(position_sink, normal_sink: []u8, position: [3]f32, nu, nv: u16) {
	put_f32(position_sink[0:], position[0])
	put_f32(position_sink[4:], position[1])
	put_f32(position_sink[8:], position[2])
	put_u16(normal_sink[0:], nu)
	put_u16(normal_sink[2:], nv)
}

// write_color stores one RGBA8 attribute record.
write_color :: proc(sink: []u8, color: Palette_Entry) {
	sink[0] = color.r
	sink[1] = color.g
	sink[2] = color.b
	sink[3] = color.a
}

// write_index stores one little-endian uint16 index at element `at`.
write_index :: proc(sink: []u8, at: i32, value: u16) {
	put_u16(sink[int(at)*2:], value)
}

put_f32 :: proc(sink: []u8, value: f32) {
	// Chunks are arm64/x86-64 little-endian, and the pinned engine's memcpy
	// stores make the byte order explicit.
	bits := transmute([4]u8)value
	sink[0] = bits[0]
	sink[1] = bits[1]
	sink[2] = bits[2]
	sink[3] = bits[3]
}

put_u16 :: proc(sink: []u8, value: u16) {
	bits := transmute([2]u8)value
	sink[0] = bits[0]
	sink[1] = bits[1]
}

// mesh_digest folds the engine-layout buffers, counts, and AABB. This is a
// deterministic comparison oracle, not a collision-free proof of equality.
mesh_digest :: proc(data: ^Mesh_Data) -> u64 {
	if data == nil {
		return 0
	}
	hash := bytes_digest(data.vertices)
	hash = (hash ~ bytes_digest(data.attributes)) * 0x100000001b3
	hash = (hash ~ bytes_digest(data.indices)) * 0x100000001b3
	hash = (hash ~ u64(data.vertex_count)) * 0x100000001b3
	hash = (hash ~ u64(data.index_count)) * 0x100000001b3
	for value in data.aabb {
		hash = (hash ~ u64(transmute(u32)value)) * 0x100000001b3
	}
	return hash
}

// count_faces counts visible faces without merging — the naive per-face
// baseline the greedy ratio test compares against. `field` is caller-provided
// padded storage; it is rebuilt from the raw view on every call.
count_faces :: proc(view: Mesher_View, field: []u8) -> i64 {
	if !fill_padded(field, view) {
		return -1
	}
	count: i64 = 0
	for axis in 0..<3 {
		for slice in 0..=CHUNK {
			for c in 0..<CHUNK {
				for b in 0..<CHUNK {
					if mask_value(field, axis, slice, b, c, 1) != 0 {
						count += 1
					}
				}
			}
		}
	}
	return count
}
