Skip to content

feat: personal shiny prediction, settings - #1268

Open
skelgaard wants to merge 7 commits into
WatWowMap:developfrom
skelgaard:personal-shiny
Open

skelgaard wants to merge 7 commits into
WatWowMap:developfrom
skelgaard:personal-shiny

Conversation

@skelgaard

@skelgaard skelgaard commented Oct 14, 2026 •

Copy link
Copy Markdown

Summary

Personal shiny prediction for ReactMap: users save their in-game player(s) once, and wild Pokémon that will be shiny for them are marked on the map. It works with any Golbat. When Golbat can roll personal shinies itself (UnownHash/Golbat#423), ReactMap uses that instead.

The branch also carries settings sync between browsers, Poracle dialog improvements and a few alert improvements. They are listed separately below and can be split into their own PRs if preferred.

Try it (Docker)

Next to your docker-compose.yml (ReactMap service named reactmap); run it again to update. It builds from its own clean clone (rm-shiny): building in a folder with your configs in it copies them into the image, and an empty local.json there breaks the build.

[ -d rm-shiny ] || git clone -b personal-shiny https://github.com/skelgaard/ReactMap.git rm-shiny
git -C rm-shiny fetch -q && git -C rm-shiny reset -q --hard origin/personal-shiny
docker build --network host -t reactmap:personal-shiny rm-shiny
printf 'services:\n  reactmap:\n    image: reactmap:personal-shiny\n' > docker-compose.override.yml && docker compose up -d --force-recreate reactmap

Back to the official image: rm docker-compose.override.yml && docker compose up -d reactmap.

Already cloned the branch yourself? New changes come as regular commits, so git pull works. Cloned it before 20 October (when it was still force-pushed)? Then once:

git fetch origin personal-shiny && git reset --hard origin/personal-shiny
docker build --network host -t <your image> . && docker compose up -d --force-recreate reactmap

Personal shiny needs a login (Discord, Telegram or local), as the players are saved per user in their profile. Enable the perm in local.json, see Config, and log out and in again to get it.

Personal shiny

How it works

  • The game decides shininess per player with a java.util.Random seeded from sha1(encounterId as uint64 LE + playerId). The first nextFloat() is compared against the species/form shiny rate. ReactMap reproduces this bit for bit.
  • Shinies are marked with the shiny sprite, a glow, a ✨ label (with the player's name when more than one player is active) and a popup line.
  • Zoomed-out views split the scan box, so every Pokémon in view is checked despite Golbat's result cap.

Profile

  • Users add named players in their profile. A player can be paused without being deleted.
  • Players are entered by player ID or, when a PlayerID lookup service is configured, by friend code. The ID is then looked up, and the trainer name, level and team are shown.
  • Optional limit on how often players can be swapped, to stop users cycling accounts during events. Adding players up to the user's maximum is always free; renaming, pausing and removing are always free.
  • With a Poracle webhook selected, active players are linked to the user's Poracle account and paused or removed ones are unlinked. This is re-synced hourly while the map is in use.

Filters

  • Shiny IV is a range in the advanced filter, like the IV range. Pokémon that are shiny for the user, inside the Shiny IV range and matching the filter's other conditions (level, CP, gender, size, PvP…) are shown next to the regular matches. Example: IV 90-100 with Shiny IV 1-100 shows normal Pokémon from 90 % and the user's shinies from 1 %. Like the other ranges, 0-100 sets no condition; shinies that match the regular filters are still marked.
  • Only Shiny (per Pokémon, next to XXS/XXL) shows only the shinies of that Pokémon. Only the regular IV range is disabled; the other conditions still apply.
  • A global Shiny IV range in the drawer, and a Personal Shinies mode:
    • Off (default): no shinies are added. The Shiny IV ranges and Only Shiny can't be set and are ignored. Pokémon that the filters show are still marked.
    • Include: the map as usual, plus every shiny of the user within the Shiny IV range (the Pokémon's own, otherwise the global one), whatever the other filters and Global Respects Selected. 0-100 is every shiny, also those without IVs.
    • Only: only the user's shinies, narrowed down by every condition the filters set. Here each set condition has to match (shiny IV, PvP rank, gender, size, stats), while the PvP leagues among themselves and XXS / XXL work as on the map (either one). The regular IV range is disabled, as the Shiny IV range takes over. With nothing set, all shinies show.
  • The legacy filter knows a shiny term, e.g. shiny & 90-100.
  • Display options (Pokémon settings, all on by default): Always Show IV of Personal Shinies, Player Names on Personal Shinies and Sparkle on Personal Shinies.
  • Half / Half Glow (on by default): a Pokémon with two glows (e.g. shiny and 100% IV) gets both colours, half and half. The "Multiple" colour is then hidden; turn it off to use one colour of your own instead.
  • A 0% IV glow, and the IV label is shown for 0 % like it is for 100 %. The Hundo / Nundo glows are labelled "100% IV" / "0% IV" in the settings, like the quick selects.

Rates

  • By default, rates are estimated from Golbat's pokemon_shiny_stats and snapped to the known in-game rates (1/512, 1/256, 1/128, 1/64, 1/25, 1/20, 1/10). Without a rates service, only species/forms that have been seen shiny there are predicted, so unreleased shinies are never shown.
  • Optionally, api.shinyRates.url points to a rates service using the same JSON schema as Golbat#423's [shiny_rates]. Its rates are used first and the own estimates fill the gaps. If the service is unreachable, ReactMap falls back to its own estimates.
  • As in Golbat#423: the service's fallback is used for variants it doesn't list, its per-timezone overrides (local events such as Community Day) for the map's timezone (api.shinyRates.timezone, the server's by default), and a known Ditto is shiny when the third draw is below 1/64. The implementation is checked against Custom Attribution Prefix #423's test vectors.

Golbat support

Recommended: a Golbat with #423. Golbat then checks the Pokémon for the user's players itself, in one scan. Without it, ReactMap fetches the candidates and checks every one of them for every player itself, which gets heavy with many players or in dense areas.

  • When Golbat advertises filters.annotate_for in /api/status (checked every 5 minutes), the main scan uses /api/pokemon/v3/scan with annotate_for. Personal shiny filtering uses shiny_for clauses. Golbat then rolls the shinies with its own rates, including timezone overrides.
  • If v3 is not available or a v3 request fails, the v2 scan and ReactMap's own check are used.
  • authentication.perms.personalShiny.golbat: false always uses ReactMap's own check.

Config

"api": {
  "playerIdLookup": { "url": "", "apiKey": "" },   // API_PLAYER_ID_LOOKUP_URL / _API_KEY
  "shinyRates": { "url": "", "timezone": "" }      // API_SHINY_RATES_URL / _TIMEZONE
},
"authentication": {
  "perms": {
    "personalShiny": {
      "enabled": true,
      "roles": [],
      // players per role (or single user by Discord / Telegram id), the highest matching max wins, 0 = unlimited
      "limits": [{ "roles": ["<role id>"], "max": 2 }],
      "defaultMax": 1,       // users without a matching role, 0 = none
      "golbat": true,        // use Golbat's personal shiny support when advertised
      "changeLimit": { "changes": 1, "hours": 24 }   // 0 changes = no limit
    }
  }
}

Backgrounds

  • With background rates from the rates service (or Golbat's background_for), Pokémon with a special background for the user are marked: the background picture is shown behind them, like research rewards, and the popup says who it is for. The background draw follows the shiny draw, as in Golbat#423.
  • A Background quick select (next to 0 % / 100 %) shows all of them; it only appears while there are backgrounds to predict (events).

Search

  • Shiny and Background search categories list the user's own shinies / backgrounds within the search radius, nearest first. Without a name all of them are listed. Shiny results show the shiny sprite.

Poracle

  • A Personal Shiny switch in the pokemon tracking dialog when Poracle reports capabilities.personalShiny in /health (PoracleNG). It sets the shiny bit of the rule's personal field, and personal shiny rules get a ✨ in the rule list. Other Poracle versions get neither the switch nor the field.
  • Fix: rules created with a bot command have an empty template, so the dialog treated them as new and opened them with the defaults (distance, IV, switches). Saved rules are now recognised by their uid as well, and the command preview leaves out their empty template and any-size value.

Settings sync

  • Logged-in users' settings are synced between browsers through a hidden backup row. Only filters that differ from the defaults are sent, gzipped.

Misc

  • Hidden Pokémon and tappables stay hidden until they despawn.
  • Selectable alert sounds per category, and IV shown in Pokémon notifications.
  • IV percentages are whole numbers everywhere, like the map labels (the popup showed two decimals, search results all of them).

Testing

server/test/personalShiny.test.js checks the draws against Golbat#423's test vectors, the shiny/background draw order, known Dittos, nearby Pokémon and disguises, the seen-shiny rule and the legacy shiny term.

Running in production (Golbat, MariaDB, PoracleNG) with both kinds of Golbat:

  • Regular Golbat: v2 scan with ReactMap's own check.
  • Golbat with Custom Attribution Prefix #423: v3 scan with annotate_for. Its shiny_for matches ReactMap's own check for the same players, and the personal shiny scan of a whole region drops from 29 calls (~2 s, 38 MB) to one (~7 ms, 14 KB).

Predictions were verified in-game: every Pokémon predicted shiny for a player was shiny when encountered. Friend code lookup, the change limit, Poracle linking (including the hourly re-sync), the Poracle Personal Shiny switch and editing command-created rules were tested with real accounts.

🤖 Generated with [Claude Code]

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ⚠️ Failed 2026-10-18T09:07:10.797633Z 20dcf4c Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d10cc156ad

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

'stateTraceLog',
])

export const SETTINGS_SYNC_KEY = 'settings-sync'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Scope sync metadata to the current account

When two sync-enabled users sign into the same browser, this global local-storage key retains the first user's revision; if the second user's server revision is lower, check() treats that server copy as stale and pushes the persisted local state, silently overwriting the second user's synced settings. Include a stable account identifier in the key (or reset the metadata when identity changes).

Useful? React with 👍 / 👎.

Comment thread server/src/services/personalShiny.js Outdated
typeof dbUser.data === 'string'
? JSON.parse(dbUser.data)
: dbUser.data || {}
data.personalShiny = result.players

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve edits made during player-ID lookup

When a user edits or removes players while the external friend-code lookup is still running, result.players was derived from the old list, and this assignment overwrites the newly re-read user's current personalShiny value with that stale list. This can resurrect deleted players and then sync the wrong accounts to Poracle; compare against the current list and merge only still-pending matching friend codes.

Useful? React with 👍 / 👎.

Comment thread server/src/models/Pokemon.js Outdated
Comment on lines +716 to +717
if (found.length < GOLBAT_SPLIT_AT || depth >= MAX_SPLIT_DEPTH) {
return found

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep splitting capped selective Golbat scans

On the Golbat v2 fallback in a dense viewport with selective clauses (for example, one species marked personal-shiny-only), Golbat caps raw candidates before applying the filters, so the filtered response can contain far fewer than 1,500 rows even though it was capped. Treating that short result as complete misses later candidates and therefore real personal shinies; cap detection must not depend only on the filtered result length.

Useful? React with 👍 / 👎.

Comment thread src/features/profile/PersonalShiny.jsx Outdated
container
alignItems="flex-start"
justifyContent="center"
spacing={1}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Remove the unapproved grid spacing

This new spacing={1} prop introduces a grid gap, but repository policy forbids adding spacing-related styles without explicit written user permission; no such permission accompanies this review, so remove it or obtain that permission.

AGENTS.md reference: AGENTS.md:L7-L7

Useful? React with 👍 / 👎.

Comment thread server/src/services/personalShiny.js Outdated
Comment on lines +391 to +394
const webhook =
user?.selectedWebhook &&
user.perms?.webhooks?.includes(user.selectedWebhook) &&
Event?.webhookObj?.[user.selectedWebhook]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Re-link player accounts when the webhook changes

When a user switches from Poracle webhook A to B, this function can act only on the currently selected webhook, while webhookChange in server/src/graphql/resolvers.js:841-855 merely updates the selection. A therefore remains linked and can keep sending alerts, while the user-only lastSynced timestamp can prevent B from being linked for up to an hour; migrate the links on webhook change and track sync per webhook.

Useful? React with 👍 / 👎.

webhooks: [],
scanner: [],
scannerCooldownBypass: [],
personalShinyLimits: personalShinyLimits(['local']),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Derive limits from the local user's status

For a local account whose stored status is used as a role (for example, a donor status with a larger personal-shiny limit), limits are computed only for the literal local role even though getPerms() later evaluates user.status. The configured status-specific limit is therefore ignored and the user receives the fallback maximum (possibly zero); recompute this array with the loaded status as part of the permission plumbing.

AGENTS.md reference: AGENTS.md:L15-L15

Useful? React with 👍 / 👎.

Comment thread server/src/models/Pokemon.js Outdated

// Pokémon set to only shinies come from the personal shiny scan; here they
// only show when the 0% / 100% quick selects match them
const shinyAllowed = !!perms.personalShiny && !args.filters.onlyLegacy

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Ignore shiny-only state when no players are active

After a user pauses/removes every active player, or their maximum is reduced to zero, the UI hides the personal-shiny controls but this flag remains true solely from permission state. The resolver's no-player path still calls getAll(), where persisted personal_shiny_only filters suppress those species, leaving the user with missing Pokémon and no visible way to disable the filter; gate this behavior on active personalShinyPlayers as well.

AGENTS.md reference: AGENTS.md:L15-L15

Useful? React with 👍 / 👎.

Comment on lines +191 to +192
if (result.status === 'failed' || result.status === 'limited') {
return { ...rest, lookup: result.status }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Persist terminal lookup failures

When a previously pending ticket later returns failed or limited during a background retry, these branches return a changed player row without setting resolved; resolvePending() then exits on !result.resolved and never saves the terminal status. The profile remains stuck on “looking up” and the server keeps retrying, so terminal status changes need to signal that the result should be persisted.

Useful? React with 👍 / 👎.

Comment thread src/features/pokemon/PokemonTile.jsx Outdated
shinyFor ? ` – ${shinyFor}` : ''
}`,
'pokemon',
shinyFor ? 'personalShiny' : 'pokemon',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Key notification deduplication by category

When a spawn is first rendered as an ordinary Pokémon and later becomes marked personal-shiny (for example after its friend-code lookup completes), the first notification caches only pkmn.id; this effect reruns with the new category, but sendNotification() drops it as a duplicate. Users with both notification types enabled miss the important shiny alert, so qualify the deduplication key by category while retaining the encounter ID for navigation.

Useful? React with 👍 / 👎.

@skelgaard

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Something went wrong. Try again later by commenting “@codex review”.

Failed to sample tokens
ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@skelgaard
skelgaard force-pushed the personal-shiny branch 6 times, most recently from 9eacbea to e677349 Compare October 17, 2026 11:01
@corbindk

corbindk commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Found a client crash with this branch in production (running f34dea0; still present on e677349).

Symptom: Profile → Permissions throws TypeError: str.charAt is not a function at getProperName (src/utils/strings.js) and the dialog crashes, for any user whose roles match an authentication.perms.personalShiny.limits entry.

Cause: DiscordClient/LocalClient now add personalShinyLimits to req.user.perms as an array of numbers (the matched max values). UserPermissions renders every perm key as a card, and PermCard maps array perms through getProperName(item), which assumes strings.

Fix we deployed: hide it from the Permissions page, since it's an internal value rather than a user-facing permission:

-export const ALWAYS_EXCLUDED = new Set(['donor', 'blockedGuildNames', 'admin'])
+export const ALWAYS_EXCLUDED = new Set([
+  'donor',
+  'blockedGuildNames',
+  'admin',
+  'personalShinyLimits',
+])

(src/assets/constants.js). An alternative would be to keep personalShinyLimits off perms entirely and only expose the computed personalShinyMax.

🤖 Generated with Claude Code

@skelgaard

Copy link
Copy Markdown
Author

Thanks for the report and the fix! Applied the same change in the latest push: personalShinyLimits is now in ALWAYS_EXCLUDED, so the Permissions page no longer tries to render it. Pull the branch again to get it.

@skelgaard

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Something went wrong. Try again later by commenting “@codex review”.

Failed to sample tokens
ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@Pogodenhelder

Copy link
Copy Markdown
fault How to overcome this one, I know that there stads shiny change 🚫 but still some people will not see this and still go to unsupported/unreleased shinies

@skelgaard

Copy link
Copy Markdown
Author

Thanks! That was ReactMap's own rates without a rates service: a species never seen shiny was assumed shiny-capable at 1/512 until enough checks ruled it out. Fixed in the latest push: without api.shinyRates.url, only species/forms that have been seen shiny in Golbat's shiny stats are predicted. With a rates service its unsupportedPokemon list already covers this

@Pogodenhelder

Copy link
Copy Markdown

yeah I was on a little bit older pull, so pulled just again and will test again if it happens.
thanks for the quick reply @skelgaard

@rpotpogo

Copy link
Copy Markdown

I had hit the limit of 150 lookups for https://playerid.mew.is/ earlier today. It got reset now, but I still see "Too many lookups today, it is tried again later" in reactmap. What happens to the accounts in this state? Does the retry happen automatically? If so, when?

@ChristianATh

Copy link
Copy Markdown

The feature works well, especially the multi-account and multi-profile support. Thanks!

I found one config quirk: authentication.perms.personalShiny.roles accepts role aliases, but authentication.perms.personalShiny.limits[].roles only matches raw role IDs and does not resolve aliases from authentication.aliases.

For example:

"personalShiny": {
  "enabled": true,
  "trialPeriodEligible": false,
  "roles": ["Shiny1", "Shiny2", "Shiny3"],
  "limits": [
    { "roles": ["2222222222333333333"], "max": 2 },
    { "roles": ["3333333333444444444"], "max": 3 }
  ],
  "defaultMax": 1,
  "golbat": true,
  "changeLimit": { "changes": 2, "hours": 720 }
}

The Shiny IV slider already works well. The issue is that enabling Only Personal Shinies currently seems to remove or override the other active filters.

Perhaps Only Personal Shinies be combined with the active filters using AND? The intended behavior is:
Personal Shiny AND (all currently active filters, using their existing logic)

This should preserve the active filters in Main, Extra, and Select while limiting the results to personal shinies.

@skelgaard

Copy link
Copy Markdown
Author

@rpotpogo Yes, it's retried automatically. When the lookup service answers "too many", ReactMap pauses all lookups for an hour. After that, a player still waiting (the "Too many lookups today" note) is looked up again on that user's next map request, and the note goes away once the player ID is found. Saving the profile again also asks again right away. The pause only lives in memory, so restarting ReactMap clears it as well.

With the next push the profile also refreshes itself every 30 s while a lookup is pending, so you don't need to reload to see the ID.

@ChristianATh Thanks, glad it works well!

Aliases in personalShiny.limits[].roles: good catch, fixed in the next push. They're now resolved like the other role lists.

Only Personal Shinies is meant to be exactly that: your personal shinies AND every condition your filters set (IV, Shiny IV, level, CP, gender, size, PvP; PvP leagues are OR'd among themselves and XXS/XXL work like on the map). With nothing set it shows all of them. The Pokémon selected in "Select" only limit it while "link global and advanced" is on; otherwise every species is included. Could you give an example of a filter that seems ignored (the filter and a Pokémon that showed although it shouldn't)? Then I can reproduce it.

@ChristianATh

ChristianATh commented Oct 19, 2026 •

Copy link
Copy Markdown

Please note I had earlier version, I will update it later on.
As per the image, Only Personal Shinies are on, showing lower 40% IV even tho the Main IV Range is 70-100% (Purposely didn't use Shiny IV slider). And Extra stats are all 1-6, which contradicts with that shundo overthere xD.

Only Persona Shinies vs filter 2 Only Persona Shinies vs filter 1 ----- Edit2: Using larger shiny sample, Select Electrike with Only Personal Shinies didn't filter out the other species, but works if I do Shiny IV x Select. I guess this is the intended approach? image image

Edit3: To my understanding, it's hard to filter (especially) the shiny PvPs, since it's slider are orange I guess. Hmm tough one. Picture are seconds apart, this sample I'm making sure there are PvP IV on the filter that is shiny. But upon Only Personal Shinies = ON, it "overrides" all filter and show all shiny even tho there are matching PvP filter. I expecting it will work as "PvP Filter AND Shiny"
image
image

@Pogodenhelder

Pogodenhelder commented Oct 19, 2026 •

Copy link
Copy Markdown

Feature request to be included in this PR.

Reactmap has an option to post in a discord channel when somebody logs in what kind of options he or she has like showing only gyms or Pokemons or both
Now it would be handy so see that the user has personal shiny as well.
below a snippet on how it looks.

Scherm­afbeelding 2026-10-19 om 11 08 53

the reason I ask this feature is because I have now a user that somehow has all the rights to have it, he used it for a day and now suddenly he doesn't has the option to use personal shiny anymore.
We looked everywhere and can't find out why this happens, he has the roles even in react map db I can see he has his players linked still he has not the option.
While he had it yesterday..

edit:
Went into the session table searched up his discordname

"personalShiny":false,

so somehow it was false changed it to true and it works again..dont know for how long.
But it might be that sometimes it gets not synced.

@Pogodenhelder

Pogodenhelder commented Oct 19, 2026 •

Copy link
Copy Markdown

in regards to my previous post
got another person with the same after session ends and a new session is created it seems that personalShiny":false is always the case.

Since I changed the secret of react map as a test everybody needed to re-auth but people then get as it seems personalShiny":false and it won't look in discord if you got the correct role.

i had to change personalShiny":false to personalShiny":true in the DB of that person to fix it

note: this is for as far as I can see only the case with "normal" users I as admin am NOT affected by this "yet"

@skelgaard

Copy link
Copy Markdown
Author

@ChristianATh Thanks for the detailed examples! That's by design: with Only Personal Shinies the regular IV range is switched off and the Shiny IV range takes its place (otherwise a 70-100 IV filter would hide almost every shiny). All other filters still apply, with two things to know:

  • A filter left at its default doesn't narrow anything. The PvP ranks default to 1-10, and counting those would hide almost all shinies, so set your own PvP range for it to apply.
  • The Pokémon picked in "Select" only limit it while "link global and advanced" is on, same as on the map.
    You're also on an older version; please update (git fetch origin personal-shiny && git reset --hard origin/personal-shiny, rebuild, docker compose up -d --force-recreate reactmap) and tell me if a non-default filter still seems ignored, with that filter and the Pokémon that showed.

@Pogodenhelder Thanks, added "Personal Shiny" to the login log (next push), so you can see what the user got.

Perms (personalShiny like iv, pvp, …) are only worked out at login, from the user's Discord roles at that moment, and stored in the session; that's ReactMap's normal role check, the same for every perm. So a re-login giving false means ReactMap didn't see a matching role then. Could you check:

  • Do the same users get the other role-based perms (iv, pvp) at that login? If those are false too, the bot couldn't read their roles (bot in the server, "Server Members" intent on).
  • Is the role in authentication.perms.personalShiny.roles the role id (or an alias defined in authentication.aliases)?
    Editing the session only lasts until the next login.
    pushing it now

Personal shiny
- users save one or more named players in their profile, by player id or,
  with a PlayerID lookup service (api.playerIdLookup), by friend code;
  Pokémon that will be shiny for any of them are marked (sprite, glow,
  label, popup)
- prediction uses the encounter id + player id roll; rates come from a
  rates service (api.shinyRates.url) when set (with its fallback for
  unlisted variants and its overrides for the map's timezone,
  api.shinyRates.timezone), else they are estimated from pokemon_shiny_stats;
  known Dittos are shiny on the third draw at 1/64
- special backgrounds: the rates service's background rates (or Golbat's
  background_for) mark Pokémon with a background for the user, shown with
  the background picture; a Background quick select shows all of them while
  there are backgrounds to predict
- Shiny and Background search categories list the user's own shinies and
  backgrounds nearby, by name or all of them (with shiny sprites)
- a Golbat that rolls personal shinies itself (annotate_for in /api/status)
  marks and filters them through the v3 scan, with the v2 scan and own
  checks as fallback; authentication.perms.personalShiny.golbat turns it off
- Shiny IV is a filter range like the others: shinies within it that match
  the filter's other conditions are shown next to the regular matches
  (0-100 sets no condition, like the other ranges); Only Shiny shows just
  the shinies of a Pokémon
- Only Personal Shinies shows all of the user's shinies, narrowed down by
  every condition the filters set (shiny IV, PvP, gender, size, stats)
- per-role limits (authentication.perms.personalShiny.limits/defaultMax, a
  limit's roles can also name single users by their Discord / Telegram id), a
  configurable limit on swapping players (changeLimit), players can be
  paused without deleting them
- a Personal Shiny switch in the Poracle pokemon dialog when Poracle
  reports per-player shiny tracking in /health
- players are linked in Poracle when a webhook is selected; zoomed out
  scans are split so every Pokémon in view is checked despite Golbat's
  result cap
- notification + sound for personal shinies, IV in map notifications

Settings sync
- logged in users' settings are synced between browsers through a hidden
  backup row; only filters that differ from the defaults are sent, gzipped
- saves are conditional on the revision they are based on (per tab), resets
  and loaded backups are synced instead of overwritten

Misc
- persistent hide, per-category alert sounds
- IV percentages are whole numbers everywhere (popup, search)
- Poracle rules made with a command (empty template) open with their own
  values in the dialog instead of the defaults, and the command preview
  leaves out their empty template and any-size value

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Pogodenhelder

Copy link
Copy Markdown

@skelgaard
Checked and they got everything, seeing Pokemon and gods know what else.
but they don't have the personalshiny anymore.
I have to go into the db in the session to fix it and set to true then it works again.

I checked the discord role id and its the same in discord as in the config.
again this worked before it broke for me after yesterday..

how to reproduce: have a working user, change in react map the secret and then you get logged out and then it happens.
maybe others can test it..

@skelgaard

Copy link
Copy Markdown
Author

@Pogodenhelder I can't find anything in the branch that sets personalShiny to false: perms are only worked out at login, by ReactMap's own role check (unchanged by this PR), and nothing changes them during a session.

My guess: what your users still get comes from alwaysEnabledPerms, which only needs them to be in the server, while personalShiny is the one that needs a role. If the bot can't read the members' roles, that's exactly the result, and the old sessions from before the secret change still had the perms from when it could. You're not affected because allowedUsers gets every perm without looking at roles. To check:

  • Is there a perm that only a role gives (not in alwaysEnabledPerms), e.g. give iv only to that role? Is it false too after a fresh login?
  • Any errors in ReactMap's log at login about fetching roles/members?
  • In the Discord developer portal, is "Server Members Intent" on for the bot, and is the bot still in the server?
    The login log now also shows "Personal Shiny: ✅/❌ (max n)", so you can see it right away after a login.

I ran the Discord login code with a test user: with the role, personalShiny comes out true; when the bot can't read the user's roles, personalShiny is false while everything from alwaysEnabledPerms stays true, which is exactly what you see.

@Pogodenhelder

Pogodenhelder commented Oct 19, 2026 •

Copy link
Copy Markdown

Is there a perm that only a role gives (not in alwaysEnabledPerms), e.g. give iv only to that role? Is it false too after a fresh login?

no the only thing that is false is the personal shiny part.

Any errors in ReactMap's log at login about fetching roles/members?
nothing.. just normal as always

In the Discord developer portal, is "Server Members Intent" on for the bot, and is the bot still in the server?
The login log now also shows "Personal Shiny: ✅/❌ (max n)", so you can see it right away after a login.

true and all my users with that role doesn't have it anymore.

so I tried the following,
people that had person shiny:true in session in db where seeing it..

I changed the session token in config so that ALL sessions are invalid again, and boom everybody looses it again.
The bot is in my discord so it should see the one and only role that is attached to this function.

@Pogodenhelder

Pogodenhelder commented Oct 19, 2026 •

Copy link
Copy Markdown

to be specific on your first question

Is there a perm that only a role gives (not in alwaysEnabledPerms), e.g. give iv only to that role? Is it false too after a fresh login?

I added the show devices to that role, people login and see that in admin panel.
So in short that works and that means that the role is seen as such by react map.
However they can't see the shiny section.
Unless I changed that in the session in the database to true

edit: found out that moving the block in the config to another place and reconfigure it did work.
seems it was on my end all the time , thanks for your patience @skelgaard

@skelgaard

Copy link
Copy Markdown
Author

@Pogodenhelder Glad you found it, thanks for letting me know!

@roynuj

roynuj commented Oct 20, 2026

Copy link
Copy Markdown

Thank you for adding this functionality to ReactMap! The server I use deployed this PR over the weekend and I ran into some user experience issues that you may want to consider resolving before merging:

  • Shinies aren't treated as a separate set of pokemon, which causes several problems:
    a) You can't show any gender hundo Combee, but only female shiny Combee. The server already builds a shiny copy of each filter with the IV range swapped for the Shiny IV range, but the UI makes shinies share every other setting with non-shinies.
    b) The per-pokemon "Only Shiny" toggle requires setting a shiny IV range and disables the non-shiny IV range. Setting a range excludes either shundos (0-99%) or shnundos (1-100%). It also excludes shinies without an IV reading.
    c) There's no Global Quick Select for shinies like there is for hundos and nundos. Today the only way to see every species' shinies alongside other pokemon you've selected is the global Shiny IV range with Global Respects Selected off, which also applies the global filter's other conditions to every species. The hundo and nundo quick selects aren't affected by Global Respects Selected.
    A better approach would be to list shinies as a separate set of pokemon with their own settings, including 0% and 100% Quick Selects. "Exclude" from a pokemon on the map could then exclude only shinies of that pokemon and Select All/search could target shinies. A shiny version of the global filter with its own "Shiny Respects Selected" would let users see every species' shinies without changing how the global filter applies to non-shinies. This would eliminate the need for "Only Personal Shinies", too.
  • Shinies unexpectedly ignore the "Minimum Circle IV" setting and always show IV% if IV Indicator is enabled. If there's a desire to always show IV Indicators (for nundos and/or) shinies, it should be a different setting.
  • Trainer names are displayed in labels below every shiny when more than one trainer is active and clog up the map. It would be nice to have a way to provide a nickname, emoji, icon, colored circle, etc. to differentiate between users without clogging up the map.
  • There's a shiny icon in the label below the pokemon and a shiny icon in the lower left corner of the sprite. The one on the sprite is hard to see. Could the shiny icon only be in the label and only when glow is disabled (since the glow is already an indicator). This will also help reduce the label length when multiple trainers are active.
  • The shundo and shnundo glows (half gold, half the hundo/nundo color) can be hard to distinguish from overlapping shinies and hundos/nundos on busy maps. I'd recommend using a different color for each. Purple like feat(pokemon): personal shiny annotation and filtering via Golbat v3 … #1267 uses for shundos stood out pretty well in my testing. The server I use has black for nundos, so I applied cyan for shnundos using a userscript and it worked well.
  • The "Enter your friend code, the player ID behind it is looked up for you." text in Profile Settings overlaps the text input field labels.

jfberry and others added 2 commits October 19, 2026 21:25
…Map#1258)

* feat(golbat): discover instance capabilities from /api/status

Add a GolbatCapabilities service that reads GET /api/status from every
configured Golbat endpoint and keeps { features, limits, filters } per
instance, keyed by the endpoint base url. Discovery runs from
DbManager.getDbContext (startup and config reload), refreshes every five
minutes, and rechecks an instance immediately, debounced to once per
30 s, when a scanner call answers 400/422 — the first sign of a
downgrade. A 404 or non-JSON body means an older Golbat with no
capabilities; any other failure keeps the last good result.

Pokestop.getAvailable now decides showcase focus support from
supportsFilter(mem, 'showcase_focus') when the status route advertises
filters, and only falls back to the deprecated per-response
showcase_focus_filter flag for a build that does not. The hard error on
an unsupported build is unchanged; only the source of truth moves. The
header builder shared by the evaluator and the service is extracted
into scannerHeaders.js.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016611GTrQ2W8WcznKrLQnMv

* fix(pokestop): re-read Golbat status without the recheck debounce

The unsupported showcase-focus verdict in Pokestop.getAvailable called
the debounced recheck(), which shares its 30 s floor with the periodic
status fetch. A Golbat upgrade landing inside that window left the
availability pass throwing until the next one. Use refresh() there: it
is still single-flight, and the path is already throttled by the
availability refresh window. The debounce stays on the hot-path recheck
triggered by 4xx scanner responses.

The replaced test now drives the real registry with a fake transport so
it exercises the debounce instead of a mocked method.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016611GTrQ2W8WcznKrLQnMv

* fix(golbat): harden capability discovery per review

- Keep every distinct credential set configured for a shared endpoint url
  and fall through to the next on 401/403, remembering the set that works.
- Read the status body outside the JSON parse guard so a stalled or reset
  body keeps the last good capabilities instead of recording a legacy result.
- Force at most one status refresh per availability payload when showcase
  focus is unsupported, so failing session inits no longer re-read the
  status on every page load.
- Resolve rechecks against the registered endpoint url instead of splitting
  at the first /api/, so endpoints configured with a path still recheck.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
# [1.54.0](WatWowMap/ReactMap@v1.53.2...v1.54.0) (2026-10-20)

### Features

* **golbat:** discover instance capabilities from /api/status ([WatWowMap#1258](WatWowMap#1258)) ([c83f93f](WatWowMap@c83f93f))
- "Personal Shinies" in the drawer replaces "Only Personal Shinies":
  - Off: no shinies are added; the Shiny IV ranges and Only Shiny can't
    be set and are ignored (Pokémon the filters show are still marked)
  - Include: the map as usual plus the user's shinies within the Shiny IV
    range (the Pokémon's own, or else the global one; 0-100 = all)
  - Only: only the user's shinies, as before; the IV range is disabled,
    as the Shiny IV range takes over
- Options to always show the IV of personal shinies, the player names and
  the sparkle, each on by default
- Half / Half Glow: with two glows, both colours split in half instead of
  the "Multiple" colour (hidden while it is on)
- The legacy filter knows a "shiny" term
- Tests against Golbat WatWowMap#423's test vectors (draws, Ditto, disguises,
  the seen-shiny rule, the legacy shiny term)
- The Hundo / Nundo glows are labelled "100% IV" / "0% IV"

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@skelgaard

Copy link
Copy Markdown
Author

@roynuj Thanks a lot for testing it this thoroughly, that's really helpful! The latest commits address most of it:

Shinies as a separate set (1a–c): keeping them part of the regular filters is by design. A shiny is the same Pokémon with the same filter, plus a shiny condition, and that keeps the filters and the UI simple. The cases you describe are now covered by a new Personal Shinies mode in the drawer (Off / Include / Only), which replaces "Only Personal Shinies":

  • Include is the global "show my shinies" you were missing: the map as usual, plus all of your shinies within the Shiny IV range (a Pokémon's own range, otherwise the global one), independent of the other filters and of Global Respects Selected. A Shiny IV range of 0-100 means every shiny, including shundos, shnundos and shinies without an IV reading.
  • Only shows only your shinies, filtered by the shiny IV and the other conditions.
  • Off turns it off. The Shiny IV ranges and Only Shiny are then disabled.

IV on shinies / Minimum Circle IV: there's now an Always Show IV of Personal Shinies setting. Turn it off and shinies follow your normal IV settings.

Trainer names: Player Names on Personal Shinies can be turned off. You then only get the ✨, and the popup still says who the shiny is for.

Two shiny icons: Sparkle on Personal Shinies turns off the sparkle on the sprite.

Shundo / shnundo glows: I'd like to keep half/half as the default, since it shows both things at once. There's now a Half / Half Glow setting. Turn it off and Pokémon with more than one glow use the "Multiple" colour, which you can set to e.g. purple.

Friend code text overlapping the input: fixed.

Updating: the branch now gets regular commits instead of force-pushes, so git pull works from here on. If it was cloned before today, run git fetch && git reset --hard origin/personal-shiny once.

skelgaard and others added 2 commits October 21, 2026 10:14
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…port

The own /api/status fetch every 5 minutes is replaced by the capability
registry of WatWowMap#1258 (`supportsFilter(mem, 'annotate_for')`). A Golbat that
refuses personal scans (e.g. its rates aren't loaded) is still skipped for a
minute, and its status is now re-read right away in case it was downgraded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ChristianATh

ChristianATh commented Oct 22, 2026 •

Copy link
Copy Markdown

The Shiny ONLY not as I think it is? Somehow it didn't work as Only shows only your shinies, filtered by the shiny IV and the other conditions

Shiny OFF, Great 1-50
image

Shiny ONLY, Great 1-50
image

Zooming out
Shiny OFF, Great 1-50, about 4 shiny
image

Shiny ONLY, Great 1-50, shows all shiny instead
image

Edit1: It specifically blunder if paired with PvP Rank filter. Extra / Select works as intended

Shiny ONLY, no filter
image

Shiny ONLY, Level 27+
image

Shiny ONLY, IV floor 9/9/9
image

Shiny ONLY, IV floor 9/9/9, Starly family
image

With all PvP ranks shown the built PvP list isn't narrowed down to the
filter's ranks, so in only mode every personal shiny with any PvP rank
passed a filter set to e.g. great league 1-50. Check the parsed ranks
against the filter's own range instead, with the same mega and level cap
conditions as the map.

The shiny filter helpers move to filters/pokemon/personalShiny.js so they
can be tested without a database.

Co-Authored-By: Claude Opus 5.5 <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.

8 participants