Skip to content

feat(api): status endpoint, fort scan limit status, availability enrichment - #393

Merged
jfberry merged 12 commits into
mainfrom
feat/fort-api-status-and-limits
Aug 22, 2026
Merged

feat(api): status endpoint, fort scan limit status, availability enrichment#393
jfberry merged 12 commits into
mainfrom
feat/fort-api-status-and-limits

Conversation

@jfberry

@jfberry jfberry commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Golbat-side prerequisites for the Diadem fort API migration (ccev/diadem#174 — the consumer design this serves).

Includes #392 as a cherry-pick with attribution preserved (thanks @ccev) — closes #392.

Deliverables

  1. GET /api/status — secret-gated but not fort_in_memory-gated: reports enabled optional features and the server's scan caps so consumers can detect capabilities (and clamp their request limits) without probing gated endpoints:

    {
      "features": { "fort_in_memory": true },
      "limits": { "max_pokemon_results": 3000, "max_fort_results": 4000 }
    }
  2. limit_reached on the fort scan responses (gym/pokestop/station/combined) — straight mirror of feat: expose v3 pokemon scan limit status #392: computed from the pre-collection key count against the effective limit (fortScanLimit = request limit clamped by tuning.max_fort_results).

  3. Availability enrichmenttemp_evolution_id on gym raid availability (mega/primal bosses were indistinguishable from base forms) and ranking_standard on showcase availability. Distinct evolutions now yield distinct entries; both fields thread through FortLookup → availability keys → readers.

  4. Fix: pokestop first_seen_timestamp was int16 on both the model and ApiPokestopResult — a unix timestamp wraps int16, truncating DB reads and API output (gym's field was already int64). Note while fixing: nothing in Golbat currently assigns this field for pokestops (existing DB values round-trip; new stops insert 0) — populating it on first insert is a separate follow-up.

Verification

  • go build -tags go_json ./...
  • go test -tags go_json ./decoder/ . (new: fort limit helper tests, /api/status route tests incl. 401 + not-503-gated assertions, availability enrichment tests incl. mega-vs-base distinctness)
  • go test -tags go_json -race -run 'Availability|Observe…' ./decoder/
  • golangci-lint run — 0 issues

🤖 Generated with Claude Code

ccev and others added 6 commits August 3, 2026 17:28
…y enrichment

Golbat-side prerequisites for ccev/diadem#174. Branch already carries
ccev's #392 as a cherry-pick (attribution preserved).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Mirrors the v3 pokemon scan limit_reached (#392) on the gym, pokestop,
station and combined fort scan responses, computed from the
pre-collection key count against the effective limit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Secret-gated but not fort_in_memory-gated: consumers (ccev/diadem#174)
detect capabilities and read the server's scan caps from it instead of
probing gated endpoints.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Mega/primal raid bosses were indistinguishable from their base form in
gym raid availability, and showcase entries lacked the ranking standard.
Both fields already exist on the fort records; thread them through
FortLookup, the availability keys and the API results so consumer pick
lists (ccev/diadem#174) can render them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Pokestop model (and ApiPokestopResult) declared the column int16 —
a unix timestamp wraps int16, so DB reads and API output truncated.
Gym's equivalent field was already int64. Note: nothing in Golbat
currently assigns this field on new pokestops (it round-trips existing
DB values only); populating it on first insert is a separate follow-up.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
FortLookup is held per resident fort, so the two int16 enrichment
fields cost a measured 8 bytes each after alignment (184 -> 192).
Both values are tiny enums: as int8 placed in RaidLevel's pad byte and
the pad before ShowcaseExpiry, unsafe.Sizeof returns 184 again — the
enrichment now adds zero bytes per fort.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jfberry

jfberry commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator Author

Memory note for the enrichment: the two new FortLookup fields are deliberately int8 placed into existing alignment padding (RaidLevel's pad byte, and the pad before ShowcaseExpiry). Measured with unsafe.Sizeof: FortLookup was 184 bytes before this PR, 192 with naive int16 placement, and 184 again as committed — the enrichment adds zero bytes per resident fort. No DNF filter fields were added; the values feed only the availability index keys (mega X/Y distinctness in pick lists — raid level alone can't distinguish those).

The fort API's design is native JSON for blob columns, but the pass
was incomplete: gyms were fully converted while stationed_pokemon,
showcase_focus, showcase_rankings and both quest_conditions still
arrived as escaped strings. Convert all five via jsonRaw, matching
the gym rsvps precedent. Consumers are unmerged (ccev/diadem#175,
WatWowMap/ReactMap#1228 — the latter already handles both forms), so
now is the time to break the wire.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
jfberry added a commit to jfberry/diadem that referenced this pull request Aug 4, 2026
Golbat is finishing its fort-API native-JSON conversion
(UnownHash/Golbat#393): stationed_pokemon, showcase_focus and
showcase_rankings switch from serialized strings to real JSON. Accept
both wire generations via blobToString so either Golbat works, and
type the wire fields honestly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jfberry

jfberry commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator Author

Added the native-JSON completion for the remaining fort blob columns (stationed_pokemon, showcase_focus, showcase_rankings, both quest_conditions) via jsonRaw — the #385 design said native JSON for blobs but the pass only fully covered gyms. Wire-breaking for those five fields, but both consumers are unmerged: ReactMap's branch already handles both forms, and ccev/diadem#175 now does too (dual-mode blobToString).

Mygod and others added 3 commits August 19, 2026 16:49
Cherry-picked from #397 (Mygod:codex/showcase-focus-filter) onto the fort
API branch so the showcase work lands with the availability enrichment it
depends on.

Conflicts resolved by keeping both sides' additions:
- FortLookup gains ShowcaseBuddyMinLevel alongside ShowcaseRankingStandard;
  both int8s share the pad before ShowcaseExpiry, so the struct stays 184 B.
- showcaseKey keys on ranking standard AND structured focus.
- ApiPokestopShowcaseAvailable carries ranking_standard and showcase_focus.

Also corrected the showcase_focus nullability doc: it is null for legacy rows
and for rows whose stored focus fails to parse, not only the former.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
first_seen_timestamp was written but never selected: it was absent from
pokestopSelectColumns, the only load path for both single-row cache-miss
loads and preload. The column never reached PokestopData, so every
pokestop API response reported first_seen_timestamp 0 while the database
held the real UNIX_TIMESTAMP() value written at insert.

This also corrects the record from ab62fc9, which widened the field to
int64 and described current behaviour as "existing DB values round-trip;
new stops insert 0". Neither held: values never round-tripped because they
were never selected, and new stops get a real UNIX_TIMESTAMP(), not 0. The
widening was needed but inert on its own.

The stored value was never corrupted by the gap because pokestop's upsert
omits first_seen_timestamp from ON DUPLICATE KEY UPDATE -- unlike
showcase_focus, which the upsert does write back and which had the same
missing-column gap.

Adds a test asserting every db-tagged field of PokestopData and GymData
appears in its select-column constant. It fails without this change and
would have caught the showcase_focus gap too.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ing standard

Availability advertised both dimensions with no way to request them. Adding
RaidPokemonEvolution split mega and base bosses into distinct raid options,
and ShowcaseRankingStandard split showcases by standard, but isFortDnfMatch
read neither -- so a client could see that mega and base Mewtwo raids were
both nearby and had no filter that could tell them apart.

Both read fields already resident in FortLookup, so there is no new plumbing
and no memory cost. Each follows the liveness gate its neighbours use:
raid_temp_evolution_id joins raid_level/raid_pokemon_id behind the raid-active
check, contest_ranking_standard sits behind the showcase expiry check.

0 is a real selector in both, not a wildcard: it means base-form boss (or
unhatched egg) and unknown ranking standard respectively, matching how the
availability payloads document those values.

Invasion availability has the same gap for confirmed/slotN_pokemon_id, but
that predates this branch and needs per-incident matching, so it is left out.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two field docs contradicted the code.

/api/status listed "station by-id endpoints" among what fort_in_memory
gates. It does not gate them: get-station and query-stations call
GetStationRecordReadOnly, which falls back to loadStationFromDatabase on a
cache miss and hydrates battles from the database either way; the flag only
decides whether the rtree is updated as a side effect. Residency is a cache
optimisation there, not a requirement, so a consumer reading
fort_in_memory:false would have skipped calls that work. Only the scan and
available routes 503 without the index, because without the rtree there is
nothing to search.

showcase_focus and showcase_rankings claimed "null when no showcase". They
are never cleared on expiry -- deliberately, matching how the raid fields
retain their last value -- so they are null only until a pokestop's first
observed showcase and hold the last contest indefinitely after. The quest
fields keep their "null when no quest" wording, which is accurate because
ExpireQuests actively clears them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jfberry
jfberry marked this pull request as ready for review August 22, 2026 09:08
@jfberry
jfberry merged commit c305264 into main Aug 22, 2026
7 checks passed
@jfberry
jfberry deleted the feat/fort-api-status-and-limits branch August 22, 2026 09:08
jfberry added a commit that referenced this pull request Aug 23, 2026
Brings in #393 (fort scan status/limits, showcase focus filtering, native
JSON for fort blob columns) and the pokestop first_seen_timestamp fixes.

Four conflicts, all shallow:
- fortRtree.go, api_fort.go: main added code adjacent to lines this branch
  had already rewritten (showcase focus parsing next to the lookup key;
  fortScanLimit helpers above internalGetForts). Both sides kept.
- api_pokestop_test.go, api_station_test.go: golden snapshots differing in
  two independent ways. Took main's JSON shape (quest_conditions,
  quest_rewards and stationed_pokemon now emit as native JSON rather than
  escaped strings) with this branch's parseable fort ids.

Two test files main added compile against the old string-keyed types and
were converted: api_fort_showcase_test.go (fortTree/fortLookupCache
instantiation, the search closure, and its generated ids) and
fort_availability_hooks_test.go's new Buddy showcase case.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

3 participants