StationpediaAscendedInternals¶
Reverse-engineered internals of the third-party Stationpedia Ascended mod, as they pertain to an integrating mod: its Harmony patch list, what it deliberately does NOT patch, the ChangeDisplay postfix flow, microwave-device coverage, the tooltip resolution chain, the SearchPatches state and hide-policy, its own link-handler inventory, and the 100ms tooltip polling coroutine.
Stationpedia Ascended Harmony patches full list + what it does NOT patch¶
All imperative _harmony.Patch(...), default Priority.Normal, no priority attributes anywhere in StationpediaAscended's codebase.
| Target | Kind |
|---|---|
UniversalPage.ChangeDisplay |
Postfix |
UniversalPage.PopulateLogicSlotInserts |
Postfix (cosmetic slot compaction) |
Stationpedia.OnDrag |
Prefix |
Stationpedia.OnBeginDrag |
Prefix |
Stationpedia.ClearPreviousSearch |
Postfix (UI state only, not filtering) |
Stationpedia.SetPage |
Prefix (custom key navigation) |
Stationpedia.SetPageGuides |
Postfix |
Stationpedia.SetPageLore |
Prefix + Postfix |
KeyManager.SetupKeyBindings |
Postfix |
KeyManager.GetButtonDown |
Prefix |
Stationpedia Ascended does NOT patch:
- Stationpedia.PopulateLogicVariables
- Stationpedia.PopulateThingPages
- Stationpedia.Register
- StationpediaPage.IsRegexMatch (our primary search-filter target)
- Stationpedia.DoSearch
- Stationpedia.PopulateGuideLoreContents
- page.LogicInsert, PopulateLogicInserts
So our three primary hooks and one optional secondary hook all land on methods Stationpedia Ascended does not touch, except UniversalPage.ChangeDisplay, where we coordinate via [HarmonyAfter].
Stationpedia Ascended ChangeDisplay postfix flow¶
StationpediaAscended's ChangeDisplay_Postfix at HarmonyPatches.cs:111: 1) Null-guards. 2) Destroys its own prior children by name: GuideSectionsContent, SurvivalManualContent, OperationalDetailsCategory. 3) Dispatches special pages (SurvivalManual, custom JSON guides, etc.). 4) DeviceDatabase.TryGetValue(pageKey, out descriptionEntry); early return on miss. 5) Applies pageDescription / Prepend / Append if set. 6) Creates OperationalDetailsCategory at sibling index 20 if entry has non-empty operationalDetails.
Stationpedia Ascended microwave coverage¶
From shipped descriptions.json: ThingStructurePowerTransmitter (line 24385), empty operationalDetails: []; logicDescriptions covers vanilla LogicTypes only. ThingStructurePowerTransmitterReceiver (line 24524), same. ThingStructurePowerTransmitterOmni (line 24482), same. None of PowerTransmitterPlus's six custom LogicType names appear in StationpediaAscended's JSON. Without StationpediaAscendedBridge enrichment, Stationpedia Ascended users hovering custom rows see "No detailed description available yet."
Stationpedia Ascended LoadDescriptions synchronous readiness¶
The StationpediaAscended DeviceDatabase is populated synchronously by LoadDescriptions inside the mod's Awake. By the time any BepInEx OnAllModsLoaded callback fires, the database is ready.
Stationpedia Ascended tooltip flow + resolution chain¶
A 100ms polling coroutine watches Stationpedia.CurrentPageKey. On page change, iterates every SPDALogic component under LogicContents.Contents and attaches SPDALogicTooltip. Resolution chain:
DeviceDatabase[deviceKey].logicDescriptions[cleanName]GenericDescriptions.logic[cleanName]GenericDescriptionsData.AdditionalData[cleanName]- Placeholder: "No detailed description available yet."
CleanName strips <[^>]+> tags and trims. No case normalization. So our LogicName column stripping leaves the bare enum name as the lookup key.
Our TextElementFactory produces raw TMP elements (not SPDALogic), so StationpediaAscended's coroutine ignores our custom content in extension sections. That's the intended behavior.
Stationpedia Ascended AdditionalData tooltip fallback (JsonExtensionData)¶
Tooltip resolution chain step 3 is GenericDescriptionsData.AdditionalData[cleanName], a [JsonExtensionData] catch-all dictionary that Newtonsoft.Json uses for unknown JSON properties deserialized into GenericDescriptionsData. Stationpedia Ascended uses it as a dynamic bucket for community-authored descriptions not captured by the typed schema. Not relevant to StationpediaAscendedBridge flow (writes to the typed logicDescriptions dict, tier 1).
Stationpedia Ascended GenericDescriptions fallback property¶
StationpediaAscendedMod.GenericDescriptions is a public static GenericDescriptionsData property populated from descriptions.json's genericDescriptions entry. Used as fallback tier 2 in tooltip resolution. StationpediaAscendedBridge does NOT write to it; it targets per-device logicDescriptions only.
Stationpedia Ascended destroys prior children by name (distinct-name convention)¶
StationpediaAscended's ChangeDisplay_Postfix destroys any pre-existing children of UniversalPage.Content named OperationalDetailsCategory, GuideSectionsContent, or SurvivalManualContent before re-creating its own. Our <ModName>Details GameObjects are NOT in that destroy list, so that postfix leaves them alone. If we named ours OperationalDetailsCategory, Stationpedia Ascended would destroy it on every navigation.
Stationpedia Ascended row-iteration prefab filter (SPDALogic only)¶
StationpediaAscended's AddTooltipsToCategory specifically filters for SPDALogic component instances under LogicContents.Contents. Our TextElementFactory.Create produces raw TextMeshProUGUI GameObjects (not SPDALogic), so StationpediaAscended's coroutine skips them. Stationpedia Ascended only decorates vanilla-populated rows, which includes our custom LogicType rows (those ARE SPDALogic instances produced by vanilla PopulateLogicInserts). Net effect: Stationpedia Ascended tooltips appear on our native custom rows (after StationpediaAscendedBridge), Stationpedia Ascended ignores our extension section's text elements.
Stationpedia Ascended SearchPatches caches + ShouldHideFromSearch policy¶
StationpediaAscended's SearchPatches class (at StationpediaAscended.Patches\SearchPatches.cs) owns the following static state:
_pageTitleIndex/_pageWordIndex, inverted indexes from words/titles toStationpediaPagereferences. Built once byBuildPageIndexeson first need, rebuilt on explicit invalidation._hideFromSearchCache, memoized bool per page computed byShouldHideFromSearch._lastSearchText,_lastResultCount, used to skip redundant reorganizations when the visible result count hasn't changed.
Key methods:
BuildPageIndexes, scansStationpedia.StationpediaPages, skips pages failingShouldHideFromSearch, populates the two indexes.ShouldHideFromSearch(StationpediaPage page), Stationpedia Ascended's own hide policy. Based on name patterns likeRuptured,Burnt,Wreckage. Returnstrue(hide) for pages matching those patterns. This is Stationpedia Ascended's opinion; our mod's hidden keys are NOT in this set.ReorganizeSearchResults, runs after vanillaDoSearchpopulates its pool; adds Stationpedia Ascended's category headers to the search UI, may inject missing matches from its own indexes viaFindMissingMatches/InjectMissingResults.ClearPreviousSearch_Postfix, patches vanillaStationpedia.ClearPreviousSearchto take care of Stationpedia Ascended's own category headers and to lazily register listeners onSearchField/ build indexes.
Risk surface for our hidden pages: if StationpediaAscended's title/word indexes contain our PowerTransmitterPlus_* keys AND StationpediaAscended's FindMissingMatches decides a query matches them, Stationpedia Ascended could inject them as "missing results" even after our IsRegexMatch postfix returned false. Mitigated in practice because Stationpedia Ascended builds its indexes via BuildPageIndexes which skips pages failing ShouldHideFromSearch, our pages DO pass ShouldHideFromSearch (Stationpedia Ascended doesn't know about our mod), so they ARE in StationpediaAscended's indexes. Whether Stationpedia Ascended re-injects them depends on whether the typed query's regex happens to match our page titles or keys. For PowerTransmitterPlus_... keys that's unlikely (players rarely type the full prefix), but flagged as open item O4 (§17) for runtime test verification.
Escape hatch if observed: soft-reflective postfix on StationpediaAscended's ShouldHideFromSearch to return true when the page's key is in our HiddenKeys set. Implement only if runtime test T5 shows re-injection.
Stationpedia Ascended UI-side click handlers + reason to ship our own¶
Stationpedia Ascended ships two of its own IPointerClickHandler MonoBehaviours in StationpediaAscended.UI\:
TocLinkHandler, attached to dynamic TMP elements Stationpedia Ascended creates inside its operational-detail sections. Handlestoc_*link clicks (its own Table-of-Contents anchor system) AND falls through toStationpedia.Instance.SetPage(linkID, true)for standard links. Includes hover-color feedback via vertex colors.CategoryHeaderHandler, attached to Stationpedia Ascended's search-result category headers for click-to-expand behavior on the Search page.
We do NOT reuse TocLinkHandler even though our use case is similar because:
- Reuse would require reflection attach of a StationpediaAscended type, which creates a soft dependency on StationpediaAscended's assembly (against our architecture, §2).
- StationpediaAscended's handler understands toc_* links we don't emit (harmless but unnecessary code path).
Our SixFive7LinkHandler (§8.5) is the click-only equivalent without a StationpediaAscended dependency. It lacks hover-color feedback, compensated by the mandatory click-phrasing authoring rule (§11.3).
Stationpedia Ascended tooltip coroutine timing¶
MonitorStationpediaCoroutine (StationpediaAscended StationpediaAscendedMod.cs) polls Stationpedia.CurrentPageKey at a 100ms interval. On a page-change detection, it schedules AddTooltipsAfterDelay via a 2-frame delay (Unity coroutine yield return null twice) to let vanilla UniversalPage.ChangeDisplay finish populating LogicContents.Contents before tooltip attachment iterates the rendered children.
Tooltip flow per detected page change:
1. Wait 2 frames.
2. Call AddTooltipsToCategory(universalPageRef.LogicContents, pageKey, "Logic").
3. Iterate transforms under LogicContents.Contents; for each, get its SPDALogic component; if non-null and no existing SPDALogicTooltip, attach a new SPDALogicTooltip MonoBehaviour seeded with pageKey, component.InfoValue.text (the rendered row name; tag-stripped by CleanLogicTypeName), and "Logic" as the category name.
Implication: our custom LogicType rows (which render as SPDALogic prefab instances inside the vanilla Logic Variables category) are automatically decorated by StationpediaAscended's coroutine regardless of whether StationpediaAscendedBridge ran. StationpediaAscendedBridge supplies the tooltip TEXT content via DeviceDatabase enrichment; without StationpediaAscendedBridge, Stationpedia Ascended's tooltip shows the "No detailed description available yet." placeholder. Decision 16 structural enforcement ensures StationpediaAscendedBridge runs every time.
Verification history¶
- 2026-04-20: page created from the Research migration; verbatim content lifted from F0212, F0214, F0215, F0216, F0219m, F0219n, F0219af, F0219o, F0219p, F0219q, F0219r, and F0248 (primary for tooltip-timing; F0219w merges per MigrationMap §5.1).
Open questions¶
None at creation.