{"id":14011,"date":"2024-06-07T10:41:17","date_gmt":"2024-06-07T09:41:17","guid":{"rendered":"https:\/\/weatherfactory.biz\/?p=14011"},"modified":"2024-06-19T12:12:53","modified_gmt":"2024-06-19T11:12:53","slug":"the-long-and-the-devil","status":"publish","type":"post","link":"https:\/\/weatherfactory.biz\/the-long-and-the-devil\/","title":{"rendered":"The Long and the Devil"},"content":{"rendered":"

WARNING: This post contains the kind of technical detail you either like or don’t… but a lot of folk tell us they find these inside glimpses interesting.<\/strong><\/p>\n

“I am not sure whether the game is still being supported,” said a recent help ticket about Cultist Simulator<\/a>. It is, and in fact we keep <\/em>Chelnoque (who also helped with BOOK OF HOURS code) on retainer specifically to fix issues with CS. After six years it’s pretty stable… but that sometimes means that where bugs still exist, they’re <\/em>weird<\/strong>.\u00a0Fortunately Chel is a dogged pursuer. Here’s his recent after-action report on one particularly stubborn blemish, as a guest post.<\/em><\/p>\n

==<\/p>\n

You may remember a CS<\/em> issue that still gets reported occasionally, with followers losing follower aspect in Apostles after being injured by an enemy Long – I was finally able to track it down (almost two years since the first report). The root cause for this one is localization (of course it is), so might be somewhat relevant for BoH<\/em>.<\/p>\n

tl;dr version (spoils some revelations): if you have more than 1 mutation in an internal recipe, any loc will break it unless you apply my fix||<\/p>\n

Actual repro steps turned out to be simple – on any language other than English, the recipe long.executestrategy.injurefollower.defencefailed<\/code> doesn’t mutate follower_wound correctly. (wanted to brag that by now I am able to write this recipe’s id by memory but apparenty I still can’t)<\/p>\n

Normally, it does two mutations –\u00a0follower_wound: 1<\/code>,\u00a0follower: -1<\/code>. But with loc,\u00a0follower_wound<\/code>\u00a0mutation id gets replaced with\u00a0follower<\/code>\u00a0in this specific recipe. Why?<\/p>\n

Mutations<\/code>\u00a0property is an array. UniqueIdBuilder, when making unique ids\u00a0 (which, in turn, are responsible for making associations between core and loc content) doesn’t care whether something is in array, and gives sub-ids like\u00a0mutations.filter<\/code>\u00a0– as if “filter” was mutation’s own property, but, of course, it’s a sub-property of a mutation object that mutations array contains. So both mutations’ filters in this recipe had an id like (simplified)\u00a0>long.executestrategy.injurefollower.defencefailed.mutations.filter<\/code>, making them essentially the same thing as far as locs are concerned.<\/p>\n

But, “Chelnoque”, you will say (more eloquently probs), “Chelnoque, I distinctively remember having more than one mutation in other recipes too. Why don’t they bug out? And why only in localizations? And mutations isn’t a localizable property, how come loc even affects it?”<\/p>\n

“Why only in localizations” is fairly easy to answer. Unique id plays no role in core content load as the loaded data there is stored in a list, while loc stores all data in a dict (so it can be later found and applied to loaded core data) by unique id. Thus, same unique ids overwrite each other.<\/p>\n

But why only this specific recipe? Well, because it’s internal. The only internal recipe in the game that uses mutations.
\nWhy it is important that it’s internal?
\nIn short, “because localizable sub-entities are localized without a regard to their own localizable properties”.<\/p>\n

For example, recipes have slots. Slots have localizable properties – label, description. But recipes don’t care about that. If there’s a slot defined in a loc, all of its properties will be seen as eligible for localization. Why? Giving the floor back to AK, in comments:<\/p>\n

\/\/Note: this doesn’t work quite in the way I intended it. Label and Description on Slots are marked as Localise, but<\/em>
\n\/\/the attribute is inspected only for the top level entity. Because the top level entity also has Label and Description marked as localise,<\/em>
\n\/\/the slot properties are added to the localisable keys, but this will break if the names are different. Consider explicitly inspecting subproperty attributes<\/em>
\n\/\/to see if they’re also subentities, when loading the data<\/em>
\n\/\/note: “explicitly inspecting subproperty attributes” won’t work in cases like “a normal Recipe defined inside “linked”<\/em>
\n\/\/LinkedRecipeDetails has no explicit properties like Label\/Description, so they can’t be marked as localizable, so they won’t be included here<\/em><\/p>\n

And:<\/p>\n

\/\/There’s another bug here that approximately cancels out the bug described above when scanning for localisable keys.<\/em>
\n\/\/We only check the top-level property, not its sub-properties. So if we’re registering loc data for ‘linked’ (localisable=true)<\/em>
\n\/\/we also register loc data for all its sub properties – eg both ‘startdescription’ (hurray) and ID (minor perf pain)<\/em><\/p>\n

Okay, to sum it up.<\/p>\n