Request-first lure pokemon processing - #391
Conversation
A MapPokemonProto only ever arrives as fort.ActivePokemon inside its fort's own GMO entry, so capture the fort id and coordinates at extraction instead of re-deriving them from a pokestop lookup that can miss (and whose failure used to commit an unplaced record at 0,0 that no later GMO could repair). updateFromMap becomes an order-tolerant merge: new records are placed from the captured fields; existing lure records only receive GMO-owned facts (verified despawn time). Also guard loadPokestopFromDatabase against nil database to allow tests with unknown forts to proceed without panicking. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Remove premature newRecord=false flip that violated the invariant: newRecord transitions to false only in getOrCreatePokemonRecord (rehydration) and savePokemonRecordAsAtTime (post-save). The early flip in updateFromMap caused saves to see genuinely-new lure placements as existing records, queuing spurious tree removes instead of inserts. Update TestUpdateFromMapMergeAddsVerifiedExpiryOnce to simulate the production commit between updateFromMap calls, matching existing test patterns (TestUpdateFromMapLeavesNonLureRecordsAlone). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The request carries the encounter id, fort id and fort coordinates, so a disk encounter no longer needs a prior GMO: it creates a fully placed lure_encounter record with a 180s unverified expiry estimate (lure spawns live 3 minutes), which a later GMO tightens to the verified despawn. The request is now essential, matching GetStationDetails; the DisplayId encounter-id stand-in is gone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Apply the codebase convention for proto unmarshaling: use the unmarshalClientProto helper instead of raw proto.Unmarshal. This provides ~3.7% faster decode rate and 5.5% fewer allocations via DiscardUnknown, consistent with every other request/response decode in the file (e.g. decodeGetStationDetails, decodeTappable). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Disk encounter responses are processed directly against the record created from their request proto; nothing waits for a GMO anymore. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… metrics, comments)
Five review fixes on the request-first lure pipeline:
- Eliminate the decoder-test statsCollector data race: set the noop
collector once in init_test.go's init() instead of per-test in
lureTestSetup, since the async stats aggregation worker reads the same
global concurrently with per-test reassignment.
- Guard UpdatePokemonRecordWithDiskEncounterProto against a SUCCESS
response with a nil PokemonDisplay, which otherwise panics downstream
in addEncounterPokemon.
- Count DiskEncounter requests with encounter id 0 as a metrics error in
decodeDiskEncounter, instead of the decoder-side early return going
unnoticed after an "ok" increment; keep the decoder-side guard as
defense in depth.
- Correct a false comment in decodeDiskEncounter about the HTTP path's
base64 decoding of an empty request (decodeBase64Pooled("") returns
nil, not a non-nil empty slice).
- Document why loadPokestopFromDatabase treats a nil GeneralDb as
sql.ErrNoRows (zero-value test DbDetails{}; never hit in production).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Thanks — this looks like it fixes #390 and substantially improves the request-first I captured and decoded current
At Lines 553 to 561 in aebc868 Consequently, PR #391 should make Disk-encountered lure Pokémon visible using the request coordinates, but:
There being only one active lure Pokémon per stop is compatible with this: the repeated field simply contains one I think the repeated-field ingestion should be tracked separately from #390. The eventual fix should retain singular support, iterate A |
Live captures (17k GMOs, PR #391 review) show current clients deliver lure pokemon exclusively in the repeated fort.ActiveFortPokemon wrapper (SpawnType LURE, nested MapPokemonProto with zero lat/lon) — the singular fort.ActivePokemon this code extracted is always nil, so the GMO lure path never fired on live traffic and un-encountered lures were invisible. Extraction now unwraps LURE entries from the repeated field, keeps legacy singular support, dedupes by encounter ID, and filters POWER_UP wrappers. Placement already comes from the enclosing fort's coordinates, which is required: the nested proto's own lat/lon are zero on the wire. Regression-tested against a sanitized live-capture fixture (testdata/gmo-active-fort-pokemon.pb). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Confirmed and fixed in 23a2334 — thanks for the capture work, and to the fixture for making this trivially testable. Root cause matched your analysis exactly: extraction only read the singular What landed:
Folded into this PR rather than tracked separately since without it the PR's GMO merge path is dead code on current traffic. |
Fixes #390. Closes #283.
Problem
A failed pokestop lookup while processing a lure
MapPokemonProtocommitted a partially initialized pokemon record: indexed in the R-tree at (0,0), nopokestop_id, no expiry, marked non-new — and therefore permanently unrepairable, while a later DiskEncounter would happily attach IVs and fire hundo webhooks at lat/lon 0 withdisappear_time0 (#390). Structurally, the wholediskEncounterCacheexisted only because the DiskEncounter response carries no location, forcing responses to wait for a GMO (#283).Approach
Each proto contributes what it alone knows, in any arrival order:
fort.ActivePokemon)DiskEncounterProto(request)DiskEncounterOutProto(response)RawMapPokemonDatanow carries the fort's id/lat/lon captured at extraction — placement cannot fail, even in pokemon-only scan contexts where the fort cache is never populated.decodeDiskEncounternow requires and parses the request proto (same posture asGetStationDetails): encounter id comes fromrequest.EncounterId(thePokemonDisplay.DisplayIdstand-in is gone), and an encounter with no prior GMO creates a fully placedlure_encounterrecord with a 180 s unverified expiry (lure spawns live 3 minutes). A later GMO tightens it to the verified despawn. Requests missing the payload are counted (request_missing) and skipped.updateFromMapis now an order-tolerant merge: new records get placed from the captured fort fields; existing lure records only receive GMO-owned facts (verified expiry, cell id, username backfill) — never a seen-type downgrade, never touching encounter data, and no-change sightings skip the save.diskEncounterCacheis deleted — nothing waits for anything.Testing
go build,go vet, fullgo test ./..., andgolangci-lint runare clean; the new tests pass under-race.main) test race indecoder/station_battle_test.go(directstatsCollectorreassignment vs the async stats worker) was found while verifying — left for a follow-up since it's unrelated to this branch.🤖 Generated with Claude Code