---
name: roblox-infinite-ocean
description: Use when a Roblox game needs an ocean or sea - an endless animated water surface, boats or objects that float, swimming, storms and calm zones, or code that asks where the water surface is. Covers KashTheKing's Infinite Ocean (Studio plugin plus the open-source kashtheking/ocean Wally module), its setup, weather, zones, tags and water queries.
---

# Infinite Ocean

**Infinite Ocean** by KashTheKing is an endless animated sea for Roblox. The surface is an `EditableMesh` that
follows the camera, with Gerstner or FFT waves. The wave function depends only on the settings, the weather and
server time, so the server and every client agree on the surface height with no replication lag.

It comes in two parts:

- **The Infinite Ocean Studio plugin** (Creator Store asset 76752250508724, product page
  https://kashtheking.com/infinite-ocean/): one-click install, Quick Start, live Edit-mode preview, a panel with a
  slider and hint for every setting, presets, one-click lighting looks, and the addons. Proprietary.
- **The Ocean module** (`kashtheking/ocean` on Wally, MIT, source in the KashTheKing/library GitHub repo under
  `packages/src/Ocean`): the sea itself, settings, weather, zones, obstacles, dry regions, water queries and events.

## Use the plugin to build the sea, the module at runtime

Set up and tune the ocean with the plugin. Use the module's API from your game scripts.

- The plugin is the **only** way to get the addons: Buoyancy, Swimming, Drowning, WaterSplash, Weather effects,
  Zones, DevTools and AgentSupport. Without the Buoyancy addon the sea is visuals, queries and events only: nothing
  floats and nobody swims until you write that yourself.
- The live preview shows waves in Edit mode, so you can tune looks without playtesting. It saves nothing to the place.
- Presets set waves, look, Lighting and Atmosphere together: **Island**, **Pirate Seas**, **Oil Rig**,
  **Great Flood** and **Blank**.

Plugin install gives you `ReplicatedStorage.Ocean` (one ModuleScript; every setting is an attribute on it) with two
bootstrap scripts inside, `OceanServer` and `OceanClient`, that start the sea on the server and on every client.
Each addon is a script next to it (`ReplicatedStorage.OceanBuoyancy`, `ReplicatedStorage.OceanSwimming`, and so on)
with its own settings as attributes.

### Driving the plugin as an AI agent

In Studio the plugin exposes `shared.InfiniteOcean` to the command bar and to MCP agents, with commands such as
`Install()`, `Set("WaveHeight", 2)`, `Addon("Swimming", true)`, `Preview(true)`, plus preset, tag and hull
commands. The AgentSupport addon's README documents every command and its arguments: read it before calling, and
prefer these commands over editing the attributes by hand.

## Requirements

- The sea is drawn on `EditableMesh`. In a **published** game the experience must allow Mesh / Image APIs
  (Experience Settings > Security > Allow Mesh / Image APIs). It always works in Studio. If it is off, the plugin
  offers a **Flat Ocean** instead, which needs no permission.
- Editable meshes and images share a strict per-device memory budget on clients. The renderer steps its quality
  down by itself when the budget is refused, so other EditableMesh / EditableImage use in your game competes with
  the sea.

## Install without the plugin (Wally)

```toml
[dependencies]
Ocean = "kashtheking/ocean@1.1.0"
```

From Wally the module lives in `ReplicatedStorage.Packages`, so start it yourself once on the server and once on
every client. `Init` is safe to call more than once.

```luau
-- In a server Script and in a client LocalScript
local Ocean = require(game:GetService("ReplicatedStorage").Packages.Ocean)
Ocean.Init()
```

With the plugin, require `game.ReplicatedStorage.Ocean` instead and don't call `Init` (the bootstrap scripts do).
If Rojo manages `ReplicatedStorage`, make sure it doesn't delete the plugin's `Ocean` module and addon scripts.

## Settings

Settings are attributes on the module. The server owns them and they replicate. Write them through
`Ocean.Settings.Set` (validated and clamped; returns the stored value or nil) or `SetAttribute`.

```luau
local Ocean = require(game:GetService("ReplicatedStorage").Ocean)

Ocean.Settings.Set("SeaLevel", 0)
Ocean.Settings.Set("WaveMode", "FFT")       -- "Gerstner" (six waves, Wave1..Wave6) or "FFT" (simulated spectrum)
Ocean.Settings.Set("FFTWaveHeight", 9)      -- FFT: significant wave height in studs
Ocean.Settings.Set("FFTWindAngle", 30)      -- FFT: wind direction in degrees, 0 = +X
Ocean.Settings.Set("Quality", "Regular")    -- "Fast", "Regular" or "Realistic"
print(Ocean.Settings.Get("SeaLevel"))       -- either side

Ocean.Settings.OnChanged(function(name)
	print(name, "is now", Ocean.Settings.Get(name))
end)

-- Apply a whole preset from code
for name, value in Ocean.Presets.Ocean["Oil Rig"] do
	Ocean.Settings.Set(name, value)
end
```

`Ocean.Settings.List` is the full spec (name, section, default, range, hint). Useful Gerstner settings:
`WaveHeight` (scales every wave), `WaveGravity` (speed), `TimeScale`, `UseWind` (follow `workspace.GlobalWind`),
and `Wave1Angle` / `Wave1Length` / `Wave1Steepness` through `Wave6...`. `FlatSea = true` gives a flat surface at
`SeaLevel` that still supports buoyancy, swimming and events.

## Weather and zones (server only)

A weather is a named table of setting overrides; only weather settings (waves, colors, foam, spray, underwater tint)
count, so presets can be passed straight in. Fades run on server time, so clients and server physics stay in step,
and late joiners get the current weather automatically.

```luau
Ocean:CreateWeather("Storm", Ocean.Presets.Ocean["Pirate Seas"])
Ocean:CreateWeather("Glassy", { WaveHeight = 0.25, FoamOpacity = 0 })
Ocean:SetWeather("Storm", 30)   -- cross-fade the whole sea over 30 s
Ocean:SetWeather("Default")     -- back to the module's attribute values
print(Ocean:GetWeather())

-- A disc of sea with its own weather, blended over Blend studs at the rim
Ocean:CreateZone("Bay", { Position = Vector3.new(0, 0, 2000), Radius = 800, Blend = 200, Weather = "Glassy" })
Ocean:SetZoneWeather("Bay", "Storm", 10)
print(Ocean:GetZoneAt(somePosition), Ocean:GetWeatherAt(somePosition))
Ocean:RemoveZone("Bay")
```

For zones built in Studio, tag a part `OceanZone`; any setting written as an attribute on that part overrides the
module inside it.

## Water queries and events (either side)

```luau
local y = Ocean:GetHeight(position)          -- world Y of the surface above/below the point, right now
local depth = Ocean:GetDepth(position)       -- studs under the surface, negative above it
local under = Ocean:IsUnderWater(position)   -- false inside an OceanDry region
local normal = Ocean:GetSurfaceNormal(position)

Ocean:Track(crate) -- events without floating it
Ocean.EnteredWater:Connect(function(instance) end)
Ocean.ExitedWater:Connect(function(instance) end)
Ocean.WentUnderWater:Connect(function(instance) end)
Ocean.WentAboveWater:Connect(function(instance) end)
local state = Ocean:GetWaterState(crate) -- { Touching, Under, Depth, Surface } or nil
```

Player characters are tracked automatically. Events fire on the side that simulates the instance: connect on the
server for game logic and on the client for effects. `GetHeight` is cheap for a handful of objects per frame; for
hundreds, sample every few frames.

## Tags

Plain CollectionService tags (names follow the `FloatTag`, `ObstacleTag`, `DryTag`, `TrackTag`, `ZoneTag` settings):

| Tag | Helper | Effect |
|---|---|---|
| `OceanFloat` | `Ocean:AddFloat(part)` | Unanchored part floats (Buoyancy addon) |
| `OceanObstacle` | `Ocean:AddObstacle(model)` | Waves calm around it (`OceanShore`, `OceanCalmness` attributes) |
| `OceanDry` | `Ocean:AddDryRegion(model)` | No water inside its box (ship interiors, underwater rooms) |
| `OceanTrack` | `Ocean:Track(instance)` | Water events only |

## Mistakes to avoid

- Building your own wave mesh or using a huge flat part when the plugin already does it, with matching physics.
- Calling `CreateWeather`, `SetWeather`, zone methods or `Settings.Set` from the client. They are server only.
- Expecting things to float or players to swim without the plugin's Buoyancy and Swimming addons.
- Publishing without allowing Mesh / Image APIs and then wondering why the sea is flat.
- Moving floating parts by setting `Position` every frame on the server for hundreds of objects. Use the Buoyancy
  addon or sample heights less often.
- Requiring `ReplicatedStorage.Ocean` in a Wally-only setup (it lives in `ReplicatedStorage.Packages`), or forgetting
  `Ocean.Init()` there.
