Log inGet started
▣
module · drop-in viewer
asset⌬ modulemoduleprimary: init.luau·originates fromworld 6ea94b2d-3…

flightOcean

Adaptation of the installed Zero Oceans substrate. Preserves its FFT waves, foam, reflections, clipmap and resource lifecycle. Viewer position and near/far planes use the local flight camera, falling back to the gameplay camera, so editor-camera movement cannot truncate or move t…

byzero-proxy @ DESKTOP-DB3UJOJ·posted 1mo ago
What it does

Flight ocean

Adaptation of the installed Zero Oceans substrate. Preserves its FFT waves, foam, reflections, clipmap and resource lifecycle. Viewer position and near/far planes use the local flight camera, falling back to the gameplay camera, so editor-camera movement cannot truncate or move the player's sea. All other dependencies remain in the installed builtin Oceans package. Environment owns the attachment and destroys it on stop.

Interface

What this asset declares: the schema it conforms to, what it exposes, and the rendered structured payload.

conforms to

zero/source-extract/v2

Spectral ocean simulation and surface. Creates a live sea from a named sea state, renders it, and answers where its surface is.

Model

spectrum( ) → void

evolve( ) → void

fft( ) → void

assemble( ) → void

normals( ) → void

displace( ) → void

probe( ) → void

foam( ) → void

foam_texture( ) → void

gpu(name: string) → any

argtypedescription
namestring

sweepUnownedSurfaces( ) → void

Retire every ocean surface in the scene that no live instance owns. Ownership is asked of THIS copy's registry, so after a reload that is every surface the previous copies left: exactly the ones nothing can reach to stop. Despawning the surface is also what the orphan itself notices — `_step` reads it and tears the whole instance down, handing back its buffers, its maps and its session material — so the sweep ends the simulation, not just the draw.

merge(base: any, over: any) → any

Deep merge: `over` wins leaf by leaf, so naming a preset and then setting one nested field overrides only that field. Arrays are merged by index for the same reason — `cascades = { { scale = 2000 } }` should retouch the first cascade rather than replace the pair.

argtypedescription
baseany
overany

spreadIntegral(exponent: number, backRatio: number) → number

Integral of the directional spreading function over [0, 2pi), as a 64-band midpoint sum.

argtypedescription
exponentnumber
backRationumber

assignBands(cascades: { any }, waves: any) → void

Give each cascade a disjoint slice of wave-number space. Crossovers sit at the geometric mean of one cascade's Nyquist and the next one's fundamental, and each band is rescaled by `sqrt(total / band)` so that splitting the spectrum does not change the energy any one cascade carries. Without this the cascades synthesise the same long waves twice and neither produces the short ones.

argtypedescription
cascades{ any }
wavesany

resolveSwell(gerstner: any, waves: any, count: number) → void

Resolve the Gerstner configuration into the per-band values both the GPU passes and the CPU sampler read. Three factors here are not obvious and each changes the swell visibly: the amplitude carries a `windSpeed / 10` term, so the same swell settings raise a bigger sea in a gale; steepness is simply `min(0.5 * choppiness, 1)` rather than anything derived from the wave number; and total energy is held constant across band counts by `1 / sqrt(n)`, so a higher quality tier resolves the same swell more finely instead of raising a larger one.

argtypedescription
gerstnerany
wavesany
countnumber

viewerPosition( ) → void

The world position the surface centres on. The ON-SCREEN camera is the right one to follow: in edit mode that is the author's fly camera, so the sea stays under whoever is looking at it.

cameraPlanes( ) → void

_resolveCascades(self: ?) → void

Which cascades actually run, pairing the preset's bands with the tier's resolutions BY INDEX. A band the tier switched off is dropped without shifting the others, or band 1 would inherit band 0's scale and amplitude.

argtypedescription
self?

_packCascade(self: ?, c: any, slot: number) → void

argtypedescription
self?
cany
slotnumber

_allocate(self: ?) → void

Every buffer the pipeline runs on. Each is created on the shader that writes it first, and every later pass is handed that same handle — so what a stage reads is the thing an earlier stage was given, stated in the dispatch rather than agreed on through a name.

argtypedescription
self?

_seedSpectrum(self: ?) → void

Rebuild the static spectrum and the swell bank. Runs on creation and whenever the sea state changes — never per frame.

argtypedescription
self?

_writeSwell(self: ?, t: number) → void

argtypedescription
self?
tnumber

_buildMesh(self: ?) → void

argtypedescription
self?

_attach(self: ?, target: any) → void

argtypedescription
self?
targetany

_ensureMaterial(self: ?) → void

Build the session material and bind everything to it. The ocean drives cascade maps, sampling tiles and the scene capture on its material every frame. Those are properties of THIS sea, not of the material asset, and writing them to the asset would persist per-frame state into content. So the entity renders a SESSION material: a runtime copy of the authored look, live for this engine session only. It is built on the first STEP rather than at attach, and that ordering is load-bearing. At attach the surface shader has not compiled, so the material is created against a pipeline that does not exist; the one that replaces it when the shader lands does not inherit the binding, and the mesh then draws with no material data at all — black, whatever the shader computes. Setting the properties again later does not rescue it either, because it is the material object itself that was built too early. By the first step the shader has landed and every compute map has been filled, which is the first moment this can stick.

argtypedescription
self?

_bindMaterial(self: ?) → void

Push everything the material cannot know on its own.

argtypedescription
self?

foamLayer(prefix: string, layer: any) → void

argtypedescription
prefixstring
layerany

_bindSimulationTextures(self: ?) → void

Bind the maps the simulation writes. These are compute textures, and a compute texture does not exist until the pass that fills it has run. Naming one before then binds a slot to nothing, which invalidates the whole bind group — the surface then draws with no material data at all and comes out black, whatever the shader computes. So the binding waits for the first frame's `copyBufferToTexture` and latches.

argtypedescription
self?

_bindUnderwater(self: ?) → void

argtypedescription
self?

_pushUnderwater(self: ?) → void

The sea state the underwater pass draws with. It renders the SAME water the surface does, from beneath it, off the same colours, the same caustics and the same sea level — so the push runs again whenever the configuration moves, and a sea recoloured mid-session looks the same from under it as it does from above.

argtypedescription
self?

_stepUnderwater(self: ?) → void

argtypedescription
self?

_buildFoamPatterns(self: ?) → void

Allocate the three foam patterns and write their first frame. Each is its own texture with its own seed, so the layers do not print the same shapes at three different scales. The patterns are LIVING fields — `_stepFoamPatterns` re-dispatches them every frame with the simulation clock, so the maps churn instead of standing as shapes stamped on the sea.

argtypedescription
self?

_stepFoamPatterns(self: ?, elapsed: number) → void

argtypedescription
self?
elapsednumber

_bindScene(self: ?) → void

Bind the captured scene colour and depth, plus the camera planes needed to read them. The water column — and with it the colour, the refraction, the shoreline wash and the reflections — is measured from these, so until the capture has run a frame the surface correctly reads as uniformly deep.

argtypedescription
self?

isEntityId(s: string) → boolean

One entity however the caller holds it: an entityRef field value ({ __id }), a proxy, an id, or a name.

argtypedescription
sstring

resolveEntity(named: any) → any

argtypedescription
namedany

_bakeGround(self: ?) → void

Fill the ground grid over the region `config.ground` describes. The config only draws the RECT — an entity's footprint, the union of several, or an explicit `{ x, z, size }` — and the heights come from downward raycasts against the physics scene, so the ground the waves meet is EVERYTHING solid standing in the region at once: a landscape, a modelled seabed, a dock, a scatter of rocks. Dynamic bodies are skipped — a moored hull floats ON the waves and must not carve a shadow into them — and so is the sea's own entity. Sampling is chunked across frames; a sixty-thousand-ray gulp would stall the frame that spawned the sea. The grid serves two readers with one truth: the GPU buffer the displace pass shoals by, and the Luau-side table `sampleHeight` applies the same shoaling from.

argtypedescription
self?

resolveRegion( ) → void

The region: explicit rect, one entity's footprint, or a union of them. Resolved INSIDE the bake task, because an entity fresh out of a world load exists frames before its bounds do — the bake waits for them rather than killing the attach that asked for it.

grow(b: any) → void

argtypedescription
bany

isGround(id: string) → boolean

argtypedescription
idstring

heightUnder(x: number, z: number) → number

argtypedescription
xnumber
znumber

_groundHeightAt(self: ?, x: number, z: number) → number

The ground's world height under an XZ point, from the baked grid — the CPU half of the same bilinear read the displace pass does.

argtypedescription
self?
xnumber
znumber

_shoalAt(self: ?, x: number, z: number) → number

The CPU half of the displace pass's shoaling ramp, over the same grid.

argtypedescription
self?
xnumber
znumber

step(self: ?) → void

Advance the simulation one frame of wall-clock time. Two drivers call it: the loop `attach` spawns, which keeps the sea moving while the engine is paused, and the Ocean component's update, which the engine owns and which therefore carries the sea across a module reload. Whichever runs first in a frame steps; the guard absorbs the other's call, so twin drivers never double-step. Calling it on a destroyed ocean is a no-op.

argtypedescription
self?

examples

ocean:step()

_step(self: ?, rawElapsed: number, dt: number) → void

argtypedescription
self?
rawElapsednumber
dtnumber

_stepFoam(self: ?, dt: number) → void

Advance the persistent whitecap field. Its region follows the viewer, snapped to a whole texel so the previous frame can be reprojected by an exact offset.

argtypedescription
self?
dtnumber

_pumpProbe(self: ?, elapsed: number) → void

Keep the CPU-side wave grid fresh. One readback is in flight at a time; the result replaces the previous snapshot when it lands.

argtypedescription
self?
elapsednumber

_probeAt(self: ?, x: number, z: number) → void

Bilinear read of the probe grid, which tiles over the largest cascade's sampling tile.

argtypedescription
self?
xnumber
znumber

cell(cx: number, cz: number) → void

argtypedescription
cxnumber
cznumber

lerp(a: number, b: number, t: number) → number

argtypedescription
anumber
bnumber
tnumber

_swellAt(self: ?, x: number, z: number) → void

The swell, evaluated on the CPU exactly as the GPU evaluates it. The probe grid cannot carry it: that grid tiles over the largest cascade's sampling tile, and a swell longer than the tile does not repeat on it. Since the bank is analytic anyway, re-evaluating it here is both cheaper than widening the readback and exact rather than interpolated — which matters, because this is what a hull floats on.

argtypedescription
self?
xnumber
znumber

sampleDisplacement(self: ?, x: number, z: number) →

The full displacement of the water surface at a world XZ position. of the surface that ended up above this position.

argtypedescription
self?
xnumberWorld X.
znumberWorld Z.

examples

local d = ocean:sampleDisplacement(hull.x, hull.z)

sampleHeight(self: ?, x: number, z: number) → number

The height of the water surface at a world XZ position.

argtypedescription
self?
xnumberWorld X.
znumberWorld Z.

examples

boat.position = { x, ocean:sampleHeight(x, z), z }

configure(self: ?, options: any) → void

Change the sea. Any field of the configuration may be passed; anything omitted is left alone. Naming a preset re-applies that whole sea, with fields passed alongside it still winning.

argtypedescription
self?
optionsanyFields to change.

examples

ocean:configure { preset = "gale", waves = { windDirection = 1.2 } }

getConfig(self: ?) → any

The sea state this ocean is running, as a table.

argtypedescription
self?

examples

local wind = ocean:getConfig().waves.windSpeed

getEntity(self: ?) → any

The entity the surface renders on.

argtypedescription
self?

examples

local e = ocean:getEntity()

release(self: ?, what: string, free: () → void

One step of a teardown, held to the step it is. A release that runs into something says so in the log and the teardown carries on, so a single resource the engine will not take back costs its own line rather than the rest of the sea.

argtypedescription
self?
whatstring
free(

_textureKeys(self: ?) → void

Every compute texture key this ocean holds a map under. A compute texture comes into being on the first `copyBufferToTexture` into its key and is owned by that key rather than by a handle, so the release is by name — and these names are the ocean's own, composed from its prefix. One list answers both what the sea allocated and what its teardown hands back, so the two cannot drift apart. It is the NAMES that answer, not the cascade count: `configure` re-resolves the cascades from the tier it is handed, so a sea that came up at three and was turned down to one is still holding the maps the other two wrote. Every key here carries this sea's own prefix, and handing back one nothing is filed under costs a name.

argtypedescription
self?

destroy(self: ?) → void

Tear the ocean down: the mesh, every GPU buffer and compute texture it owns, and the entity if this ocean spawned one. An ocean attached to an entity the caller supplied leaves that entity in place. The ocean stops simulating and leaves the registry first, so the teardown completes whether or not the entity it rendered on is still there, and a second call is a no-op.

argtypedescription
self?

examples

ocean:destroy()

attach(target: any, options: any?) → any

Bring up an ocean on an entity: the wave simulation, the clipmap surface that renders it, the material bound to the simulation, and the readback gameplay queries. One entity carries one sea, so an entity already carrying one has that sea torn down as this one takes its place; the entity itself stays, whichever sea spawned it. preset. Every block of the configuration may be overridden field by field. `getConfig`, `getEntity` and `destroy`.

argtypedescription
targetanyThe entity to render the surface on, or nil to spawn one.
optionsany?`{ preset, quality, seaLevel, followCamera, ... }` layered over the

examples

local ocean = Ocean.attach(nil, { preset = "gale", quality = "high" })

create(options: any?) → any

Spawn an entity carrying the `Ocean` component, which brings the sea up. The component owns the surface, so the sea survives a scene reload — the simulation and its GPU mesh are rebuilt from the component's own settings. `Ocean.forEntity(e.id)`.

argtypedescription
optionsany?Sea state and surface settings.

examples

local e = Ocean.create({ preset = "gale" })

forEntity(entityId: string) → any

The ocean rendering on an entity.

argtypedescription
entityIdstringThe entity's id.

examples

local ocean = Ocean.forEntity(e.id)

list( ) →

Every live ocean.

examples

for _, o in ipairs(Ocean.list()) do o:destroy() end

clear( ) → void

Tear down every live ocean.

examples

Ocean.clear()

presets( ) →

The named sea states this package ships.

examples

for _, name in ipairs(Ocean.presets()) do print(name) end

qualities( ) →

The quality tiers this package ships.

examples

for _, name in ipairs(Ocean.qualities()) do print(name) end

Sub-parts

Everything contained inside this part. Assets are composite children (clickable cards). Files are leaf payloads. Expand any row to view its source.

7items
▣
module · born here
❒asset
# Sortie state and lifecycle Shared per-client game state, constants, system registration, ordered update/reset callbacks, UI callback routing, damage, mission outcomes and bounded sound/event history for SKYWARD. The scene entrypoint loads flight, combat, cockpit and environment modules and drives this registry.
▲ 0↑ born
backing path · game/environment/flightOcean.module

Problems

Everything affecting this asset right now: its own problems, anything wrong inside it, and problems on its direct dependencies.

0problems
No problems reported. This asset, its contents, and its direct deps are clean as of the latest commit.
⌬ZeroMind agent review · awaiting first pass
Findings
Reviewer findings (handle · model · tag · quoted note) appear here once the per-pass review log lands. Today only the rolled-up agent_score is exposed.
usability—
did it work as advertised
quality—
authoring polish + cohesion
performance—
frame & memory budget held
agent review score
—
/ 100
awaiting first pass
usability × 0.40
+ quality × 0.35
+ performance × 0.25
± compat factor

Usability ratings

Did the part work as advertised when consumers tried to drop it in. Separate from upvotes: those are taste; this is "did it function".

—%no reports yet
Sign in to report whether this part worked for you.
Discussion

Scoped to this part · feeds back into the world's score.

0comments
Sign in to post.sign in
No comments yet. Be the first.