Skip to content

feat(oceanpark): add Ocean Park Hong Kong#130

Open
technodisney wants to merge 1 commit intoThemeParks:mainfrom
technodisney:oceanpark-pr
Open

feat(oceanpark): add Ocean Park Hong Kong#130
technodisney wants to merge 1 commit intoThemeParks:mainfrom
technodisney:oceanpark-pr

Conversation

@technodisney
Copy link
Copy Markdown
Contributor

@technodisney technodisney commented Apr 8, 2026

Summary

Adds Ocean Park Hong Kong as a new destination.

Single destination, one park. No API credentials required — the park's mobile API uses a short-lived bearer token obtained from a public endpoint.

Implementation

Auth

  • optoken header injected via @inject on all requests to sop.oceanpark.com.hk
  • Device UUID generated once and persisted 90 days via @cache
  • Token TTL driven by tokenExpire field in the API response

Coordinates

  • reference_points.json from map.oceanpark.com.hk provides pixel→lat/lng anchor points
  • Affine transform (least-squares, Cramer's rule) projects each entity's pixel position to real-world coordinates across 6 map categories

Entities

  • Rides (sortId: 8), transport (sortId: 7), shows (sortId: 15), dining (sortId: 17)
  • Tags: minimum/maximum height, wet rides, unsuitable for pregnant, FastPass (paidReturnTime)

Live data

  • Wait times and today's operating hours for rides/transport (from pflowInfo)
  • Show times from activityList on entity detail endpoint

Schedules

  • 30-day park operating hours, parking hours, Summit zone informational entries

Test plan

  • npm run dev -- oceanparkhongkong — entities, live data, and schedules return data
  • npm run dev -- oceanparkhongkong --ignore-cache — fresh fetch works correctly
  • npm test — existing tests unaffected

🤖 Generated with Claude Code

- Auth token (optoken) injected via @Inject; device UUID persisted
  90 days via @cache; dynamic TTL from API tokenExpire field
- Coordinate affine transform: fetches reference_points.json from map
  subdomain and projects pixel positions to lat/lng for all entities
- Entities: rides, transport, shows, dining — with height restriction
  tags, wet rides, pregnant warning, and FastPass (paidReturnTime)
- Live data: wait times + today's operating hours (rides/transport),
  showtimes from activityList (shows)
- Schedules: 30-day park operating hours, parking, Summit zone entries

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Member

@cubehouse cubehouse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work — well-structured implementation with clean auth flow and good use of the decorator patterns. A few items to address:

m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0]) +
m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0]);

const D = det(M);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the map API ever returns collinear or duplicate reference points, D will be zero and all coordinates become NaN/Infinity — which silently propagates through entity locations (the if (coords) check passes because {latitude: NaN, longitude: NaN} is truthy).

Add a guard here:

const D = det(M);
if (Math.abs(D) < 1e-10) return null;

Then handle the null in getCoordinateMapEntries by falling back to no coordinates for that category.

// ── Implementation ──────────────────────────────────────────────────────────

@destinationController({category: 'Ocean Park'})
@config
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The @config class decorator is not needed here — @destinationController already applies @config internally (see destinationRegistry.ts line 208). Having both double-wraps the class in config proxies. Remove this line.

const coeffs = computeAffineTransform(refPoints);
const entries: [string, {latitude: number; longitude: number}][] = [];

for (const category of MAP_CATEGORIES) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These 6 map category fetches are independent but awaited sequentially. With the HTTP queue rate limit (250ms), this adds ~1.5s of wall time on a cold cache.

Use Promise.all to parallelize:

const responses = await Promise.all(
  MAP_CATEGORIES.map(cat => this.fetchMapCategoryData(cat))
);
for (const resp of responses) {
  const entities: OceanParkMapEntity[] = await resp.json();
  // ...
}

@cubehouse cubehouse changed the base branch from ts to main April 14, 2026 07:38
@cubehouse
Copy link
Copy Markdown
Member

Hey @technodisney — just checking in on this. There are 3 inline review comments from April 8 that still need addressing:

  1. Affine transform guard — add a null check when the determinant is zero to prevent NaN coordinates
  2. Remove @config class decorator@destinationController already applies it, so having both double-wraps the class
  3. Parallel map fetches — the 6 sequential category fetches can use Promise.all for a ~1.5s speedup

Let us know if you need any help with these or if you have questions!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants