CompatibilityStandardsV2

From The Battle for Wesnoth Wiki

As a piece of software matures, there are often new designs, paradigms, and idioms developed which are superior to old ones. This creates an inherent conflict between the need for progress and the need for compatibility. Wesnoth is no exception. This document describes Wesnoth's approach toward resolving that conflict in a way which is most beneficial to both goals, as well as the rationale behind this approach.

Policy

This policy defines how the creation of new Wesnoth APIs and the deprecation and removal of old ones are to be handled. For the purposes of this document "API" means "any technical channel by which a content creator interacts with the game engine". This includes, but is not limited to: preprocessor macros, WML tags, WFL functions, IPFs, and the Lua API. Note that this policy applies only to software APIs. Core content such as sprites, portraits, animations, lore, etc. are to be updated freely, without consideration for stylistic or literary conflicts that such content changes may present to add-ons.

When to deprecate

Adding a better API

Any time a superior API is introduced which has complete feature-parity with an existing API, the old one should be immediately deprecated. Note that in most cases, in order to be considered to have "complete feature-parity", the API should be available in the same language or area of the code as the obsolete one was. For example, the introduction of a more powerful API in Lua which can accomplish a superset of the functionality which had previously been available with a certain WML tag would not obsolete that WML tag. Exceptions can be made to this rule in cases where it is clear that the feature does not make a lot of sense in its current language or area, and was merely there for legacy reasons (such as the proper place for it not having been introduced yet at the time of its creation). Such exceptions should be determined by developer consensus.

Preventing needed fixes or improvements

In such cases where a feature is preventing an important new feature from being added or makes it impossible to fix a problem impacting players, it can be preferable to deprecate the API to allow for the necessary changes to be made. APIs deprecated for this reason should still follow the deprecation schedule as normal, unless the fix or improvement is urgently needed.

Actively harmful

If an API is found to cause significant problems for players or UMC authors, such as being prone to causing crashes while also very difficult to properly fix, corrupting saves and replays when not used correctly while being difficult to use correctly, or other similar situations, then such APIs should be deprecated and removed regardless of whether there is a replacement available.

Security

If an API is found to have a security flaw that can't be fixed, then it should not be deprecated, it should be immediately removed.

Unused

Any API that is not used in mainline and also is not used by the most recent version of an add-on on the add-ons server of the current or previous stable release can be deprecated. For example, if an add-on for 1.16 uses a deprecated API but the updated version of the add-on for 1.18 does not, then that add-on is not considered as currently using the API. Once deprecated for this reason, a new add-on being uploaded that uses the API is not a reason to undeprecate it.

This does not need to be a passive process where developers simply check the add-ons server for whether an API is used - developers who want to deprecate an API for removal can proactively talk to and work with UMC authors to help update their add-ons to remove usage of said API. This can be anything from talking with them online about how to update to submitting updated code directly (ie: opening a PR against an add-on's public git repository).

Deprecating and then removing APIs that are unused is the most preferred approach since their removal does not have any impact on UMC authors.

When NOT to deprecate

Style

Deprecation should not be done purely for reasons of style. This is very subjective and prone to change as new contributors join and current contributors leave or become less active. As such, allowing deprecation for stylistic reasons would lead to entirely unnecessary work for UMC authors as developer preferences change over time.

Renaming

While there can be exceptions, it is rarely a net positive to deprecate an API simply for the sake of renaming it to something else. It is preferable to either add a second name for the same function, leaving the old name as-is, or simply live with the current name rather than expecting all UMC authors using the API to update to the new name.

Any other reason

Accepted reasons for deprecating APIs should be something that's discussed and agreed upon by the development team while also, ideally, including UMC authors. It should not become the norm that additional reasons to deprecate APIs are treated as exceptions and left as an increasingly forgotten discussion on Discord, IRC, or the forums - they should be added to here with the reasoning behind them.

Deprecation awareness

Conflicting goals

When deprecating APIs there is an inherent conflict in terms of how to make UMC authors aware of the deprecation. After all, if they aren't aware something is deprecated, they can't know they may need to update their add-on. Therefore, deprecations need to be displayed in a place where they will see them and most UMC authors don't look at Wesnoth's logs unless there's some other issue they're investigating. At the same time, deprecation warnings aren't relevant to players and spamming deprecation warnings is not an effective way of communicating what the issues are.

A middle ground

Each deprecated feature should make use of an appropriate deprecation function call for that language or subsystem to ensure that the appropriate deprecation notice is printed to the log output. Additionally, deprecation warnings should be displayed in-game in the following cases:

  • If the player is running a development version, level 3 and level 4 deprecations should be shown in-game by default.
  • If the player enables debug mode then all deprecation warnings should be shown, regardless of whether they're using a stable release or a development release.

Documentation

It is also not enough to only display a warning at runtime when something deprecated is encountered. It is the responsibility of the development team to proactively make UMC authors aware of the deprecations and removals being done. To accomplish this:

  • When an API is deprecated, and again if it's later removed, its deprecation or removal must be documented in the appropriate section of the changelog for the version it was deprecated or removed in.
  • Likewise, it should be added to https://wiki.wesnoth.org/CompatibilityBreakingChanges

Additionally, it is not enough to simply say that an API is deprecated. In the deprecation message itself as well as in the changelog and https://wiki.wesnoth.org/CompatibilityBreakingChanges, a description must be included as to why it was deprecated or removed and how UMC authors can update their add-ons to address it.

Lastly, it should be understood that "removed" doesn't necessarily mean that the API is entirely gone from Wesnoth's codebase. There is no maintenance burden to keeping macro or method stubs that do nothing aside from printing an error message describing what was removed and why. Keeping such stubs around is highly encouraged as it is helpful for UMC authors trying to update very old add-ons to the current version of Wesnoth.

How to deprecate

Every effort should be made to create the simplest possible wrappers which will translate from an obsolete API to the updated one. Such wrappers should ideally be organized into their own compatibility file or module, and set up in such a way that the internals of the updated API will not affect how the old calls get wrapped to the new one. Essentially, the idea is to create a set-it-and-forget-it compatibility wrapper which will continue to work regardless of updates made to the newer API.

Additionally, in all cases where it's practical, the wmllint tool must be updated to be able to automatically handle updating add-ons for anything that's been deprecated except for APIs deprecated at level 1. While developers are still heavily encouraged to add wmllint support for level 1 deprecations, it is not required as these do not show deprecation warnings by default and are expected to continue working indefinitely.

Deprecation levels - When to remove deprecated features

While creating simple compatibility wrappers should be possible most of the time, it would be unreasonable to assume that this approach will be viable in absolutely every case. Wesnoth's compatibility policy therefore has four different levels of deprecation which are used to set expectations for when and if an API is expected to be removed:

  1. Deprecated indefinitely — This deprecation level is for changes which have newer preferred alternatives but, barring any unforeseen issues, have little to no maintenance impact and should be kept indefinitely in order to reduce the work required for UMC authors to maintain their content. For example, functions or attributes which have had their names changed, macros which have been replaced with tags, macro stubs containing only an error message describing what was removed and why, or simple wrappers with little to no maintenance overhead. If future large scale changes make it impractical to maintain an API deprecated at this level then the deprecation level should be raised accordingly. This is generally the preferred deprecation level - higher deprecation levels are for APIs which are intended for eventual removal and must be weighed against the maintenance work this forces upon UMC authors.
  2. Deprecated with the intention of future removal — This deprecation level is for APIs which will be removed in a future version, but the version they are removed in has not yet have been decided. Barring urgent circumstances, all APIs that are deprecated with the intention of being removed must start with being deprecated at level 2 for 1-2 development cycles at minimum depending on the impact of the API's removal on UMC authors. Once the API has spent the necessary amount of time at deprecation level 2, it can be moved to deprecation level 3. It is not allowed to deprecate an API at level 2 and change it to level 3 in the same development cycle. There is no maximum amount of time that an API can remain deprecated at level 2.
  3. Deprecated for removal in a specific version — This deprecation level is for APIs which were previously deprecated at level 2 and now have a future version at which they will be removed. The version an API is removed in must be at least two development cycles after the API was deprecated at level 2. It is not allowed to move an API to deprecation level 3 and then remove it in the same development cycle.
  4. Removed without deprecation — This level should be used EXTREMELY rarely, and only in cases where it is ABSOLUTELY NECESSARY. Occasionally, an update to a feature will change the underlying architecture in such a fundamental way that the old paradigm cannot coexist with the new one no matter how much redundant code one would create. While this kind of scenario is extremely rare, and every effort should be made to find creative solutions to avoid it, there are occasionally cases where it truly is impossible to maintain both methods even in the short term. This level should only be used with broad developer consensus, after the majority of active developers familiar with the feature in question have given at least some thought to trying to deprecate gracefully and failed.

Rationale

The above policy is the result of a large amount of thought, discussion, and debate. Following is a brief outline of the considerations and goals on both sides of the problem, why there is an inherent conflict between them, some failed approaches to resolving the conflict, and how the final policy ultimately maximizes the pursuit of both goals.

The Problem - The paradox of progress

Invariably, as development on any project moves forward, developers will realize that there are better, cleaner, or more elegant ways to structure things than they had been previously. These changes can be to improve efficiency, make an API more intuitive, keep code better organized, make common tasks more straightforward, or accomplish any number of other positive things. These changes can also make the development of additional features much more viable. In short, progress is good.

On the other hand, a content-heavy program such as Wesnoth relies on the ability of content creators to efficiently create, maintain, and update their content. Too many changes all at once will force creators of existing content to spend obscene amounts time updating their creations just to keeping them up-to-date and in working order. This can lead to a high amount of frustration, a drop in motivation, and a decline in content being created. In short, progress is bad.

Backwards Compatibility - benefits and drawbacks

Most of the time, older paradigms can still be maintained in a manner in which they coexist with the newer ones. This allows for existing content to continue functioning, while at the same time allowing and encouraging new content to be created using the newer methods. However, maintaining such code can be problematic in the following ways:

  1. There may eventually be architectural changes a developer would want to make where the old method's square peg no longer fits, even forcibly, into the new method's round hole.
  2. Having compatibility code hanging around may, depending on how it is implemented, mean that any updates made to the feature, module, or subsystem in question would have to made in both the new, cleaner design, and the older, poorly structured one, adding more work for developers.

The Naive Approach - Deprecate and remove everything old

One approach to balancing old and new is to deprecate the old and slate its eventual removal after either a certain amount of time has passed or a certain number of subsequent versions have been released. This sounds good in theory, but in practice, there will be many changes which are minor or cosmetic in nature and which will add up. Things like replacing macros with WML tags, updating the name of an API call or order of parameters to be more consistent with other similar functions, or switching from a functional to an object-oriented structure are very good for organizational purposes, but will result in a large amount of maintenance required on the part of content creators to keep existing code operational, and for very little real gain. This approach invariably leads to the situation where so much is being changed from one version to the next that creators turn into maintainers, forced to spend nearly all of their time trying to stay ahead of the update curve in an attempt to keep their existing content working, and leaving them very little time and motivation to create new content. And of course, by the time they're finished painstakingly updating their existing code for every little change made for the current release, whoops, there's a new release with a whole slew of new changes that need accounting for. It simply becomes unmanageable.

The Naive Approach - Deprecate and remove only when necessary

The opposite approach would be to keep all existing paradigms until they actively interfere with a new architecture or create a double-maintenance problem. While this approach does cut down on the maintenance burden by ensuring that content using an older design continues to work, it hinders progress by the fact that the moment at which it first becomes clear that an architectural or double-maintenance problem will occur is exactly the same moment at which keeping the old structure around becomes problematic. Beginning a deprecation cycle at that point and then having to "wait out" the old paradigm will cause an unacceptable delay in development.

The Middle Ground - Deprecate everything old, remove only when necessary

Most of the time, older APIs can be implemented in terms of their newer, cleaner counterparts through the use of things like simple wrappers, parse-translators which re-write the older paradigm's code in terms of the new one, or other relatively low-maintenance "set-it-and-forget-it" approaches. These simple wrappers are not really detrimental to making progress, don't require updating when the new APIs internals are changed, and can usually be organized into their own files and/or modules so that they don't clutter the cleaner code. Many such wrappers will never truly present either of the backwards compatibility drawbacks mentioned above. As such, there is really no detriment to keeping them around indefinitely. However, occasionally, a new idea or approach will be put forth that updates the newer paradigm in such a way that the older one can no longer cleanly wrap to it. This usually happens, as inspiration is wont to do, suddenly, unexpectedly, and without warning. As such developers need the flexibility to be able to remove outdated code as freely as possible when the situation requires. Therefore, the ideal solution would be to deprecate any API which has newer, feature-complete ways to do it, while leaving it in the codebase until such time as its presence becomes a hindrance. Essentially, deprecation need not necessarily mean "this WILL be removed" so much as "this is now a candidate for removal". By separating the concepts of deprecation and removal, both goals can be better served.

Undefined removal timeline - issues encountered

In practice however, simply stating something is a candidate for removal while providing no further information on when it will actually be removed causes multiple issues:

  • When a deprecated API is causing a maintenance burden worthy of removal is a subjective decision, often leading to debates over removal any time a developer decides it's time for an API to be removed, initially deprecated, or moved to a higher deprecation level.
  • Lack of developer incentive to provide good documentation and support for the deprecation of the API given there's no particular timeline for when it will actually cause issues for UMC authors.
  • UMC authors often don't find out about deprecated APIs and so can't possibly migrate to the new APIs.
  • UMC authors aren't provided the documentation or tooling support needed to make updating their add-ons easier.
  • UMC authors may simply decide there's no reason to update to the new API even if they know it's deprecated and how to update their add-on. After all, why spend time updating APIs that may stick around forever?
  • Giving a version that an API may be removed instead of a version that it will be removed is not as clear as it may seem to UMC authors who aren't already aware of how Wesnoth handles deprecation. This can lead to UMC authors ignoring deprecation warnings after seeing such warnings for APIs which provide a version that's years old.

Certainty is also a benefit

Wesnoth continues to exist today in large part because of the content made by its community. As such, developers should always be sparing in what they choose to deprecate and what they choose to remove, so that UMC authors can update their content to the latest version of Wesnoth without needing to make a herculean effort every couple of years.

But, for cases where APIs are removed, the benefit of providing certainty for when that will happen outweighs the flexibility of being able remove them at any time after they been marked as deprecated. It encourages developers to provide the documentation and tooling support to make it easier for UMC authors to update their add-ons, and it lets UMC authors know when they can expect deprecated APIs to be removed rather than it effectively happening at random when a developer decides a deprecated API needs to be dropped.

More Complex Cases - One size does not fit all

There may, however, be cases where the obsolete API cannot be implemented cleanly in terms of the updated one and must be maintained separately, or, in extremely rare cases, cannot coexist at all. There needs to be some leeway for such cases as well. In the former case, it therefore makes sense to allow for a feature to be deprecated pending removal after the shortest reasonable deprecation period. In the latter case, there is obviously no choice but to make the change and remove the old immediately. Developers should, naturally, be encouraged to find creative solutions to avoid such cases, but it is inevitable that there will eventually be a few cases where no graceful transition procedure can be found. These more aggressive forms of deprecation should only be done with developer consensus, and only after all options of creating a backwards-compatible transition have been exhausted.

Graphics - The exception that proves the rule

The one area where backwards compatibility should NOT be a factor is graphical changes. Any change to the terrain graphics, unit sprites, or portrait images has the potential to result in visually-incompatible custom content. Core content creators cannot be expected to maintain a deprecated visual style alongside a more modern one, as any approach toward doing so would add an unreasonable amount of bloat and overhead, and make it very difficult for core graphics to be updated without having to do double the work. In addition, add-ons will continue to function even with such visual incompatibilities present, they just won't look right. As such, it can be said that stylistic incompatibilities fall more within the realm of content than of code, and it is not unreasonable to expect a content creator to... well... create content. Expecting the graphical style to remain backwards-compatible would be just as unreasonable as expecting stories, help descriptions, or other forms of lore to never change because they may introduce plot holes into add-on stories. Essentially, since the goal of the software portion of Wesnoth is, at its heart, merely to facilitate the creation and advancement of this kind of content, core content needs to be free to develop unhindered by considerations for add-on content.

This page was last edited on 19 April 2026, at 02:40.