Skip to content
AFM Studio
  • Pages

    AFM Studio
  • Work
  • Lab
  • About
  • CV
  • Contact
  • Projects

    AksaraA searchable library for Otorita IKN's knowledge, where the server checks every reader's access across three levels
  • Daily TaqwaTamper-resistant prayer attendance system for ~2,000 OIKN government employees at Masjid Negara IKN
  • Lantara v2A permit engine for Otorita IKN where administrators add new permit types from a screen, without a developer
  • eKiosk IKNTwo control rooms for Nusantara: kiosk content management and live bus tracking with arrival times that grade themselves
  • E-Monev IKNA monitoring platform for Nusantara's 23-year build, tracking every unit's quarterly progress against 24 national KPIs
  • JDIH OIKNOIKN's public legal document portal, with a controlled publishing workflow and AI summaries that never leave the server
  • Jejak PenanamanA check-in app for IKN tree-planting events that proves each participant was on site with a geofenced selfie
  • Dashboard SAKTIA nightly pipeline and dashboard that replaces OIKN's manual budget downloads for all six of its budget units
  • DoseRxA bedside dose calculator for 127 drugs that rounds each dose to tablets and syrups Indonesian pharmacies sell
  • CubiqA speedcubing trainer with competition-grade timing and a solver I wrote for each of the 8 major puzzles
  • ScimotionAn interactive science library of 86 articles, each explained next to animations the reader can control
  • WA Daily Scrum BotA WhatsApp bot that chases missing standup reports for a 14-person team and flags tasks stuck for days

↑ ↓ to move · Enter to open · Esc to close

Tide Analysis

A tide explainer fitting eight Indonesian ports from raw readings, which refuses when a record is too short

Screenshots
Indonesian ports covering all four tide types
8
hourly records per port, January to August 2026
212 days
amplitude error recovering constants under 15 cm noise
< 1 cm
tide, linear-algebra or charting libraries
0
On this page

The problem

Students and curious readers who want to understand tides mostly find tools that look up answers. Most tide predictors take published constants, numbers that describe how the Moon and Sun pull the water at one coastline, and add them up into a curve. That's fast, but it proves nothing about the physics and can't work for a place with no published constants.

Tide Analysis starts from raw hourly sea-level readings and works out those constants itself, in the browser. That exposes something lookup tools hide. Some tidal cycles are so close that telling them apart takes about six months of data. Ask a two-week record to separate them, and the right answer is "I can't". The site is not for navigation, and it names Indonesia's official hydrographic office, Pushidrosal, as the source to use.

What I built

The maths is tested against records with known answers

I wrote the astronomy, the curve fitting and the prediction engine myself, as plain functions I can test without a browser. Before the solver existed, I built a generator that makes fake tide records from constants I choose, with noise I choose. The solver has to recover those constants. With 15 cm of noise on every reading, it still recovers every amplitude within a centimetre. When one of these tests fails, the maths is wrong, never the tolerance.

It says when a record is too short

Before fitting, the app checks whether the record is long enough to separate each pair of cycles. If not, it refuses and names the two that conflict and how many days would resolve them. Every fit also reports a stability figure, and the chart always shows the gap between prediction and observation, where weather and storm surges appear. The main interactive lets you drag the record length from 15 days to about six months and watch cycles become resolvable.

I pick the sensor that is actually recording

A tide station can report several sensors, and the most complete one can be broken. One radar sensor at Benoa sat flat at −0.281 m for seven months while the gauge beside it recorded 2.3 m tides. Another sensor, at Semarang, had 9-metre spikes that made it look the most active. I settled on picking the sensor with the largest typical range and treating spikes as gaps. Each of these cases is now a test.

Surprising results stay in, as tests

Losing 70% of a record in scattered pieces barely hurts the fit. Losing the same 70% in one block ruins it. I kept that as a test. A stability panel fits each record in four separate stretches to show whether a constant belongs to the harbour or to those months. At Benoa, the main lunar tide moves 9% between stretches and the main solar tide moves 56%.

Data is only shipped when its licence is clear

A check runs before every build and fails it if any bundled data source has an unresolved licence. Two of the three sources I wired up, UHSLC and BIG, ship switched off, because their terms for Indonesian stations were never confirmed.

Result

Tide Analysis is live, in Indonesian and English, and runs entirely in your browser with no server. It bundles eight Indonesian ports from the IOC sea-level network, each a 212-day hourly record from 1 January to 1 August 2026. Together they cover all four tide types, each classified from the station's own fit. You can compare modern fitting with the classical Admiralty method, and toggle cycles on and off to see the two-week spring-neap rhythm appear. One worked example walks a single constant from timestamp to answer. I built it over three days, 12 to 14 August 2026.

Under the hoodTechnical detail for engineers
  • lib/astro holds the Doodson argument calculator and nodal corrections; lib/tide holds the least-squares fit, prediction and the Admiralty method. Plain functions over typed arrays, with no React, DOM, clock or network.
  • The Rayleigh criterion gates each fit. Refusals are structured and tested in both directions. K1/P1 and S2/K2 need about 183 days; 15 days resolves six of the ten standard constituents and 29 days resolves eight.
  • Every solve returns its condition number (κ) in the result type. Constituent correlation, leakage at refused frequencies and coverage masks are reported alongside.
  • Sensor rule: largest robust scale, with spikes rejected into declared gaps. Two stack overflows on real-sized inputs were fixed after synthetic tests passed.
  • records:validate checks every source against a licence manifest before the build. IOC, UHSLC and BIG each have an adapter to one internal Record shape. UHSLC was planned as enabled, but its Indonesian data is attributed through BIG.
  • The constituent explorer re-sums fitted constants and doesn't refit. A live refit would let M2 absorb its neighbours and hide the spring-neap beat between M2 and S2.
  • The ± on each constant is one standard error assuming independent hourly noise, which tidal residuals violate. At Benoa, S2's ± is about 1% while its movement between stretches is 56%. M4 moves 81%.
  • Tide type uses the Formzahl ratio, from 0.204 at Sabang to 3.486 at Kolinamil in Jakarta's port. Kolinamil has 740 missing hours and covers 202.5 of the 212 days, but its gaps are scattered and it fits cleanly. The chart is hand-built SVG, with no charting, linear-algebra or tide library. Two runtime dependencies (Next.js, React).
  • Static export to GitHub Pages, gated on tests and the licence check. 377 tests in 24 files. About 12,500 lines of TypeScript, 88 commits.

Screenshots

Beranda
Tidal Character
Record
Tide Chart
Method

Hiring, or building something similar?

I'm open to full-time roles and select freelance work.