Skip to main content

Overview

Gitlantis provides multiple navigation methods with realistic physics simulation. The boat responds to keyboard input or touch-based joystick controls with smooth acceleration, deceleration, and turning mechanics.

Keyboard Controls

The boat responds to both arrow keys and WASD controls:
KeyAlternativeAction
WMove forward
SMove backward (50% speed)
ATurn left
DTurn right
H-Sound horn
F-Toggle minimap fullscreen
Source: src/browser/hooks/useBoat/keyboard/index.ts:10-35

Joystick Controls (Mobile)

On mobile devices, a dynamic joystick appears using the nipplejs library:
const options: JoystickManagerOptions = {
  zone: joystickRef.current,
  mode: "dynamic",
  color: "red",
  size: 100,
  restOpacity: 0.4,
};
The joystick translates touch input into directional commands:
manager.on("move", (_, data: JoystickOutputData) => {
  const angle = data.angle?.radian ?? 0;
  const force = data.force ?? 0;
  const dx = Math.cos(angle) * force;
  const dy = Math.sin(angle) * force;
  onMove(dx, dy);
});
The joystick is only visible on mobile devices and automatically hides on desktop (using md:invisible class).
Source: src/browser/components/world/joystick/index.tsx:25-42

Movement Physics

The navigation system uses frame-based physics with configurable parameters:

Speed and Acceleration

const config = {
  maxSpeed: settings.boatSpeed,
  acceleration: settings.acceleration,
  deceleration: settings.deceleration,
  turnSpeed: settings.turnSpeed,
  turnDeceleration: settings.turnDeceleration,
  rockingAmplitude: settings.rockingAmplitude,
  rockingSpeed: settings.rockingSpeed,
  bobbingAmplitude: settings.bobbingAmplitude,
  bobbingSpeed: settings.bobbingSpeed,
};
All parameters are user-configurable through the extension settings.Source: src/browser/hooks/useBoat/navigation/index.tsx:24-34

Forward/Backward Movement

The boat accelerates smoothly toward the target speed:
if (keys.forward) {
  targetSpeed = config.maxSpeed;
  activeInput = INTENDED_DIRECTION.FORWARD;
} else if (keys.backward) {
  targetSpeed = -config.maxSpeed * 0.5;
  activeInput = INTENDED_DIRECTION.BACKWARD;
}

if (targetSpeed !== 0) {
  currentState.speed += (targetSpeed - currentState.speed) * config.acceleration;
} else {
  currentState.speed *= 1 - config.deceleration;
}
Backward movement is limited to 50% of max speed for realistic boat physics.
Source: src/browser/hooks/useBoat/navigation/index.tsx:75-106

Turning Mechanics

Turning behavior adapts based on movement direction:
  • Moving forward: Left/right keys turn in the expected direction
  • Moving backward: Turning is reversed (like a car in reverse)
  • Stationary: Boat can pivot in place
if (keys.left) {
  if (isStationary) {
    targetTurn = config.turnSpeed;
  } else {
    targetTurn =
      currentState.intendedDirection === INTENDED_DIRECTION.BACKWARD
        ? -config.turnSpeed
        : config.turnSpeed;
  }
}
Source: src/browser/hooks/useBoat/navigation/index.tsx:112-121

Visual Effects

Rocking and Bobbing

When moving, the boat exhibits realistic rocking and bobbing motion that decreases with speed:
const movementFactor = 1 - Math.min(Math.abs(state.current.speed) / config.maxSpeed, 1);
const time = performance.now() / 1000;

// Side-to-side rocking
(floating as unknown as Group).rotation.y =
  Math.sin(time * config.rockingSpeed * Math.PI * 2) *
  config.rockingAmplitude *
  movementFactor;

// Up-down bobbing
(floating as unknown as Group).position.y =
  Math.sin(time * config.bobbingSpeed * Math.PI * 2) *
  config.bobbingAmplitude *
  movementFactor;
The movementFactor reduces rocking when the boat is moving fast, creating more stability at high speeds. Source: src/browser/hooks/useBoat/navigation/index.tsx:51-64

Stationary Floating

When completely stationary, the boat uses a more complex floating simulation:
const primaryWave = Math.sin(time * 0.6) * state.waveHeight;
const secondaryWave = Math.sin(time * 1.3 + Math.PI / 3) * (state.waveHeight * 0.4);
const chop = Math.sin(time * 2.8) * (state.waveHeight * state.roughness);
const positionWave = Math.sin(boatPos.x * 0.1 + time) * Math.cos(boatPos.z * 0.1 + time);

const totalFloat = primaryWave + secondaryWave + chop + positionWave * 0.2;
This creates realistic multi-layered wave motion when the boat is idle. Source: src/browser/hooks/useBoat/floating/index.tsx:46-53

Frame-Rate Independence

All physics calculations use delta multipliers to ensure consistent behavior across different frame rates:
const deltaMultiplier = Math.min(delta * 60, 2);
boat.rotateY(currentState.angularVelocity * deltaMultiplier);
boat.translateX(-currentState.speed * deltaMultiplier);
This prevents the boat from moving faster on high-refresh-rate displays.
Source: src/browser/hooks/useBoat/navigation/index.tsx:48,142-146

Build docs developers (and LLMs) love