Repository navigation
Map topology and surrender - #1
Merged
Merged
Conversation
The world map is stretched on purpose — Europe and Japan wide, the oceans narrow — and the stretch itself cannot break topology: longitude and latitude each map to the grid monotonically. What broke it was elsewhere. Finland had no polygon. Scandinavia's east edge ran up the Gulf of Bothnia and Europe's west edge ran north from St Petersburg, and everything between 21°E and 29°E came out as sea. Helsinki was one anchored hex, St Petersburg and Murmansk took the rest, and Finland touched Latvia and Belarus across a gulf that was not there. Finland is now drawn, the Gulf of Bothnia is sea, the Gulf of Finland is carved as a strait, and 60–72°N gets proper rows instead of 2.6° each. Kiruna, Sundsvall, Gothenburg and Oulu give Sweden and Finland their own ground, so Oslo no longer owns central Sweden and Copenhagen no longer owns the Swedish coast. Straits were carved everywhere except on city hexes, so two cities facing each other across one became a land bridge: Busan and Fukuoka were neighbours and Japanese infantry could walk into Korea. The same happened across Florida, the Irish Sea, Hormuz, Bab-el-Mandeb and the Red Sea. Island groups (geodata.ISLANDS) now never touch other land, and pairs of mainland polygons that a sea divides (geodata.SEAS: Arabia against Africa, Iran and South Asia) never touch each other. When two cities would, the island city moves to the nearest hex of its own island. Cities also snap only to land on their own side of the water. Several regions were simply missing: Jutland, the Indus valley and the Himalayan border between India and China. The Middle East polygon started off the Libyan coast, turning the Levantine Sea into land that joined Alexandria to Syria. Those are drawn now. Hamhung gives North Korea its north-east, which Shenyang used to own, and Guwahati gives India its north-east, which Dhaka used to own. An island with no city goes to one owner whole instead of being split tile by tile. tools/topology.py writes the geography down as two lists, borders that must exist and seas that must stay seas, and genworld now fails when the world map breaks either one. CI regenerates the maps, so a coastline edit that reopens a land bridge fails the build. Every map changed, so tile and province indices in old saves point at the wrong places. A save now records the map's fingerprint, and one that does not match is refused with its own message instead of loading units into the sea.
…emy cities This follows the reference game's observable rules, written down in the new docs/original-behavior.md together with what is still unknown about them. A nation that loses its last city surrenders on the spot. Every unit it has left disappears, including ships at sea and troops deep in someone else's land, and whatever provinces it still holds go to the nation that took that last city. Before, a nation was only gone once it had no provinces and no units at all, so a beaten country lingered for as long as one destroyer was still afloat somewhere. A nation that never had a city is unaffected and still disappears only when it has nothing left. Conquest is won by taking every city of every enemy: the members of the opposing bloc, plus anyone you are at war with, such as a neutral you declared war on. Allies and neutrals you left alone do not count. Since an enemy surrenders with its last city, "every enemy has surrendered" and "every enemy city is taken" are the same test. The old fallback of holding 80% of the world is gone; it could only be met by fighting allies and neutrals. With no enemy at all there is no conquest victory; you have to declare war first. The objectives panel, the event ticker, the README and the store listing say the same thing. Tests cover a surrender with a leftover province and a ship, a lost city that was not the last, a win that leaves a neutral alone, a declared-on neutral that must be beaten too, and a map with no enemy.
Running out of turns in conquest is a loss in the reference game. Session already treats "turn past the limit and not yet won" as defeat for any scenario; conquest scenarios were just generated with no limit. write_conquest now takes turn_limit, and a test pins the defeat. The limit for each year is still to be decided, so the shipped scenarios keep 0. docs/original-behavior.md now records that an AI ally taking an enemy's last city has the same effect as the player taking it, and that a bloc member joining the war late only matters in play that cannot happen without cheating, so the conservative rule stays.
The guard asserted 20–45% land, and the map was already at 44%: the oceans are squeezed on purpose, so land takes far more of the grid than the real world's 30% the comment claimed. Drawing Finland, Jutland and the Indus valley and giving 60–72°N proper rows brings it to 46%. The test exists to catch a generator that turned everything to sea or land, and 20–55% still does that.
Denmark touched Sweden: Copenhagen's hex bridged the Øresund. Jutland and Zealand now never touch Scandinavia by land (geodata.SEAS), and Zealand has its own polygon so Copenhagen sits on it. Norway was in two pieces. The Scandinavian polygon's north coast only reached the grid's top land row at the North Cape, so an inland Swedish province ran to the Arctic between Narvik and the far north. The coast is drawn up to 71.3°N, Trondheim and Hammerfest give Norway its middle and north, and Luleå replaces Kiruna as Sweden's northern city so it stops winning ties on the Norwegian coast. topology.py now also checks that a list of countries' mainland is one piece; it found Chile cut in two by Bariloche's province, so Argentina's Patagonian cities are now Neuquén and Comodoro Rivadavia on the east side and Chile gets Temuco and Coyhaique on the west. Vladivostok was an island: the Siberian polygon cut straight from 135°E 48°N to 130°E 44°N and left Primorye out. The Philippines were one blob from 120°E to 126.5°E that counted the Sibuyan Sea as land; they are now Luzon, the Visayas and Mindanao. Hong Kong, Gibraltar (new) and Belfast (new, Northern Ireland) are places.SINGLE_TILE provinces: one hex, no territory grown around them. The Strait of Gibraltar line moved to the southern half of the strait so Gibraltar stands on the Iberian side, and Europe never touches Africa by land. GBR–IRL is now a required land border and GBR–ESP one too. Cuba, the Dominican Republic and Panama are independent neutrals in 1939 and 1943, not part of the United States. Ethiopia is neutral in 1943 instead of an Allied member. In 1939 and 1943 the Republic of China is shown as "China" under 🇹🇼; from 1950 China is the People's Republic 🇨🇳 and Taiwan the Republic of China 🇹🇼. The Republic of China starts 1950 with its remnants: two infantry in the Shan States, one south-east of Kunming, and ships off Zhoushan, off Fujian and in the strait.
A new game made the player watch every nation ahead of them in the scenario file move first — in conquest that is dozens of AI turns before the first click, because the file lists nations by strength. The reference game starts on the player's turn and the AI moves after it. Session now puts the player at the front of the nation list and keeps the scenario's order for everyone else, so every round starts with the player. Nation ids shift with that, so the save format goes to version 2 and older saves are refused as outdated. docs/original-behavior.md records the turn order.
Every fresh runner generated its own debug keystore, so each CI debug APK had a different signature and could not be installed over the previous one. The workflow now restores a fixed keystore from the DEBUG_KEYSTORE secret (base64) to the path the Android build tools read by default, so the Gradle files stay unchanged and no key is committed. Without the secret, as on pull requests from forks, it warns and falls back to a random key. Release builds are untouched; F-Droid signs those itself.
…lity The shared debug key from the previous commit is dropped: phones get the release build. CI still builds the release APK without any signing config, so the unsigned artifact is what F-Droid will build from source, and then signs it with apksigner using the RELEASE_KEYSTORE secret (base64) and its password and alias secrets. The keystore exists only in the runner's temp directory for that one step. The certificate fingerprint is printed for F-Droid's AllowedAPKSigningKeys. Without the secrets, as on pull requests from forks, the job warns and ships only the unsigned APK. The debug APK is no longer built or uploaded; nobody installs it and its random per-runner key made it impossible to update anyway. A new reproducible job builds the release twice, from the checkout and from a second worktree at another path with the build cache off, and fails unless the two APKs are byte-identical. F-Droid only publishes the developer-signed APK when its own build matches, so this catches timestamps, absolute paths and unstable ordering before a release does.
Both still described the first version: five training exercises, 20 unit types across land, sea and air, fighters that rule the sky, a 96×60 map and everyone at peace in conquest. They now describe the game as it is: three campaign chapters with nine battles playable from either side, conquest in four years with blocs and surrender, player-first turns, 17 unit types plus air missions, single-hex strongholds, and a stretched world map whose borders and straits are checked on every generation. The README gains a section pointing to docs/original-behavior.md, and one on releases: CI signs with the project key, the release must be reproducible, the certificate fingerprint, and how a v<versionName> tag publishes a GitHub Release. The test and generator lists cover SessionTest and tools/topology.py. The versionCode 1 changelog, not yet released, now matches what that release will contain.
Pushing v<versionName> runs the whole workflow and, once the build, the reproducibility check and the generated-assets check have passed, a release job creates the GitHub Release with the signed APK attached as mapconquer-<versionName>.apk, the stable name F-Droid's Binaries URL can point at. The notes are the fastlane changelogs for that versionCode in all three languages, with the APK's SHA-256. The version stays in the source, where F-Droid reads it; the job fails if the tag does not match versionName or the changelog is missing. On a tag, missing signing secrets are an error rather than a warning, so an unsigned APK can never be published.
Flags were emoji only, so the Soviet Union flew the Russian tricolour, East Germany had no flag at all, and Germany in 1939 and 1943 flew the Federal Republic's. No emoji font has the flags these need. A scenario's flag can now be an @-code as well as an emoji, and render/FlagArt.kt draws those codes in the style of the default Android emoji flags: the same box, measured from a real emoji flag so the two always line up, a wave along the top and bottom edges, a light sheen and a faint dark edge. Each is composed once per pixel size and cached, since the map shows a hundred city badges a frame. The five places that drew a flag as text now go through FlagArt; the two that glued flag and name into one string draw them separately. - @su: red field, gold star, hammer and sickle — the Soviet Union in every scenario from 1939 to 1985. - @GDR: black, red and gold with the wreath, hammer and compass, from 1959. In 1950 East Germany's flag was the same plain tricolour as West Germany's, and that is what it flies. - @reich: Germany from 1935 to 1945. The historical flag is the swastika flag; by the maintainer's decision the game shows the Wehrmacht's Iron Cross instead, black with a white rim on field grey. tools/places.py picks the flag by nation and year (flag_for), for both conquest and campaign scenarios, and ScenarioAssetTest rejects any @-code FlagArt does not know.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.