diff --git a/CHANGELOG.md b/CHANGELOG.md
index 16ec084e0..d54dcb31b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,12 +1,30 @@
-# Geplante Aenderungen in zukuenftigen Eressea-Versionen
 
-Als Anhaltspunkt fuer die Empfaenger der woechentlichen Testauswertungnen
-will ich versuchen, die Liste meiner Aenderungen seit dem letzten Release
-zu dokumentieren.
+# 3.26
 
-## Version 3.12.0
+  - Akademien, Traenke und Verzauberungen wirken auch bei LERNE AUTO
+  - Das lernen in einer Akademie erhoeht die Lernkosten. Koennen diese
+  nicht bezahlt werden, wird ohne deren Bonus gelernt.
+  - Lehrer muessen nicht mehr in der Akademie stehen, damit ihre Schueler
+  den Bonus bekommen
+  - Rohstoffe koennen jetzt bereits gesehen werden, wenn eine Einheit nur
+  die Haelfte des zum Abbau noetigen Talentes hat (statt bisher
+  Talent-1)
+  - Mauern der Ewigkeit und Störe Astrale Integrität brauchen keine
+  Stufenangabe, ihre Kosten sind nicht variabel [2651]
+
+# 3.25
+
+  - Ab sofort ist es nicht mehr erlaubt, Befehle mit weniger als 3 
+  Zeichen abzukürzen.
+  - Leuchttürme entdecken Seeschlangen und Drachen auf dem Ozean [2688]
+  - Magieresistenz von Insekten und Goblins repariert [2685]
+  - Getarnte Einheiten können wieder Eisen abbauen [2679]
+  - Gestaltwandlung kann nur einmal auf die selbe Einheit wirken [2680] 
+  - Handel benötigt eine Burg mit Mindestgröße 2 [2678]
+  - Geschützte Leerzeichen in Befehlen werden ignoriert [2670]
+
+# 3.12
 
-- [other] optimierte Berechnung der Sichtbarkeit von Leuchttuermen
 - [bug] Einheitenlimit bei GIB PERSON beachten
 - [bug] Einheitenlimit bei ALLIANCE JOIN beachten
 - [rule] Einheiten- und Personenzahl im Report beinhaltet *alle* Einheiten der Partei.
diff --git a/conf/e2/catalog.xml b/conf/e2/catalog.xml
deleted file mode 100644
index d0c7333aa..000000000
--- a/conf/e2/catalog.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0"?>
-<!DOCTYPE catalog
-    PUBLIC "-//OASIS/DTD Entity Resolution XML Catalog V1.0//EN"
-    "http://www.oasis-open.org/committees/entity/release/1.0/catalog.dtd">
-
-<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
-    <rewriteURI
-        uriStartString="config://core/"
-        rewritePrefix="../../res/core/" />
-    <rewriteURI
-        uriStartString="config://game/"
-        rewritePrefix="../../res/eressea/" />
-    <rewriteURI
-        uriStartString="config://default/"
-        rewritePrefix="../../res/" />
-</catalog>
diff --git a/conf/e2/config.json b/conf/e2/config.json
index 114fffe52..fdb616816 100644
--- a/conf/e2/config.json
+++ b/conf/e2/config.json
@@ -1,63 +1,64 @@
 {
-    "include": [
-        "config://conf/keywords.json",
-        "config://conf/calendar.json",
-        "config://conf/prefixes.json",
-        "config://conf/e2/locales.json",
-        "config://conf/e2/terrains.json",
-        "config://conf/e2/items.json",
-        "config://res/core/ships.xml",
-        "config://res/core/common/buildings.xml",
-        "config://res/eressea/buildings.xml",
-        "config://res/buildings/castle.xml",
-        "config://res/eressea/races.xml",
-        "config://res/eressea/artrewards.xml",
-        "config://res/eressea/spells.xml",
-        "config://res/eressea/spellbooks/gray.xml",
-        "config://res/eressea/spellbooks/gwyrrd.xml",
-        "config://res/eressea/spellbooks/draig.xml",
-        "config://res/eressea/spellbooks/illaun.xml",
-        "config://res/eressea/spellbooks/cerddor.xml",
-        "config://res/eressea/spellbooks/tybied.xml"
-    ],
-    "disabled": [
-        "jsreport"
-    ],
-    "settings": {
-        "game.name" : "Eressea",
-        "game.mailcmd" : "ERESSEA",
-        "game.id" : 2,
-        "orders.default": "work",
-        "NewbieImmunity": 8,
-        "modules.market": false,
-        "modules.astralspace": true,
-        "modules.wormhole": true,
-        "modules.iceberg": true,
-        "modules.volcano": true,
-        "monsters.spawn.chance": 50,
-        "entertain.base": 0,
-        "entertain.perlevel": 20,
-        "taxing.perlevel": 20,
-        "nmr.timeout": 5,
-        "nmr.removenewbie": false,
-        "GiveRestriction": 3,
-        "hunger.long": false,
-        "hunger.damage": "1d8+6",
-        "init_spells": 0,
-        "game.era": 2,
-        "game.start": 184,
-        "rules.reserve.twophase": true,
-        "rules.give.max_men": -1,
-        "rules.check_overload": false,
-        "rules.limit.faction": 2500,
-        "rules.maxskills.magic": 5,
-        "rules.guard.base_stop_prob": 0.30,
-        "rules.guard.skill_stop_prob": 0.05,
-        "rules.guard.amulet_stop_prob": 0.10,
-        "rules.guard.guard_number_stop_prob": 0.001,
-        "rules.guard.castle_stop_prob": 0.05,
-        "rules.guard.region_type_stop_prob": 0.05,
-        "rules.economy.repopulate_maximum": 500,
-        "rules.lighthouse.unit_capacity": true
-    }
+	"settings": {
+		"game.name": "Eressea",
+		"game.mailcmd": "ERESSEA",
+		"game.id": 2,
+		"orders.default": "work",
+		"NewbieImmunity": 8,
+		"modules.market": false,
+		"modules.astralspace": true,
+		"modules.wormhole": true,
+		"modules.iceberg": true,
+		"modules.volcano": true,
+		"monsters.spawn.chance": 50,
+		"entertain.base": 0,
+		"entertain.perlevel": 20,
+		"taxing.perlevel": 20,
+		"nmr.timeout": 5,
+		"nmr.removenewbie": false,
+		"GiveRestriction": 3,
+		"hunger.long": false,
+		"hunger.damage": "1d8+6",
+		"init_spells": 0,
+		"game.era": 2,
+		"game.start": 184,
+		"rules.reserve.twophase": true,
+		"rules.give.max_men": -1,
+		"rules.check_overload": false,
+		"rules.limit.faction": 2500,
+		"rules.maxskills.magic": 5,
+		"rules.guard.base_stop_prob": 0.30,
+		"rules.guard.skill_stop_prob": 0.05,
+		"rules.guard.amulet_stop_prob": 0.10,
+		"rules.guard.guard_number_stop_prob": 0.001,
+		"rules.guard.castle_stop_prob": 0.05,
+		"rules.guard.region_type_stop_prob": 0.05,
+		"rules.economy.repopulate_maximum": 500,
+		"rules.lighthouse.unit_capacity": true,
+		"resource.visibility.rule": 0
+	},
+	"disabled": [
+		"jsreport"
+	],
+	"include": [
+		"config://conf/keywords.json",
+		"config://conf/calendar.json",
+		"config://conf/prefixes.json",
+		"config://conf/e2/locales.json",
+		"config://conf/e2/terrains.json",
+		"config://conf/e2/items.json",
+		"config://res/core/ships.xml",
+		"config://res/core/common/buildings.xml",
+		"config://res/eressea/buildings.xml",
+		"config://res/buildings/castle.xml",
+		"config://res/eressea/races.xml",
+		"config://res/eressea/artrewards.xml",
+		"config://res/eressea/spells.xml",
+		"config://res/eressea/spellbooks/gray.xml",
+		"config://res/eressea/spellbooks/gwyrrd.xml",
+		"config://res/eressea/spellbooks/draig.xml",
+		"config://res/eressea/spellbooks/illaun.xml",
+		"config://res/eressea/spellbooks/cerddor.xml",
+		"config://res/eressea/spellbooks/tybied.xml"
+	]
 }
diff --git a/conf/e3/catalog.xml b/conf/e3/catalog.xml
deleted file mode 100644
index 9987e72c8..000000000
--- a/conf/e3/catalog.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0"?>
-<!DOCTYPE catalog
-    PUBLIC "-//OASIS/DTD Entity Resolution XML Catalog V1.0//EN"
-    "http://www.oasis-open.org/committees/entity/release/1.0/catalog.dtd">
-
-<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
-    <rewriteURI
-        uriStartString="config://core/"
-        rewritePrefix="../../res/core/" />
-    <rewriteURI
-        uriStartString="config://game/"
-        rewritePrefix="../../res/e3a/" />
-    <rewriteURI
-        uriStartString="config://default/"
-        rewritePrefix="../../res/" />
-</catalog>
diff --git a/conf/e3/config.json b/conf/e3/config.json
index dcc3e9f53..c7c390372 100644
--- a/conf/e3/config.json
+++ b/conf/e3/config.json
@@ -67,7 +67,6 @@
         "init_spells": 0,
         "recruit.allow_merge": true,
         "study.expensivemigrants": true,
-        "study.speedup": 2,
         "game.era": 3,
         "game.start": 1,
         "rules.reserve.twophase": true,
diff --git a/conf/ex/config.json b/conf/ex/config.json
new file mode 100644
index 000000000..58d460f85
--- /dev/null
+++ b/conf/ex/config.json
@@ -0,0 +1,68 @@
+{
+    "include": [
+        "config://conf/keywords.json",
+        "config://conf/calendar.json",
+        "config://conf/prefixes.json",
+        "config://conf/e2/locales.json",
+        "config://conf/e2/terrains.json",
+        "config://conf/e2/items.json",
+        "config://res/core/ships.xml",
+        "config://res/core/common/buildings.xml",
+        "config://res/eressea/buildings.xml",
+        "config://res/buildings/castle.xml",
+        "config://res/eressea/races.xml",
+        "config://res/eressea/artrewards.xml",
+        "config://res/eressea/spells.xml",
+        "config://res/eressea/spellbooks/gray.xml",
+        "config://res/eressea/spellbooks/gwyrrd.xml",
+        "config://res/eressea/spellbooks/draig.xml",
+        "config://res/eressea/spellbooks/illaun.xml",
+        "config://res/eressea/spellbooks/cerddor.xml",
+        "config://res/eressea/spellbooks/tybied.xml"
+    ],
+    "disabled": [
+        "destroy",
+        "steal",
+        "number",
+        "jsreport"
+    ],
+    "settings": {
+        "game.name" : "Eressea",
+        "game.mailcmd" : "ERESSEA",
+        "game.id" : 2,
+        "orders.default": "work",
+        "NewbieImmunity": 8,
+        "modules.market": false,
+        "modules.astralspace": true,
+        "modules.wormhole": true,
+        "modules.iceberg": true,
+        "modules.volcano": true,
+        "monsters.spawn.chance": 50,
+        "entertain.base": 0,
+        "entertain.perlevel": 20,
+        "taxing.perlevel": 20,
+        "nmr.timeout": 5,
+        "nmr.removenewbie": false,
+        "GiveRestriction": 3,
+        "hunger.long": false,
+        "hunger.damage": "1d8+6",
+        "init_spells": 0,
+        "game.era": 2,
+        "game.start": 184,
+        "rules.reserve.twophase": true,
+        "rules.give.max_men": -1,
+        "rules.check_overload": false,
+        "rules.wage.function": 2,
+        "monsters.spawn.chance" : 0,
+        "rules.limit.faction": 2500,
+        "rules.maxskills.magic": 5,
+        "rules.guard.base_stop_prob": 0.30,
+        "rules.guard.skill_stop_prob": 0.05,
+        "rules.guard.amulet_stop_prob": 0.10,
+        "rules.guard.guard_number_stop_prob": 0.001,
+        "rules.guard.castle_stop_prob": 0.05,
+        "rules.guard.region_type_stop_prob": 0.05,
+        "rules.economy.repopulate_maximum": 500,
+        "rules.lighthouse.unit_capacity": true
+    }
+}
diff --git a/conf/ex/items.json b/conf/ex/items.json
new file mode 100644
index 000000000..643248d32
--- /dev/null
+++ b/conf/ex/items.json
@@ -0,0 +1,50 @@
+{
+    "include": [
+        "config://res/core/spoils.xml",
+        "config://res/core/common/herbs.xml",
+        "config://res/core/common/items.xml",
+        "config://res/core/common/luxuries.xml",
+        "config://res/core/common/potions.xml",
+        "config://res/core/armor/chainmail.xml",
+        "config://res/core/armor/laenmail.xml",
+        "config://res/core/armor/laenshield.xml",
+        "config://res/core/armor/plate.xml",
+        "config://res/core/armor/rustychainmail.xml",
+        "config://res/core/armor/rustyshield.xml",
+        "config://res/core/armor/shield.xml",
+        "config://res/core/resources/cart.xml",
+        "config://res/core/resources/horse.xml",
+        "config://res/core/resources/hp.xml",
+        "config://res/core/resources/iron.xml",
+        "config://res/core/resources/laen.xml",
+        "config://res/core/resources/log.xml",
+        "config://res/core/resources/mallorn.xml",
+        "config://res/core/resources/mallornseed.xml",
+        "config://res/core/resources/seed.xml",
+        "config://res/core/resources/peasant.xml",
+        "config://res/core/resources/stone.xml",
+        "config://res/core/weapons/axe.xml",
+        "config://res/core/weapons/bow.xml",
+        "config://res/core/weapons/catapult.xml",
+        "config://res/core/weapons/crossbow.xml",
+        "config://res/core/weapons/firesword.xml",
+        "config://res/core/weapons/greatbow.xml",
+        "config://res/core/weapons/greatsword.xml",
+        "config://res/core/weapons/halberd.xml",
+        "config://res/core/weapons/laensword.xml",
+        "config://res/core/weapons/lance.xml",
+        "config://res/core/weapons/mallornbow.xml",
+        "config://res/core/weapons/mallorncrossbow.xml",
+        "config://res/core/weapons/mallornlance.xml",
+        "config://res/core/weapons/mallornspear.xml",
+        "config://res/core/weapons/runesword.xml",
+        "config://res/core/weapons/rustyaxe.xml",
+        "config://res/core/weapons/rustygreatsword.xml",
+        "config://res/core/weapons/rustyhalberd.xml",
+        "config://res/core/weapons/rustysword.xml",
+        "config://res/core/weapons/spear.xml",
+        "config://res/core/weapons/sword.xml",
+        "config://res/eressea/items.xml",
+        "config://res/adamantium.xml"
+    ]
+}
diff --git a/conf/ex/locales.json b/conf/ex/locales.json
new file mode 100644
index 000000000..02967d134
--- /dev/null
+++ b/conf/ex/locales.json
@@ -0,0 +1,34 @@
+{
+  "include": [
+    "config://res/translations/strings.de.po",
+    "config://res/translations/strings-e2.de.po",
+    "config://res/translations/strings.en.po",
+    "config://res/translations/strings-e2.en.po",
+    "config://res/translations/messages.de.po",
+    "config://res/translations/messages.en.po",
+    "config://res/core/messages.xml"
+  ],
+  "aliases": {
+    "de": {
+      "spell::earthquake": [
+        "Beschwöre einen Erdelementar",
+        "Beschwörung eines Erdelementares"
+      ],
+      "spell::goodwinds": [
+        "Beschwörung eines Wasserelementares",
+        "Beschwöre einen Wasserelementar"
+      ],
+      "spell::stormwinds": [
+        "Beschwöre einen Sturmelementar",
+        "Beschwörung eines Sturmelementares"
+      ],
+      "spell::summonfireelemental": [
+        "Beschwöre einen Hitzeelementar",
+        "Beschwörung eines Hitzeelementares"
+      ]
+    },
+    "en": {
+      "spell::migration": "Rit of Acceptance"
+    }
+  }
+}
diff --git a/conf/ex/readme.txt b/conf/ex/readme.txt
new file mode 100644
index 000000000..c3e8801e9
--- /dev/null
+++ b/conf/ex/readme.txt
@@ -0,0 +1,8 @@
+# Änderungen gegenüber E2
+
+- ARBEITE produziert nur genug Silber für den Unterhalt der Einheit
+- NUMMER Befehl abgeschafft.
+- Ebene und Hochland haben 20%, alle anderen Regionen 10% der Arbeitsplätze von E2.
+- Keine Zufallsmonster
+- ZERSTÖRE Befehl abgeschafft.
+- BEKLAUE Befehl abgeschafft.
diff --git a/conf/ex/terrains.json b/conf/ex/terrains.json
new file mode 100644
index 000000000..eb1019fe1
--- /dev/null
+++ b/conf/ex/terrains.json
@@ -0,0 +1,286 @@
+{
+    "terrains": {
+        "ocean": {
+            "size": 10,
+            "flags": [ "swim", "sea", "sail", "fly" ]
+        },
+        "plain": {
+            "size": 2000,
+            "herbs": [ "h0", "h1", "h2", "h3", "h4", "h5" ],
+            "seed": 3,
+            "road": 50,
+            "flags": [ "forest", "cavalry", "land", "walk", "sail", "fly" ],
+            "production": {
+                "iron": {
+                    "chance": 0.1,
+                    "base": "5d8",
+                    "div": "2d20+10",
+                    "level": "2d4-1"
+                },
+                "stone": {
+                    "chance": 0.15,
+                    "base": "5d8",
+                    "div": "2d30+20",
+                    "level": "1d4"
+                },
+                "laen": {
+                    "chance": 0.01,
+                    "base": "1d4",
+                    "div": "2d20+50",
+                    "level": "1d4"
+                }
+            }
+        },
+        "swamp": {
+            "size": 200,
+            "herbs": [ "h6", "h7", "h8" ],
+            "seed": 2,
+            "road": 75,
+            "flags": [ "land", "walk", "sail", "fly" ],
+            "production": {
+                "iron": {
+                    "chance": 0.02,
+                    "base": "5d8",
+                    "div": "2d20+10",
+                    "level": "2d4-1"
+                },
+                "stone": {
+                    "chance": 0.02,
+                    "base": "5d8",
+                    "div": "2d30+20",
+                    "level": "1d4"
+                },
+                "laen": {
+                    "chance": 0.02,
+                    "base": "1d4",
+                    "div": "2d20+50",
+                    "level": "1d4"
+                }
+            }
+        },
+        "desert": {
+            "size": 50,
+            "herbs": [ "h9", "h10", "h11" ],
+            "seed": 2,
+            "road": 100,
+            "flags": [ "land", "walk", "sail", "fly", "cavalry" ],
+            "production": {
+                "iron": {
+                    "chance": 0.15,
+                    "base": "5d8",
+                    "div": "2d20+10",
+                    "level": "2d4-1"
+                },
+                "stone": {
+                    "chance": 0.25,
+                    "base": "5d8",
+                    "div": "2d30+20",
+                    "level": "1d4"
+                },
+                "laen": {
+                    "chance": 0.025,
+                    "base": "1d4",
+                    "div": "2d20+50",
+                    "level": "1d4"
+                }
+            }
+        },
+        "highland": {
+            "size": 800,
+            "herbs": [ "h12", "h13", "h14" ],
+            "seed": 2,
+            "road": 100,
+            "flags": [ "land", "walk", "sail", "fly", "cavalry" ],
+            "production": {
+                "iron": {
+                    "chance": 0.15,
+                    "base": "5d8",
+                    "div": "2d20+10",
+                    "level": "2d4-1"
+                },
+                "stone": {
+                    "chance": 0.25,
+                    "base": "5d8",
+                    "div": "2d30+20",
+                    "level": "1d4"
+                },
+                "laen": {
+                    "chance": 0.025,
+                    "base": "1d4",
+                    "div": "2d20+50",
+                    "level": "1d4"
+                }
+            }
+        },
+        "mountain": {
+            "size": 100,
+            "herbs": [ "h15", "h16", "h17" ],
+            "seed": 2,
+            "road": 250,
+            "flags": [ "land", "walk", "sail", "fly" ],
+            "production": {
+                "iron": {
+                    "chance": 1.0,
+                    "base": "50",
+                    "div": "50",
+                    "level": "1"
+                },
+                "stone": {
+                    "chance": 1.0,
+                    "base": "100",
+                    "div": "100",
+                    "level": "1"
+                },
+                "laen": {
+                    "chance": 0.05,
+                    "base": "4",
+                    "div": "100",
+                    "level": "1"
+                }
+            }
+        },
+        "glacier": {
+            "size": 10,
+            "herbs": [ "h18", "h19", "h20" ],
+            "seed": 2,
+            "road": 250,
+            "flags": [ "arctic", "land", "walk", "sail", "fly" ],
+            "production": {
+                "iron": {
+                    "chance": 1.0,
+                    "base": "3",
+                    "div": "50",
+                    "level": "1"
+                },
+                "stone": {
+                    "chance": 1.0,
+                    "base": "2",
+                    "div": "100",
+                    "level": "1"
+                },
+                "laen": {
+                    "chance": 0.05,
+                    "base": "4",
+                    "div": "100",
+                    "level": "1"
+                }
+            }
+        },
+        "iceberg": {
+            "size": 10,
+            "herbs": [ "h18", "h19", "h20" ],
+            "flags": [ "arctic", "land", "walk", "sail", "fly" ],
+            "production": {
+                "iron": {
+                    "chance": 0.9,
+                    "base": "3",
+                    "div": "50",
+                    "level": "1"
+                },
+                "stone": {
+                    "chance": 0.9,
+                    "base": "2",
+                    "div": "100",
+                    "level": "1"
+                }
+            }
+        },
+        "iceberg_sleep": {
+            "size": 10,
+            "herbs": [ "h18", "h19", "h20" ],
+            "flags": [ "arctic", "land", "walk", "sail", "fly" ],
+            "production": {
+                "iron": {
+                    "chance": 0.9,
+                    "base": "3",
+                    "div": "50",
+                    "level": "1"
+                },
+                "stone": {
+                    "chance": 0.9,
+                    "base": "2",
+                    "div": "100",
+                    "level": "1"
+                },
+                "laen": {
+                    "chance": 0.05,
+                    "base": "4",
+                    "div": "100",
+                    "level": "1"
+                }
+            }
+        },
+        "firewall": {
+            "flags": [ "forbidden" ]
+        },
+        "fog": {
+            "flags": [ "walk", "fly" ]
+        },
+        "thickfog": {
+            "flags": [ "forbidden" ]
+        },
+        "volcano": {
+            "size": 50,
+            "road": 250,
+            "seed": 1,
+            "flags": [ "land", "walk", "sail", "fly" ],
+            "production": {
+                "iron": {
+                    "chance": 0.5,
+                    "level": "1",
+                    "base": "50",
+                    "div": "50"
+                },
+                "stone": {
+                    "chance": 0.5,
+                    "level": "1",
+                    "base": "100",
+                    "div": "100"
+                },
+                "laen": {
+                    "chance": 0.075,
+                    "level": "1",
+                    "base": "4",
+                    "div": "100"
+                }
+            }
+        },
+        "activevolcano": {
+            "size": 50,
+            "road": 250,
+            "flags": [ "land", "walk", "sail", "fly" ],
+            "production": {
+                "iron": {
+                    "chance": 0.5,
+                    "level": "1",
+                    "base": "50",
+                    "div": "50"
+                },
+                "stone": {
+                    "chance": 0.5,
+                    "level": "1",
+                    "base": "100",
+                    "div": "100"
+                },
+                "laen": {
+                    "chance": 0.075,
+                    "level": "1",
+                    "base": "4",
+                    "div": "100"
+                }
+            }
+        },
+        "hell": {
+            "flags": [ "walk" ]
+        },
+        "hall1": {
+            "flags": [ "land", "walk", "sail" ]
+        },
+        "corridor1": {
+            "flags": [ "land", "walk", "sail" ]
+        },
+        "wall1": {
+            "flags": [ "forbidden", "land" ]
+        }
+    }
+}
diff --git a/res/e3a/spells.xml b/res/e3a/spells.xml
index d72386803..64172390f 100644
--- a/res/e3a/spells.xml
+++ b/res/e3a/spells.xml
@@ -595,7 +595,7 @@
   <spell name="shockwave" rank="5" variable="true" combat="2">
     <resource name="aura" amount="1" cost="level"/>
   </spell>
-  <spell name="eternal_walls" rank="5" parameters="b" ship="true" variable="true">
+  <spell name="eternal_walls" rank="5" parameters="b" ship="true">
     <resource name="aura" amount="50" cost="fixed"/>
     <resource name="permaura" amount="1" cost="fixed"/>
   </spell>
diff --git a/res/eressea/spells.xml b/res/eressea/spells.xml
index a56b147ef..0570fa80d 100644
--- a/res/eressea/spells.xml
+++ b/res/eressea/spells.xml
@@ -214,7 +214,7 @@
   <spell name="view_reality" rank="5">
     <resource name="aura" amount="40" cost="fixed"/>
   </spell>
-  <spell name="astral_disruption" rank="4" variable="true">
+  <spell name="astral_disruption" rank="4">
     <resource name="aura" amount="140" cost="fixed"/>
   </spell>
   <spell name="seduction" rank="5" parameters="u" los="true">
@@ -422,7 +422,7 @@
     <resource name="aura" amount="1" cost="fixed"/>
     <resource name="permaura" amount="1" cost="fixed"/>
   </spell>
-  <spell name="eternal_walls" rank="5" parameters="b" ship="true" variable="true">
+  <spell name="eternal_walls" rank="5" parameters="b" ship="true">
     <resource name="aura" amount="50" cost="fixed"/>
     <resource name="permaura" amount="1" cost="fixed"/>
   </spell>
diff --git a/res/translations/messages.de.po b/res/translations/messages.de.po
index 2cd71ecbc..f4a70e083 100644
--- a/res/translations/messages.de.po
+++ b/res/translations/messages.de.po
@@ -1161,7 +1161,7 @@ msgid "spyreport_faction"
 msgstr "\"$unit($target) gehört der Partei $faction($faction) an.\""
 
 msgid "ship_drift"
-msgstr "\"Die $ship($ship) treibt nach $direction($dir).\""
+msgstr "\"Die $ship($ship) hat zu wenig Segler und treibt nach $direction($dir).\""
 
 msgid "error_max_magicians"
 msgstr "\"$unit($unit) in $region($region): '$order($command)' - Es kann maximal $int($amount) Magier pro Partei geben.\""
diff --git a/res/translations/messages.en.po b/res/translations/messages.en.po
index 1ab2b1608..0e1d32a51 100644
--- a/res/translations/messages.en.po
+++ b/res/translations/messages.en.po
@@ -1161,7 +1161,7 @@ msgid "spyreport_faction"
 msgstr "\"$unit($target) belongs to $faction($faction).\""
 
 msgid "ship_drift"
-msgstr "\"The ship $ship($ship) drifts to the $direction($dir).\""
+msgstr "\"The ship $ship($ship) needs more sailors and drifts to the $direction($dir).\""
 
 msgid "error_max_magicians"
 msgstr "\"$unit($unit) in $region($region): '$order($command)' - There may not be more than $int($amount) magicians in your faction.\""
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 76764f0bd..f14303e6c 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -92,7 +92,6 @@ set (PARSER_SRC
 
 set (ERESSEA_SRC
   vortex.c
-  academy.c
   alchemy.c
   automate.c
   battle.c
@@ -168,6 +167,7 @@ set(SERVER_SRC
   bindings.c
   console.c
   helpers.c
+  signals.c
   main.c
   )
 
@@ -179,8 +179,10 @@ set (SERVER_SRC ${SERVER_SRC}
 )
 endif(CURSES_FOUND)
 
-find_program(IWYU_PATH NAMES include-what-you-use iwyu)
-if(NOT IWYU_PATH)
+#find_program(IWYU_PATH NAMES include-what-you-use iwyu)
+if(IWYU_PATH)
+  #  set(C_INCLUDE_WHAT_YOU_USE "${IWYU_PATH} -Xiwyu --no_fwd_decls")
+else(IWYU_PATH)
 	message(STATUS "Could not find the program include-what-you-use")
 endif()
 
@@ -212,7 +214,6 @@ target_link_libraries(eressea
 )
 
 set(TESTS_SRC
-  academy.test.c
   alchemy.test.c
   automate.test.c
   battle.test.c
diff --git a/src/academy.c b/src/academy.c
deleted file mode 100644
index 630552b10..000000000
--- a/src/academy.c
+++ /dev/null
@@ -1,22 +0,0 @@
-#include "platform.h"
-#include "kernel/config.h"
-#include <kernel/unit.h>
-#include <kernel/building.h>
-#include <kernel/item.h>
-#include <kernel/pool.h>
-
-#include "academy.h"
-#include "study.h"
-
-void academy_teaching_bonus(struct unit *u, skill_t sk, int students) {
-    if (students > 0 && sk != NOSKILL) {
-        /* actually students * EXPERIENCEDAYS / MAX_STUDENTS */
-        learn_skill(u, sk, students);
-    }
-}
-
-bool academy_can_teach(unit *teacher, unit *scholar, skill_t sk) {
-    const struct building_type *btype = bt_find("academy");
-    return (active_building(scholar, btype)); 
-}
-
diff --git a/src/academy.h b/src/academy.h
deleted file mode 100644
index 3c496d8fa..000000000
--- a/src/academy.h
+++ /dev/null
@@ -1,16 +0,0 @@
-#ifndef H_ACADEMY
-#define H_ACADEMY
-
-#include <skill.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-    struct unit;
-    void academy_teaching_bonus(struct unit *u, skill_t sk, int academy);
-    bool academy_can_teach(struct unit *teacher, struct unit *scholar, skill_t sk);
-#ifdef __cplusplus
-}
-#endif
-#endif
diff --git a/src/academy.test.c b/src/academy.test.c
deleted file mode 100644
index 6f26cee93..000000000
--- a/src/academy.test.c
+++ /dev/null
@@ -1,52 +0,0 @@
-#include <platform.h>
-
-#include "academy.h"
-#include "skill.h"
-
-#include <kernel/config.h>
-#include <kernel/building.h>
-#include <kernel/faction.h>
-#include <kernel/unit.h>
-#include <kernel/item.h>
-#include <kernel/region.h>
-
-#include <CuTest.h>
-#include "tests.h"
-
-static void test_academy(CuTest * tc)
-{
-    faction *f;
-    unit *u, *u2;
-    region *r;
-    building *b;
-    const item_type *it_silver;
-
-    test_setup();
-    config_set_int("skills.cost.alchemy", 100);
-    r = test_create_region(0, 0, NULL);
-    f = test_create_faction(NULL);
-    u = test_create_unit(f, r);
-    b = test_create_building(r, test_create_buildingtype("academy"));
-    u2 = test_create_unit(f, r);
-    it_silver = test_create_silver();
-
-    CuAssert(tc, "teacher must be in academy", !academy_can_teach(u, u2, SK_CROSSBOW));
-    u_set_building(u, b);
-    CuAssert(tc, "student must be in academy", !academy_can_teach(u, u2, SK_CROSSBOW));
-    u_set_building(u2, b);
-    CuAssert(tc, "student must have 50 silver", !academy_can_teach(u, u2, SK_CROSSBOW));
-    i_change(&u2->items, it_silver, 50);
-    CuAssert(tc, "building must be maintained", !academy_can_teach(u, u2, SK_CROSSBOW));
-    b->flags |= BLD_MAINTAINED;
-    CuAssert(tc, "building must have capacity", !academy_can_teach(u, u2, SK_CROSSBOW));
-    b->size = 2;
-    CuAssertTrue(tc, academy_can_teach(u, u2, SK_CROSSBOW));
-    test_teardown();
-}
-
-CuSuite *get_academy_suite(void)
-{
-    CuSuite *suite = CuSuiteNew();
-    SUITE_ADD_TEST(suite, test_academy);
-    return suite;
-}
diff --git a/src/alchemy.c b/src/alchemy.c
index bc66cb931..bd4220bdb 100644
--- a/src/alchemy.c
+++ b/src/alchemy.c
@@ -73,7 +73,6 @@ void herbsearch(unit * u, int max_take)
     int herbsfound;
     const item_type *whichherb;
     int effsk = effskill(u, SK_HERBALISM, NULL);
-    int herbs = rherbs(r);
 
     if (effsk == 0) {
         cmistake(u, u->thisorder, 59, MSG_PRODUCE);
@@ -91,13 +90,10 @@ void herbsearch(unit * u, int max_take)
         return;
     }
 
-    if (max_take < herbs) {
-        herbs = max_take;
-    }
     herbsfound = ntimespprob(effsk * u->number,
         (double)rherbs(r) / 100.0F, -0.01F);
 
-    if (herbsfound > herbs) herbsfound = herbs;
+    if (herbsfound > max_take) herbsfound = max_take;
     rsetherbs(r, rherbs(r) - herbsfound);
 
     if (herbsfound) {
diff --git a/src/automate.c b/src/automate.c
index ecb130fff..24657f893 100644
--- a/src/automate.c
+++ b/src/automate.c
@@ -6,6 +6,8 @@
 #include "kernel/order.h"
 #include "kernel/region.h"
 #include "kernel/unit.h"
+#include "kernel/pool.h"
+#include "kernel/item.h"
 
 #include "util/keyword.h"
 #include "util/log.h"
@@ -210,7 +212,12 @@ void do_autostudy(region *r)
                 autostudy_run(scholars, nscholars);
                 for (i = 0; i != nscholars; ++i) {
                     int days = STUDYDAYS * scholars[i].learn;
-                    learn_skill(scholars[i].u, skill, days);
+                    int money = learn_skill(scholars[i].u, skill, days, 0);
+                    if (money > 0) {
+                        use_pooled(u, get_resourcetype(R_SILVER), GET_DEFAULT, money);
+                        ADDMSG(&u->faction->msgs, msg_message("studycost",
+                            "unit region cost skill", u, u->region, money, skill));
+                    }
                 }
             }
         }
diff --git a/src/battle.c b/src/battle.c
index 7e3373019..2d45c6df8 100644
--- a/src/battle.c
+++ b/src/battle.c
@@ -899,7 +899,7 @@ void drain_exp(struct unit *u, int n)
         }
     }
     if (sk != NOSKILL) {
-        reduce_skill_days(u, sk, n);
+        change_skill_days(u, sk, -n);
     }
 }
 
diff --git a/src/bindings.c b/src/bindings.c
index 6d2481a29..453c29d08 100755
--- a/src/bindings.c
+++ b/src/bindings.c
@@ -310,18 +310,6 @@ static int tolua_create_curse(lua_State * L)
     return 1;
 }
 
-static int tolua_learn_skill(lua_State * L)
-{
-    unit *u = (unit *)tolua_tousertype(L, 1, 0);
-    const char *skname = tolua_tostring(L, 2, 0);
-    int days = (int)tolua_tonumber(L, 3, 0);
-    skill_t sk = findskill(skname);
-    if (sk != NOSKILL) {
-        learn_skill(u, sk, days);
-    }
-    return 0;
-}
-
 static int tolua_update_scores(lua_State * L)
 {
     UNUSED_ARG(L);
@@ -973,7 +961,6 @@ int tolua_bindings_open(lua_State * L, const dictionary *inifile)
         tolua_function(L, TOLUA_CAST "remove_empty_units", tolua_remove_empty_units);
         tolua_function(L, TOLUA_CAST "update_scores", tolua_update_scores);
         tolua_function(L, TOLUA_CAST "update_owners", tolua_update_owners);
-        tolua_function(L, TOLUA_CAST "learn_skill", tolua_learn_skill);
         tolua_function(L, TOLUA_CAST "create_curse", tolua_create_curse);
         tolua_function(L, TOLUA_CAST "translate", &tolua_translate);
         tolua_function(L, TOLUA_CAST "spells", tolua_get_spells);
diff --git a/src/creport.c b/src/creport.c
index 4fb9a50b2..551ee7fa9 100644
--- a/src/creport.c
+++ b/src/creport.c
@@ -1363,9 +1363,11 @@ static void cr_output_region(FILE * F, report_context * ctx, region * r)
                     fprintf(F, "%d;Rekruten\n", rpeasants(r) / RECRUITFRACTION);
                 }
                 if (max_production(r)) {
-                    int p_wage = wage(r, NULL, NULL, turn + 1);
+                    /* Im CR steht der Bauernlohn, der bei Trauer nur 10 ist */
+                    bool mourn = is_mourning(r, turn);
+                    int p_wage = peasant_wage(r, mourn);
                     fprintf(F, "%d;Lohn\n", p_wage);
-                    if (is_mourning(r, turn + 1)) {
+                    if (mourn) {
                         fputs("1;mourning\n", F);
                     }
                 }
diff --git a/src/economy.c b/src/economy.c
index d7a1b9373..fd87ee20c 100644
--- a/src/economy.c
+++ b/src/economy.c
@@ -1993,7 +1993,8 @@ expandwork(region * r, econ_request * work_begin, econ_request * work_end, int m
     /* n: verbleibende Einnahmen */
     /* fishes: maximale Arbeiter */
     int jobs = maxwork;
-    int p_wage = wage(r, NULL, NULL, turn);
+    bool mourn = is_mourning(r, turn);
+    int p_wage = peasant_wage(r, mourn);
     int money = rmoney(r);
     if (total > 0 && !rule_autowork()) {
         econ_request *o;
@@ -2017,7 +2018,7 @@ expandwork(region * r, econ_request * work_begin, econ_request * work_end, int m
 
                 assert(workers >= 0);
 
-                u->n = workers * wage(u->region, u->faction, u_race(u), turn);
+                u->n = workers * wage(u->region, u_race(u));
 
                 jobs -= workers;
                 assert(jobs >= 0);
@@ -2061,7 +2062,7 @@ static int work_cmd(unit * u, order * ord, econ_request ** io_req)
             }
             return 0;
         }
-        w = wage(r, u->faction, u_race(u), turn);
+        w = wage(r, u_race(u));
         add_request(req++, ECON_WORK, u, ord, w * u->number);
         *io_req = req;
         return u->number;
diff --git a/src/items/xerewards.c b/src/items/xerewards.c
index e0ce80802..25b1acc0b 100644
--- a/src/items/xerewards.c
+++ b/src/items/xerewards.c
@@ -37,7 +37,7 @@ struct order *ord)
         skill *sv = u->skills;
         while (sv != u->skills + u->skill_size) {
             /* only one person learns for 3 weeks */
-            learn_skill(u, (skill_t)sv->id, STUDYDAYS * 3);
+            change_skill_days(u, (skill_t)sv->id, STUDYDAYS * 3);
             ++sv;
         }
     }
diff --git a/src/items/xerewards.test.c b/src/items/xerewards.test.c
index d135b27fb..c8bda3f04 100644
--- a/src/items/xerewards.test.c
+++ b/src/items/xerewards.test.c
@@ -52,17 +52,17 @@ static void test_skillpotion(CuTest *tc) {
     itype = test_create_itemtype("skillpotion");
     change_resource(u, itype->rtype, 2);
 
-    learn_skill(u, SK_ENTERTAINMENT, STUDYDAYS * u->number);
+    change_skill_days(u, SK_ENTERTAINMENT, STUDYDAYS * u->number);
     pSkill = unit_skill(u, SK_ENTERTAINMENT);
     sk_set(pSkill, 5);
     initialWeeks_Entertainment = pSkill->weeks = 4;
 
-    learn_skill(u, SK_STAMINA, STUDYDAYS * u->number);
+    change_skill_days(u, SK_STAMINA, STUDYDAYS * u->number);
     pSkill = unit_skill(u, SK_STAMINA);
     sk_set(pSkill, 5);
     initialWeeks_Stamina = pSkill->weeks = 4;
 
-    learn_skill(u, SK_MAGIC, STUDYDAYS * u->number);
+    change_skill_days(u, SK_MAGIC, STUDYDAYS * u->number);
     pSkill = unit_skill(u, SK_MAGIC);
     sk_set(pSkill, 5);
     initialWeeks_Magic = pSkill->weeks = 4;
diff --git a/src/kernel/building.c b/src/kernel/building.c
index a2283c1cc..f93f28948 100644
--- a/src/kernel/building.c
+++ b/src/kernel/building.c
@@ -495,20 +495,22 @@ extern struct attrib_type at_icastle;
 /** returns the building's build stage (NOT size in people).
  * only makes sense for castles or similar buildings with multiple
  * stages */
-int buildingeffsize(const building * b, int img)
+int buildingeffsize(const building * b, bool imaginary)
 {
     const struct building_type *btype = NULL;
 
     if (b == NULL)
         return 0;
 
-    btype = b->type;
-    if (img) {
+    if (imaginary) {
         const attrib *a = a_find(b->attribs, &at_icastle);
         if (a) {
             btype = (const struct building_type *)a->data.v;
         }
     }
+    else {
+        btype = b->type;
+    }
     return bt_effsize(btype, b, b->size);
 }
 
@@ -518,7 +520,7 @@ int bt_effsize(const building_type * btype, const building * b, int bsize)
         bsize = adjust_size(b, bsize);
     }
 
-    if (btype->stages) {
+    if (btype && btype->stages) {
         int n = 0;
         const building_stage *stage = btype->stages;
         do {
@@ -728,7 +730,7 @@ static const int wagetable[7][3] = {
 };
 
 static int
-default_wage(const region * r, const faction * f, const race * rc, int in_turn)
+default_wage(const region * r, const race * rc)
 {
     building *b = largestbuilding(r, cmp_wage, false);
     int esize = 0;
@@ -739,25 +741,23 @@ default_wage(const region * r, const faction * f, const race * rc, int in_turn)
         esize = buildingeffsize(b, false);
     }
 
-    if (f != NULL) {
+    if (rc != NULL) {
+        static const struct race *rc_orc, *rc_snotling;
+        static int rc_cache;
         int index = 0;
-        if (rc == get_race(RC_ORC) || rc == get_race(RC_SNOTLING)) {
+        if (rc_changed(&rc_cache)) {
+            rc_orc = get_race(RC_ORC);
+            rc_snotling = get_race(RC_SNOTLING);
+        }
+        if (rc == rc_orc || rc == rc_snotling) {
             index = 1;
         }
         wage = wagetable[esize][index];
     }
     else {
-        if (is_mourning(r, in_turn)) {
-            wage = 10;
-        }
-        else if (fval(r->terrain, SEA_REGION)) {
-            wage = 11;
-        }
-        else {
-            wage = wagetable[esize][2];
-        }
-        if (r->attribs && rule_blessed_harvest() == HARVEST_WORK) {
-            /* E1 rules */
+        wage = wagetable[esize][2];
+        if (rule_blessed_harvest() & HARVEST_WORK) {
+            /* Ge�ndert in E3 */
             wage += harvest_effect(r);
         }
     }
@@ -766,7 +766,7 @@ default_wage(const region * r, const faction * f, const race * rc, int in_turn)
         attrib *a;
         curse *c;
         variant vm;
-
+     
         /* Godcurse: Income -10 */
         vm = frac_make(wage, 1);
 
@@ -786,31 +786,37 @@ default_wage(const region * r, const faction * f, const race * rc, int in_turn)
 }
 
 static int
-minimum_wage(const region * r, const faction * f, const race * rc, int in_turn)
+minimum_wage(const region * r, const race * rc)
 {
-    if (f && rc) {
+    if (rc) {
         return rc->maintenance;
     }
-    return default_wage(r, f, rc, in_turn);
+    return default_wage(r, rc);
 }
 
 /**
  * Gibt Arbeitslohn fuer entsprechende Rasse zurueck, oder fuer
- * die Bauern wenn f == NULL. */
-int wage(const region * r, const faction * f, const race * rc, int in_turn)
+ * die Bauern wenn rc == NULL. */
+int wage(const region * r, const race * rc)
 {
     static int config;
     static int rule_wage;
     if (config_changed(&config)) {
         rule_wage = config_get_int("rules.wage.function", 1);
     }
-    if (rule_wage==0) {
+    if (rule_wage == 0) {
         return 0;
     }
-    if (rule_wage==1) {
-        return default_wage(r, f, rc, in_turn);
+
+    if (rule_wage == 1) {
+        return default_wage(r, rc);
     }
-    return minimum_wage(r, f, rc, in_turn);
+    return minimum_wage(r, rc);
+}
+
+int peasant_wage(const struct region *r, bool mourn)
+{
+    return mourn ? 10 : wage(r, NULL);
 }
 
 int cmp_wage(const struct building *b, const building * a)
diff --git a/src/kernel/building.h b/src/kernel/building.h
index 74128a036..dfe1b544a 100644
--- a/src/kernel/building.h
+++ b/src/kernel/building.h
@@ -116,8 +116,8 @@ extern "C" {
         int id, int size, struct order *ord);
     bool building_finished(const struct building *b);
 
-    int wage(const struct region *r, const struct faction *f,
-        const struct race *rc, int in_turn);
+    int wage(const struct region *r, const struct race *rc);
+    int peasant_wage(const struct region *r, bool mourn);
 
     typedef int(*cmp_building_cb) (const struct building * b,
         const struct building * a);
@@ -130,7 +130,7 @@ extern "C" {
     int building_taxes(const building *b);
 
     /* old functions, still in build.c: */
-    int buildingeffsize(const building * b, int imaginary);
+    int buildingeffsize(const building * b, bool imaginary);
     void bhash(struct building *b);
     void bunhash(struct building *b);
     int buildingcapacity(const struct building *b);
diff --git a/src/kernel/building.test.c b/src/kernel/building.test.c
index daa75b496..91806ba3d 100644
--- a/src/kernel/building.test.c
+++ b/src/kernel/building.test.c
@@ -434,6 +434,80 @@ static void test_cmp_castle_size(CuTest *tc) {
     test_teardown();
 }
 
+static void test_wage(CuTest *tc) {
+    region *r;
+    building *b;
+    building_type *btype;
+    struct building_stage *stage;
+    race *rc_orc, *rc_elf;
+    test_setup();
+    rc_orc = test_create_race("orc");
+    rc_elf = test_create_race("elf");
+    rc_elf->maintenance = 13;
+    btype = test_create_buildingtype("castle");
+    stage = btype->stages;
+    stage->construction->maxsize = 2; /* site */
+    stage = stage->next = calloc(1, sizeof(struct building_stage));
+    stage->construction = calloc(1, sizeof(struct construction));
+    stage->construction->maxsize = 8; /* tradepost */
+    stage = stage->next = calloc(1, sizeof(struct building_stage));
+    stage->construction = calloc(1, sizeof(struct construction));
+    stage->construction->maxsize = 40; /* fortification */
+    stage = stage->next = calloc(1, sizeof(struct building_stage));
+    stage->construction = calloc(1, sizeof(struct construction));
+    stage->construction->maxsize = 200; /* fortification */
+    r = test_create_plain(0, 0);
+    CuAssertIntEquals(tc, 10, wage(r, rc_elf));
+    CuAssertIntEquals(tc, 10, wage(r, rc_orc));
+    CuAssertIntEquals(tc, 11, peasant_wage(r, false));
+    CuAssertIntEquals(tc, 10, peasant_wage(r, true));
+
+    b = test_create_building(r, btype);
+    b->size = 1;
+    CuAssertIntEquals(tc, 0, buildingeffsize(b, false));
+    CuAssertIntEquals(tc, 10, wage(r, rc_elf));
+    CuAssertIntEquals(tc, 10, wage(r, rc_orc));
+    CuAssertIntEquals(tc, 11, peasant_wage(r, false));
+    CuAssertIntEquals(tc, 10, peasant_wage(r, true));
+    b->size = 2;
+    CuAssertIntEquals(tc, 1, buildingeffsize(b, false));
+    b->size = 9;
+    CuAssertIntEquals(tc, 1, buildingeffsize(b, false));
+    CuAssertIntEquals(tc, 10, wage(r, rc_elf));
+    CuAssertIntEquals(tc, 10, wage(r, rc_orc));
+    CuAssertIntEquals(tc, 11, peasant_wage(r, false));
+    CuAssertIntEquals(tc, 10, peasant_wage(r, true));
+    b->size = 10;
+    CuAssertIntEquals(tc, 2, buildingeffsize(b, false));
+    b->size = 49;
+    CuAssertIntEquals(tc, 2, buildingeffsize(b, false));
+    CuAssertIntEquals(tc, 11, wage(r, rc_elf));
+    CuAssertIntEquals(tc, 11, wage(r, rc_orc));
+    CuAssertIntEquals(tc, 12, peasant_wage(r, false));
+    CuAssertIntEquals(tc, 10, peasant_wage(r, true));
+    b->size = 50;
+    CuAssertIntEquals(tc, 3, buildingeffsize(b, false));
+    b->size = 249;
+    CuAssertIntEquals(tc, 3, buildingeffsize(b, false));
+    CuAssertIntEquals(tc, 12, wage(r, rc_elf));
+    CuAssertIntEquals(tc, 11, wage(r, rc_orc));
+    CuAssertIntEquals(tc, 13, peasant_wage(r, false));
+    CuAssertIntEquals(tc, 10, peasant_wage(r, true));
+    b->size = 250;
+    CuAssertIntEquals(tc, 4, buildingeffsize(b, false));
+    CuAssertIntEquals(tc, 13, wage(r, rc_elf));
+    CuAssertIntEquals(tc, 12, wage(r, rc_orc));
+    CuAssertIntEquals(tc, 14, peasant_wage(r, false));
+    CuAssertIntEquals(tc, 10, peasant_wage(r, true));
+    config_set_int("rules.wage.function", 1);
+    CuAssertIntEquals(tc, 13, wage(r, rc_elf));
+    config_set_int("rules.wage.function", 0);
+    CuAssertIntEquals(tc, 0, wage(r, rc_elf));
+    config_set_int("rules.wage.function", 2);
+    CuAssertIntEquals(tc, rc_elf->maintenance, wage(r, rc_elf));
+    test_teardown();
+}
+
 static void test_cmp_wage(CuTest *tc) {
     region *r;
     building *b1, *b2;
@@ -619,6 +693,7 @@ CuSuite *get_building_suite(void)
     SUITE_ADD_TEST(suite, test_cmp_castle_size);
     SUITE_ADD_TEST(suite, test_cmp_taxes);
     SUITE_ADD_TEST(suite, test_cmp_wage);
+    SUITE_ADD_TEST(suite, test_wage);
     SUITE_ADD_TEST(suite, test_cmp_current_owner);
     SUITE_ADD_TEST(suite, test_register_building);
     SUITE_ADD_TEST(suite, test_btype_defaults);
diff --git a/src/kernel/config.h b/src/kernel/config.h
index 9b10f1520..4ee59406b 100644
--- a/src/kernel/config.h
+++ b/src/kernel/config.h
@@ -42,7 +42,7 @@ extern "C" {
     bool rule_stealth_anon(void);  /* units can anonymize their faction, TARNE PARTEI [NICHT] */
     int rule_alliance_limit(void);
     int rule_faction_limit(void);
-#define HARVEST_WORK  0x00
+#define HARVEST_WORK  0x02
 #define HARVEST_TAXES 0x01
     int rule_blessed_harvest(void);
 #define GIVE_SELF 1
diff --git a/src/kernel/region.c b/src/kernel/region.c
index 6f700ae03..bd5b66287 100644
--- a/src/kernel/region.c
+++ b/src/kernel/region.c
@@ -602,7 +602,7 @@ int rpeasants(const region * r)
     return value;
 }
 
-void rsetpeasants(region * r, int value)
+int rsetpeasants(region * r, int value)
 {
     assert(r->land || value==0);
     assert(value >= 0);
@@ -612,7 +612,9 @@ void rsetpeasants(region * r, int value)
             value = USHRT_MAX;
         }
         r->land->peasants = (unsigned short)value;
+        return r->land->peasants;
     }
+    return 0;
 }
 
 int rmoney(const region * r)
@@ -746,17 +748,16 @@ int rsettrees(const region * r, int ageclass, int value)
 {
     if (!r->land) {
         assert(value == 0);
+        return 0;
+    }
+    assert(value >= 0);
+    if (value < MAXTREES) {
+        r->land->trees[ageclass] = value;
     }
     else {
-        assert(value >= 0);
-        if (value <= MAXTREES) {
-            return r->land->trees[ageclass] = value;
-        }
-        else {
-            r->land->trees[ageclass] = MAXTREES;
-        }
+        r->land->trees[ageclass] = MAXTREES;
     }
-    return 0;
+    return r->land->trees[ageclass];
 }
 
 region *region_create(int uid)
@@ -1095,11 +1096,10 @@ void init_region(region *r)
 
     if (!fval(r, RF_CHAOTIC)) {
         int peasants;
+        int p_wage = 1 + peasant_wage(r, false) + rng_int() % 5;
         peasants = (region_maxworkers(r) * (20 + dice(6, 10))) / 100;
         if (peasants < 100) peasants = 100;
-        rsetpeasants(r, peasants);
-        rsetmoney(r, rpeasants(r) * ((wage(r, NULL, NULL,
-            INT_MAX) + 1) + rng_int() % 5));
+        rsetmoney(r, rsetpeasants(r, peasants) * p_wage);
     }
 }
 
diff --git a/src/kernel/region.h b/src/kernel/region.h
index 89b240f1b..9a7563712 100644
--- a/src/kernel/region.h
+++ b/src/kernel/region.h
@@ -176,7 +176,7 @@ extern "C" {
     int rsettrees(const struct region *r, int ageclass, int value);
 
     int rpeasants(const struct region *r);
-    void rsetpeasants(struct region *r, int value);
+    int rsetpeasants(struct region *r, int value);
     int rmoney(const struct region *r);
     void rsetmoney(struct region *r, int value);
     int rhorses(const struct region *r);
diff --git a/src/kernel/resources.c b/src/kernel/resources.c
index 17cec5ea2..6c93a0a13 100644
--- a/src/kernel/resources.c
+++ b/src/kernel/resources.c
@@ -118,22 +118,34 @@ static void terraform_default(struct rawmaterial *res, const region * r)
 }
 
 static int visible_default(const rawmaterial * res, int skilllevel)
-/* resources are visible, if skill equals minimum skill to mine them
+/* resources are visible if skill equals minimum skill to mine them
  * plus current level of difficulty */
 {
     const struct item_type *itype = res->rtype->itype;
+    int level = res->level + itype->construction->minskill - 1;
     if (res->level <= 1
-        && res->level + itype->construction->minskill <= skilllevel + 1) {
+        && level <= skilllevel) {
         assert(res->amount > 0);
         return res->amount;
     }
-    else if (res->level + itype->construction->minskill <= skilllevel + 2) {
+    else if (level < skilllevel) {
         assert(res->amount > 0);
         return res->amount;
     }
     return -1;
 }
 
+static int visible_half_skill(const rawmaterial * res, int skilllevel)
+/* resources are visible if skill equals half as much as normal */
+{
+    const struct item_type *itype = res->rtype->itype;
+    int level = res->level + itype->construction->minskill - 1;
+    if (2 * skilllevel >= level) {
+        return res->amount;
+    }
+    return -1;
+}
+
 static void use_default(rawmaterial * res, const region * r, int amount)
 {
     assert(res->amount > 0 && amount >= 0 && amount <= res->amount);
@@ -171,13 +183,19 @@ struct rawmaterial_type *rmt_get(const struct resource_type *rtype)
 struct rawmaterial_type *rmt_create(struct resource_type *rtype)
 {
     if (!rtype->raw) {
+        int rule = config_get_int("resource.visibility.rule", 0);
         rawmaterial_type *rmtype = rtype->raw = malloc(sizeof(rawmaterial_type));
         if (!rmtype) abort();
         rmtype->rtype = rtype;
         rmtype->terraform = terraform_default;
         rmtype->update = NULL;
         rmtype->use = use_default;
-        rmtype->visible = visible_default;
+        if (rule == 0) {
+            rmtype->visible = visible_default;
+        }
+        else {
+            rmtype->visible = visible_half_skill;
+        }
     }
     return rtype->raw;
 }
diff --git a/src/kernel/version.c b/src/kernel/version.c
index 8c694f7f0..0739b11b1 100644
--- a/src/kernel/version.c
+++ b/src/kernel/version.c
@@ -8,7 +8,7 @@
 
 #ifndef ERESSEA_VERSION
 /* the version number, if it was not passed to make with -D */
-#define ERESSEA_VERSION "3.25.0"
+#define ERESSEA_VERSION "3.26.0"
 #endif
 
 const char *eressea_version(void) {
diff --git a/src/laws.c b/src/laws.c
index 3d3d174b5..8ee1e9341 100644
--- a/src/laws.c
+++ b/src/laws.c
@@ -743,7 +743,8 @@ void immigration(void)
         /* if less than 50 are in the region and there is space and no monster or demon units in the region */
         if (repopulate) {
             int peasants = rpeasants(r);
-            int income = wage(r, NULL, NULL, turn) - maintenance_cost(NULL) + 1;
+            bool mourn = is_mourning(r, turn);
+            int income = peasant_wage(r, mourn) - maintenance_cost(NULL) + 1;
             if (income >= 0 && r->land && (peasants < repopulate) && region_maxworkers(r) >(peasants + 30) * 2) {
                 int badunit = 0;
                 unit *u;
@@ -754,7 +755,7 @@ void immigration(void)
                     }
                 }
                 if (badunit == 0) {
-                    peasants += (int)(rng_double()*income);
+                    peasants += (int)(rng_double() * income);
                     rsetpeasants(r, peasants);
                 }
             }
@@ -834,8 +835,10 @@ void demographics(void)
 
                 if (r->age > 20) {
                     double mwp = fmax(region_maxworkers(r), 1);
+                    bool mourn = is_mourning(r, turn);
+                    int p_wage = peasant_wage(r, mourn);
                     double prob =
-                        pow(rpeasants(r) / (mwp * wage(r, NULL, NULL, turn) * 0.13), 4.0)
+                        pow(rpeasants(r) / (mwp * p_wage * 0.13), 4.0)
                         * PLAGUE_CHANCE;
 
                     if (rng_double() < prob) {
diff --git a/src/lighthouse.c b/src/lighthouse.c
index 7915bdf44..257836d20 100644
--- a/src/lighthouse.c
+++ b/src/lighthouse.c
@@ -21,7 +21,12 @@ attrib_type at_lighthouse = {
 
 bool is_lighthouse(const building_type *btype)
 {
-    return is_building_type(btype, "lighthouse");
+    static int config;
+    static const building_type *bt_lighthouse;
+    if (bt_changed(&config)) {
+        bt_lighthouse = bt_find("lighthouse");
+    }
+    return btype == bt_lighthouse;
 }
 
 /* update_lighthouse: call this function whenever the size of a lighthouse changes
@@ -81,7 +86,7 @@ void remove_lighthouse(const building *lh) {
 
 int lighthouse_range(const building * b)
 {
-    if (b->size >= 10 && (b->flags & BLD_MAINTAINED)) {
+    if (b->size >= 10) {
         return (int)log10(b->size) + 1;
     }
     return 0;
@@ -112,13 +117,19 @@ bool lighthouse_guarded(const region * r)
     for (a = a_find(r->attribs, &at_lighthouse); a && a->type == &at_lighthouse;
         a = a->next) {
         building *b = (building *)a->data.v;
-
-        assert(is_building_type(b->type, "lighthouse"));
-        if ((b->flags & BLD_MAINTAINED) && b->size >= 10) {
-            int maxd = (int)log10(b->size) + 1;
-            int d = distance(r, b->region);
-            assert(maxd >= d);
-            return true;
+        if (b->flags & BLD_MAINTAINED) {
+            if (r == b->region) {
+                return true;
+            }
+            else {
+                int maxd = lighthouse_range(b);
+                if (maxd > 0) {
+                    int d = distance(r, b->region);
+                    if (maxd >= d) {
+                        return true;
+                    }
+                }
+            }
         }
     }
 
diff --git a/src/lighthouse.test.c b/src/lighthouse.test.c
index 9dc6d2999..8230b97f2 100644
--- a/src/lighthouse.test.c
+++ b/src/lighthouse.test.c
@@ -30,8 +30,6 @@ static void test_lighthouse_range(CuTest * tc)
     b->size = 9;
     CuAssertIntEquals(tc, 0, lighthouse_range(b));
     b->size = 10;
-    CuAssertIntEquals(tc, 0, lighthouse_range(b));
-    b->flags |= BLD_MAINTAINED;
     CuAssertIntEquals(tc, 2, lighthouse_range(b));
     u1->building = b;
     u2->building = b;
@@ -39,6 +37,8 @@ static void test_lighthouse_range(CuTest * tc)
     set_level(u1, SK_PERCEPTION, 3);
     set_level(u2, SK_PERCEPTION, 3);
 
+    CuAssertIntEquals(tc, 0, lighthouse_view_distance(b, u1));
+    b->flags |= BLD_MAINTAINED;
     CuAssertIntEquals(tc, 1, lighthouse_view_distance(b, u1));
     set_level(u1, SK_PERCEPTION, 6);
     CuAssertIntEquals(tc, 1, lighthouse_view_distance(b, u2));
@@ -126,6 +126,7 @@ static void test_lighthouse_guard(CuTest * tc) {
     CuAssertIntEquals(tc, true, lighthouse_guarded(r3));
     CuAssertIntEquals(tc, false, lighthouse_guarded(r4));
     b->size = 1; /* size can go down in destroy_cmd */
+    update_lighthouse(b);
     CuAssertIntEquals(tc, false, lighthouse_guarded(r2));
     CuAssertIntEquals(tc, false, lighthouse_guarded(r3));
     test_teardown();
diff --git a/src/main.c b/src/main.c
index 130a2f6f7..9cf76f262 100644
--- a/src/main.c
+++ b/src/main.c
@@ -17,6 +17,7 @@
 #include "gmtool.h"
 #endif
 
+#include "signals.h"
 #include "bindings.h"
 
 #include <iniparser.h>
@@ -247,39 +248,6 @@ static int parse_args(int argc, char **argv)
     return 0;
 }
 
-#ifdef HAVE_BACKTRACE
-#include <execinfo.h>
-#include <signal.h>
-static void *btrace[50];
-
-static void report_segfault(int signo, siginfo_t * sinf, void *arg)
-{
-    size_t size;
-    int fd = fileno(stderr);
-
-    fflush(stdout);
-    fputs("\n\nProgram received SIGSEGV, backtrace follows.\n", stderr);
-    size = backtrace(btrace, 50);
-    backtrace_symbols_fd(btrace, size, fd);
-    abort();
-}
-
-static int setup_signal_handler(void)
-{
-    struct sigaction act;
-
-    act.sa_flags = SA_RESETHAND | SA_SIGINFO;
-    act.sa_sigaction = report_segfault;
-    sigfillset(&act.sa_mask);
-    return sigaction(SIGSEGV, &act, NULL);
-}
-#else
-static int setup_signal_handler(void)
-{
-    return 0;
-}
-#endif
-
 void locale_init(void)
 {
     setlocale(LC_CTYPE, "");
diff --git a/src/modules/museum.c b/src/modules/museum.c
index 8f8350655..cbaf8d3c0 100644
--- a/src/modules/museum.c
+++ b/src/modules/museum.c
@@ -124,7 +124,7 @@ order * ord)
     UNUSED_ARG(amount);
 
     /* Pruefen ob in Eingangshalle */
-    if (u->region->x != 9525 || u->region->y != 9525) {
+    if (warden == NULL || u->region->x != 9525 || u->region->y != 9525) {
         cmistake(u, ord, 266, MSG_MAGIC);
         return 0;
     }
diff --git a/src/monsters.c b/src/monsters.c
index bd156238b..148e6f46d 100644
--- a/src/monsters.c
+++ b/src/monsters.c
@@ -974,7 +974,11 @@ void spawn_undead(void)
 {
     region *r;
     faction *monsters = get_monsters();
+    int spawn_chance = config_get_int("monsters.spawn.chance", 100) * 100;
 
+    if (spawn_chance <= 0) {
+        return;
+    }
     for (r = regions; r; r = r->next) {
         int unburied = deathcount(r);
 
@@ -985,7 +989,7 @@ void spawn_undead(void)
         }
 
         if (r->land && unburied > rpeasants(r) / 20
-            && rng_int() % 10000 < 100) {
+            && rng_int() % spawn_chance < 100) {
             message *msg;
             unit *u;
             /* es ist sinnfrei, wenn irgendwo im Wald 3er-Einheiten Untote entstehen.
diff --git a/src/move.c b/src/move.c
index 38cc0d8bb..e9bbea9a0 100644
--- a/src/move.c
+++ b/src/move.c
@@ -1612,9 +1612,9 @@ static const region_list *travel_route(unit * u,
         /* Berichte ueber Durchreiseregionen */
 
         if (mode != TRAVEL_TRANSPORTED) {
+            arg_regions ar;
             arg_regions *arp = NULL;
             if (steps > 1) {
-                arg_regions ar;
                 arp = &ar;
                 var_create_regions(arp, route_begin, steps - 1);
             }
@@ -2192,8 +2192,8 @@ void move_cmd_ex(unit * u, order * ord, const char *directions)
         init_order(ord, u->faction->locale);
     }
     if (u->ship && u == ship_owner(u->ship)) {
-        bool drifting = (getkeyword(ord) == K_MOVE);
-        sail(u, ord, drifting);
+        keyword_t kwd = getkeyword(ord);
+        sail(u, ord, (kwd == K_MOVE || kwd == K_ROUTE));
     }
     else {
         travel(u, ord);
diff --git a/src/report.c b/src/report.c
index f283b552f..09b9ab82e 100644
--- a/src/report.c
+++ b/src/report.c
@@ -1182,11 +1182,13 @@ static void report_statistics(struct stream *out, const region * r, const factio
     if (max_production(r) && (!fval(r->terrain, SEA_REGION)
         || f->race == get_race(RC_AQUARIAN))) {
         if (markets_module()) {     /* hack */
+            bool mourn = is_mourning(r, turn);
+            int p_wage = peasant_wage(r, mourn);
             m =
-                msg_message("nr_stat_salary_new", "max", wage(r, NULL, NULL, turn + 1));
+                msg_message("nr_stat_salary_new", "max", p_wage);
         }
         else {
-            m = msg_message("nr_stat_salary", "max", wage(r, f, f->race, turn + 1));
+            m = msg_message("nr_stat_salary", "max", wage(r, f->race));
         }
         nr_render(m, f->locale, buf, sizeof(buf), f);
         paragraph(out, buf, 2, 2, 0);
@@ -1337,7 +1339,7 @@ report_template(const char *filename, report_context * ctx, const char *bom)
                     }
                     rps_nowrap(out, buf);
                     newline(out);
-                    sprintf(buf, "; ECheck Lohn %d", wage(r, f, f->race, turn + 1));
+                    sprintf(buf, "; ECheck Lohn %d", wage(r, f->race));
                     rps_nowrap(out, buf);
                     newline(out);
                     newline(out);
diff --git a/src/signals.c b/src/signals.c
new file mode 100644
index 000000000..7cbe680df
--- /dev/null
+++ b/src/signals.c
@@ -0,0 +1,35 @@
+#ifdef HAVE_BACKTRACE
+#include <execinfo.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+static void *btrace[50];
+
+static void report_segfault(int signo, siginfo_t * sinf, void *arg)
+{
+    size_t size;
+    int fd = fileno(stderr);
+
+    fflush(stdout);
+    fputs("\n\nProgram received SIGSEGV, backtrace follows.\n", stderr);
+    size = backtrace(btrace, 50);
+    backtrace_symbols_fd(btrace, size, fd);
+    abort();
+}
+
+int setup_signal_handler(void)
+{
+    struct sigaction act;
+
+    act.sa_flags = SA_RESETHAND | SA_SIGINFO;
+    act.sa_sigaction = report_segfault;
+    sigfillset(&act.sa_mask);
+    return sigaction(SIGSEGV, &act, 0);
+}
+#else
+int setup_signal_handler(void)
+{
+    return 0;
+}
+#endif
+
diff --git a/src/signals.h b/src/signals.h
new file mode 100644
index 000000000..640a766f7
--- /dev/null
+++ b/src/signals.h
@@ -0,0 +1,4 @@
+#pragma once
+
+int setup_signal_handler(void);
+
diff --git a/src/study.c b/src/study.c
index 74f3d108c..e41d9185d 100644
--- a/src/study.c
+++ b/src/study.c
@@ -8,7 +8,6 @@
 #include "move.h"
 #include "monsters.h"
 #include "alchemy.h"
-#include "academy.h"
 #include "kernel/calendar.h"
 
 #include <spells/regioncurse.h>
@@ -154,37 +153,37 @@ const attrib_type at_learning = {
 
 #define EXPERIENCEDAYS 10
 
-static int study_days(unit * scholar, skill_t sk)
+static int study_days(unit * u, skill_t sk)
 {
     int speed = STUDYDAYS;
-    if (u_race(scholar)->study_speed) {
-        speed += u_race(scholar)->study_speed[sk];
+    if (u_race(u)->study_speed) {
+        speed += u_race(u)->study_speed[sk];
         if (speed < STUDYDAYS) {
-            skill *sv = unit_skill(scholar, sk);
+            skill *sv = unit_skill(u, sk);
             if (sv == 0) {
                 speed = STUDYDAYS;
             }
         }
     }
-    return scholar->number * speed;
+    return u->number * speed;
 }
 
 static int
-teach_unit(unit * teacher, unit * scholar, int nteaching, skill_t sk,
+teach_unit(unit * teacher, unit * student, int nteaching, skill_t sk,
     bool report, int *academy_students)
 {
     teaching_info *teach = NULL;
     attrib *a;
     int students;
 
-    if (magic_lowskill(scholar)) {
+    if (magic_lowskill(student)) {
         cmistake(teacher, teacher->thisorder, 292, MSG_EVENT);
         return 0;
     }
 
-    students = scholar->number;
+    students = student->number;
     /* subtract already taught students */
-    a = a_find(scholar->attribs, &at_learning);
+    a = a_find(student->attribs, &at_learning);
     if (a != NULL) {
         teach = (teaching_info *)a->data.v;
         students -= teach->students;
@@ -194,18 +193,16 @@ teach_unit(unit * teacher, unit * scholar, int nteaching, skill_t sk,
 
     if (students > 0) {
         if (teach == NULL) {
-            a = a_add(&scholar->attribs, a_new(&at_learning));
+            a = a_add(&student->attribs, a_new(&at_learning));
             teach = (teaching_info *)a->data.v;
         }
         selist_push(&teach->teachers, teacher);
         teach->days += students * STUDYDAYS;
         teach->students += students; 
 
-        if (scholar->building) {
-            /* Solange Akademien groessenbeschraenkt sind, sollte Lehrer und
-             * Student auch in unterschiedlichen Gebaeuden stehen duerfen */
-            /* FIXME comment contradicts implementation */
-            if (academy_can_teach(teacher, scholar, sk)) {
+        if (student->building) {
+            const struct building_type *btype = bt_find("academy");
+            if (active_building(student, btype)) {
                 /* Jeder Schueler zusaetzlich +10 Tage wenn in Uni. */
                 teach->days += students * EXPERIENCEDAYS;  /* learning erhoehen */
                 /* Lehrer zusaetzlich +1 Tag pro Schueler. */
@@ -258,12 +255,12 @@ int teach_cmd(unit * teacher, struct order *ord)
 
     count = 0;
 
+#if TEACH_ALL
     init_order(ord, NULL);
 
-#if TEACH_ALL
     if (getparam(teacher->faction->locale) == P_ANY) {
         skill_t sk;
-        unit *scholar;
+        unit *student;
         skill_t teachskill[MAXSKILLS];
         int t = 0;
 
@@ -272,15 +269,15 @@ int teach_cmd(unit * teacher, struct order *ord)
             teachskill[t] = getskill(teacher->faction->locale);
         } while (sk != NOSKILL);
 
-        for (scholar = r->units; teaching > 0 && scholar; scholar = scholar->next) {
-            if (LongHunger(scholar)) {
+        for (student = r->units; teaching > 0 && student; student = student->next) {
+            if (LongHunger(student)) {
                 continue;
             }
-            else if (scholar->faction == teacher->faction) {
-                if (getkeyword(scholar->thisorder) == K_STUDY) {
+            else if (student->faction == teacher->faction) {
+                if (getkeyword(student->thisorder) == K_STUDY) {
                     /* Input ist nun von student->thisorder !! */
-                    init_order(scholar->thisorder, scholar->faction->locale);
-                    sk = getskill(scholar->faction->locale);
+                    init_order(student->thisorder, student->faction->locale);
+                    sk = getskill(student->faction->locale);
                     if (sk != NOSKILL && teachskill[0] != NOSKILL) {
                         for (t = 0; teachskill[t] != NOSKILL; ++t) {
                             if (sk == teachskill[t]) {
@@ -290,20 +287,20 @@ int teach_cmd(unit * teacher, struct order *ord)
                         sk = teachskill[t];
                     }
                     if (sk != NOSKILL
-                        && effskill_study(teacher, sk) - TEACHDIFFERENCE > effskill_study(scholar, sk)) {
-                        teaching -= teach_unit(teacher, scholar, teaching, sk, true, &academy_students);
+                        && effskill_study(teacher, sk) - TEACHDIFFERENCE > effskill_study(student, sk)) {
+                        teaching -= teach_unit(teacher, student, teaching, sk, true, &academy_students);
                     }
                 }
             }
 #ifdef TEACH_FRIENDS
-            else if (alliedunit(teacher, scholar->faction, HELP_GUARD)) {
-                if (getkeyword(scholar->thisorder) == K_STUDY) {
+            else if (alliedunit(teacher, student->faction, HELP_GUARD)) {
+                if (getkeyword(student->thisorder) == K_STUDY) {
                     /* Input ist nun von student->thisorder !! */
-                    init_order(scholar->thisorder, scholar->faction->locale);
-                    sk = getskill(scholar->faction->locale);
+                    init_order(student->thisorder, student->faction->locale);
+                    sk = getskill(student->faction->locale);
                     if (sk != NOSKILL
-                        && effskill_study(teacher, sk) - TEACHDIFFERENCE >= effskill(scholar, sk, NULL)) {
-                        teaching -= teach_unit(teacher, scholar, teaching, sk, true, &academy_students);
+                        && effskill_study(teacher, sk) - TEACHDIFFERENCE >= effskill(student, sk, NULL)) {
+                        teaching -= teach_unit(teacher, student, teaching, sk, true, &academy_students);
                     }
                 }
             }
@@ -322,15 +319,15 @@ int teach_cmd(unit * teacher, struct order *ord)
 
         while (!parser_end()) {
             skill_t sk;
-            unit *scholar;
+            unit *student;
             bool feedback;
 
-            getunit(r, teacher->faction, &scholar);
+            getunit(r, teacher->faction, &student);
             ++count;
 
             /* Falls die Unit nicht gefunden wird, Fehler melden */
 
-            if (!scholar) {
+            if (!student) {
                 char tbuf[20];
                 const char *uid;
                 const char *token;
@@ -362,8 +359,8 @@ int teach_cmd(unit * teacher, struct order *ord)
                 continue;
             }
 
-            feedback = teacher->faction == scholar->faction
-                || alliedunit(scholar, teacher->faction, HELP_GUARD);
+            feedback = teacher->faction == student->faction
+                || alliedunit(student, teacher->faction, HELP_GUARD);
 
             /* Neuen Befehl zusammenbauen. TEMP-Einheiten werden automatisch in
              * ihre neuen Nummern uebersetzt. */
@@ -371,102 +368,63 @@ int teach_cmd(unit * teacher, struct order *ord)
                 strncat(zOrder, " ", sz - 1);
                 --sz;
             }
-            sz -= str_strlcpy(zOrder + 4096 - sz, itoa36(scholar->no), sz);
+            sz -= str_strlcpy(zOrder + 4096 - sz, itoa36(student->no), sz);
 
-            if (getkeyword(scholar->thisorder) != K_STUDY) {
+            if (getkeyword(student->thisorder) != K_STUDY) {
                 ADDMSG(&teacher->faction->msgs,
-                    msg_feedback(teacher, ord, "teach_nolearn", "student", scholar));
+                    msg_feedback(teacher, ord, "teach_nolearn", "student", student));
                 continue;
             }
 
             /* Input ist nun von student->thisorder !! */
             parser_pushstate();
-            init_order(scholar->thisorder, scholar->faction->locale);
-            sk = getskill(scholar->faction->locale);
+            init_order(student->thisorder, student->faction->locale);
+            sk = getskill(student->faction->locale);
             parser_popstate();
 
             if (sk == NOSKILL) {
                 ADDMSG(&teacher->faction->msgs,
-                    msg_feedback(teacher, ord, "teach_nolearn", "student", scholar));
+                    msg_feedback(teacher, ord, "teach_nolearn", "student", student));
                 continue;
             }
 
-            if (effskill_study(scholar, sk) > effskill_study(teacher, sk)
+            if (effskill_study(student, sk) > effskill_study(teacher, sk)
                 - TEACHDIFFERENCE) {
                 if (feedback) {
                     ADDMSG(&teacher->faction->msgs, msg_feedback(teacher, ord, "teach_asgood",
-                        "student", scholar));
+                        "student", student));
                 }
                 continue;
             }
             if (sk == SK_MAGIC) {
                 /* ist der Magier schon spezialisiert, so versteht er nur noch
                  * Lehrer seines Gebietes */
-                magic_t mage2 = unit_get_magic(scholar);
+                magic_t mage2 = unit_get_magic(student);
                 if (mage2 != M_GRAY) {
                     magic_t mage1 = unit_get_magic(teacher);
                     if (mage1 != mage2) {
                         if (feedback) {
                             ADDMSG(&teacher->faction->msgs, msg_feedback(teacher, ord,
-                                "error_different_magic", "target", scholar));
+                                "error_different_magic", "target", student));
                         }
                         continue;
                     }
                 }
             }
             sk_academy = sk;
-            teaching -= teach_unit(teacher, scholar, teaching, sk, false, &academy_students);
+            teaching -= teach_unit(teacher, student, teaching, sk, false, &academy_students);
         }
         new_order = create_order(K_TEACH, teacher->faction->locale, "%s", zOrder);
         replace_order(&teacher->orders, ord, new_order);
         free_order(new_order);      /* parse_order & set_order have each increased the refcount */
     }
-    if (academy_students > 0 && sk_academy!=NOSKILL) {
-        academy_teaching_bonus(teacher, sk_academy, academy_students);
+    if (academy_students > 0 && sk_academy != NOSKILL) {
+        change_skill_days(teacher, sk_academy, academy_students);
     }
     reset_order();
     return 0;
 }
 
-typedef enum study_rule_t {
-    STUDY_DEFAULT = 0,
-    STUDY_FASTER = 1,
-    STUDY_AUTOTEACH = 2
-} study_rule_t;
-
-static double study_speedup(unit * u, skill_t s, study_rule_t rule)
-{
-#define MINTURN 16
-    if (turn > MINTURN) {
-        if (rule == STUDY_FASTER) {
-            double learnweeks = 0;
-            int i;
-            for (i = 0; i != u->skill_size; ++i) {
-                skill *sv = u->skills + i;
-                if (sv->id == s) {
-                    learnweeks = sv->level * (sv->level + 1) / 2.0;
-                    if (learnweeks < turn / 3.0) {
-                        return 2.0;
-                    }
-                }
-            }
-            return 2.0; /* If the skill was not found it is the first study. */
-        }
-        if (rule == STUDY_AUTOTEACH) {
-            double learnweeks = 0;
-            int i;
-            for (i = 0; i != u->skill_size; ++i) {
-                skill *sv = u->skills + i;
-                learnweeks += (sv->level * (sv->level + 1) / 2.0);
-            }
-            if (learnweeks < turn / 2.0) {
-                return 2.0;
-            }
-        }
-    }
-    return 1.0;
-}
-
 static bool ExpensiveMigrants(void)
 {
 	static bool rule;
@@ -552,17 +510,12 @@ bool check_student(const struct unit *u, struct order *ord, skill_t sk) {
 
 int study_cmd(unit * u, order * ord)
 {
-    region *r = u->region;
-    int p;
-    int l;
     int studycost, days;
-    double multi = 1.0;
     attrib *a = NULL;
     teaching_info *teach = NULL;
     int money = 0;
     skill_t sk;
     int maxalchemy = 0;
-    int speed_rule = (study_rule_t)config_get_int("study.speedup", 0);
     static const race *rc_snotling;
     static int rc_cache;
 
@@ -585,7 +538,6 @@ int study_cmd(unit * u, order * ord)
         }
     }
 
-    p = studycost = study_cost(u, sk);
     a = a_find(u->attribs, &at_learning);
     if (a != NULL) {
         teach = (teaching_info *)a->data.v;
@@ -594,16 +546,13 @@ int study_cmd(unit * u, order * ord)
     /* keine kostenpflichtigen Talente fuer Migranten. Vertraute sind
      * keine Migranten, wird in is_migrant abgefangen. Vorsicht,
      * studycost darf hier noch nicht durch Akademie erhoeht sein */
+    studycost = study_cost(u, sk);
+
     if (studycost > 0 && !ExpensiveMigrants() && is_migrant(u)) {
         ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "error_migrants_nolearn",
             ""));
         return -1;
     }
-    /* Akademie: */
-    if (active_building(u, bt_find("academy"))) {
-        studycost = studycost * 2;
-        if (studycost < 50) studycost = 50;
-    }
 
     if (sk == SK_MAGIC) {
         magic_t mtype;
@@ -684,71 +633,31 @@ int study_cmd(unit * u, order * ord)
             }
         }
     }
+
+    days = teach ? teach->days : 0;
+    days += study_days(u, sk);
+
     if (studycost) {
         int cost = studycost * u->number;
         money = get_pooled(u, get_resourcetype(R_SILVER), GET_DEFAULT, cost);
         if (money > cost) money = cost;
-    }
-    if (money < studycost * u->number) {
-        studycost = p;              /* Ohne Univertreurung */
-        if (money > studycost) money = studycost;
-        if (p > 0 && money < studycost * u->number) {
-            cmistake(u, ord, 65, MSG_EVENT);
-            multi = money / (double)(studycost * u->number);
+        if (money < studycost * u->number) {
+            int cost = studycost * u->number;
+            if (money > studycost) money = studycost;
+            if (money < cost) {
+                /* we cannot afford everyone, but use as much as we have */
+                cmistake(u, ord, 65, MSG_EVENT);
+                days = days * money / cost;
+            }
         }
     }
+    money += learn_skill(u, sk, days, studycost);
 
-    if (teach == NULL) {
-        a = a_add(&u->attribs, a_new(&at_learning));
-        teach = (teaching_info *)a->data.v;
-        assert(teach);
-        teach->teachers = NULL;
-    }
     if (money > 0) {
         use_pooled(u, get_resourcetype(R_SILVER), GET_DEFAULT, money);
         ADDMSG(&u->faction->msgs, msg_message("studycost",
             "unit region cost skill", u, u->region, money, sk));
     }
-
-    if (get_effect(u, oldpotiontype[P_WISE])) {
-        l = get_effect(u, oldpotiontype[P_WISE]);
-        if (l > u->number) l = u->number;
-        teach->days += l * EXPERIENCEDAYS;
-        change_effect(u, oldpotiontype[P_WISE], -l);
-    }
-    if (get_effect(u, oldpotiontype[P_FOOL])) {
-        l = get_effect(u, oldpotiontype[P_FOOL]);
-        if (l > u->number) l = u->number;
-        teach->days -= l * STUDYDAYS;
-        change_effect(u, oldpotiontype[P_FOOL], -l);
-    }
-
-    if (p != studycost) {
-        /* ist_in_gebaeude(r, u, BT_UNIVERSITAET) == 1) { */
-        /* p ist Kosten ohne Uni, studycost mit; wenn
-         * p!=studycost, ist die Einheit zwangsweise
-         * in einer Uni */
-        teach->days += u->number * EXPERIENCEDAYS;
-    }
-
-    if (is_cursed(r->attribs, &ct_badlearn)) {
-        teach->days -= u->number * EXPERIENCEDAYS;
-    }
-
-    multi *= study_speedup(u, sk, speed_rule);
-    days = study_days(u, sk);
-    days = (int)((days + teach->days) * multi);
-
-    /* the artacademy currently improves the learning of entertainment
-       of all units in the region, to be able to make it cumulative with
-       with an academy */
-
-    if (sk == SK_ENTERTAINMENT
-        && buildingtype_exists(r, bt_find("artacademy"), false)) {
-        days *= 2;
-    }
-
-    learn_skill(u, sk, days);
     if (a != NULL) {
         if (teach->teachers) {
             msg_teachers(teach->teachers, u, sk);
@@ -789,56 +698,133 @@ void produceexp_ex(struct unit *u, skill_t sk, int n, learn_fun learn)
     }
 }
 
-void produceexp(struct unit *u, skill_t sk, int n)
-{
-    produceexp_ex(u, sk, n, learn_skill);
-}
-
 static learn_fun inject_learn_fun = 0;
 
 void inject_learn(learn_fun fun) {
     inject_learn_fun = fun;
 }
 
-/** days should be scaled by u->number; STUDYDAYS * u->number is one week worth of learning */
-void learn_skill(unit *u, skill_t sk, int days) {
-    int leveldays = STUDYDAYS * u->number;
-    int weeks = 0;
+static void increase_skill_days(unit *u, skill_t sk, int days) {
+    assert(sk >= 0 && sk < MAXSKILLS && days >= 0);
+    if (days > 0) {
+        int leveldays = STUDYDAYS * u->number;
+        int weeks = 0;
+        if (inject_learn_fun) {
+            inject_learn_fun(u, sk, days);
+            return;
+        }
+        while (days >= leveldays) {
+            ++weeks;
+            days -= leveldays;
+        }
+        if (days > 0 && rng_int() % leveldays < days) {
+            ++weeks;
+        }
+        if (weeks > 0) {
+            increase_skill(u, sk, weeks);
+        }
+    }
+}
 
-    if (fval(u, UFL_HUNGER)) {
-        days /= 2;
+static void reduce_skill_days(unit *u, skill_t sk, int days) {
+    if (days > 0) {
+        skill *sv = unit_skill(u, sk);
+        if (sv) {
+            while (days > 0) {
+                if (days >= STUDYDAYS * u->number) {
+                    reduce_skill(u, sv, 1);
+                    days -= STUDYDAYS;
+                }
+                else {
+                    if (chance(days / ((double)STUDYDAYS * u->number))) /* (rng_int() % (30 * u->number) < days)*/
+                        reduce_skill(u, sv, 1);
+                    days = 0;
+                }
+            }
+        }
     }
-    assert(sk >= 0 && sk < MAXSKILLS);
-    if (inject_learn_fun) {
-        inject_learn_fun(u, sk, days);
-        return;
+}
+
+void produceexp(struct unit *u, skill_t sk, int n)
+{
+    produceexp_ex(u, sk, n, increase_skill_days);
+}
+
+/** 
+ * days should be scaled by u->number; STUDYDAYS * u->number is one week worth of learning
+ * @return int
+ *   The additional spend, i.e. from an academy.
+ */
+int learn_skill(unit *u, skill_t sk, int days, int studycost) {
+    region *r = u->region;
+    int cost = 0;
+
+    if (r->buildings) {
+        static const building_type *bt_artacademy;
+        static const building_type *bt_academy;
+        static int config;
+
+        if (bt_changed(&config)) {
+            bt_artacademy = bt_find("artacademy");
+            bt_academy = bt_find("academy");
+        }
+
+        /* Akademie: */
+        if (bt_academy && active_building(u, bt_academy)) {
+            int avail, n = u->number;
+            if (studycost < 50) studycost = 50;
+            cost = studycost * u->number;
+            avail = get_pooled(u, get_resourcetype(R_SILVER), GET_DEFAULT, studycost * u->number);
+            if (avail < cost) {
+                /* not all students can afford the academy */
+                n = n * avail / cost;
+                cost = n * studycost;
+            }
+            days += EXPERIENCEDAYS * n;
+        }
+
+        /* the artacademy currently improves the learning of entertainment
+           of all units in the region, to be able to make it cumulative with
+           with an academy */
+
+        if (bt_artacademy && sk == SK_ENTERTAINMENT
+            && buildingtype_exists(r, bt_artacademy, false)) {
+            days *= 2;
+        }
     }
-    while (days >= leveldays) {
-        ++weeks;
-        days -= leveldays;
+
+    if (u->attribs) {
+        if (get_effect(u, oldpotiontype[P_WISE])) {
+            int effect = get_effect(u, oldpotiontype[P_WISE]);
+            if (effect > u->number) effect = u->number;
+            days += effect * EXPERIENCEDAYS;
+            change_effect(u, oldpotiontype[P_WISE], -effect);
+        }
+        if (get_effect(u, oldpotiontype[P_FOOL])) {
+            int effect = get_effect(u, oldpotiontype[P_FOOL]);
+            if (effect > u->number) effect = u->number;
+            days -= effect * STUDYDAYS;
+            change_effect(u, oldpotiontype[P_FOOL], -effect);
+        }
     }
-    if (days > 0 && rng_int() % leveldays < days) {
-        ++weeks;
+
+    if (is_cursed(r->attribs, &ct_badlearn)) {
+        days -= EXPERIENCEDAYS * u->number;
     }
-    if (weeks > 0) {
-        increase_skill(u, sk, weeks);
+
+    if (fval(u, UFL_HUNGER)) {
+        days /= 2;
     }
+    increase_skill_days(u, sk, days);
+    return cost;
 }
 
-void reduce_skill_days(unit *u, skill_t sk, int days) {
-    skill *sv = unit_skill(u, sk);
-    if (sv) {
-        while (days > 0) {
-            if (days >=  STUDYDAYS * u->number) {
-                reduce_skill(u, sv, 1);
-                days -= STUDYDAYS;
-            }
-            else {
-                if (chance (days / ((double) STUDYDAYS * u->number))) /* (rng_int() % (30 * u->number) < days)*/
-                    reduce_skill(u, sv, 1);
-                days = 0;
-            }
-        }
+void change_skill_days(unit *u, skill_t sk, int days) {
+    if (days < 0) {
+        reduce_skill_days(u, sk, -days);
+    }
+    else {
+        increase_skill_days(u, sk, days);
     }
 }
 
@@ -885,7 +871,7 @@ void demon_skillchange(unit *u)
                 }
             }
             else {
-                learn_skill(u, sv->id, STUDYDAYS * u->number * weeks);
+                change_skill_days(u, sv->id, STUDYDAYS * u->number * weeks);
             }
         }
         ++sv;
diff --git a/src/study.h b/src/study.h
index 6fed0c50d..dcdc354ca 100644
--- a/src/study.h
+++ b/src/study.h
@@ -34,8 +34,8 @@ extern "C" {
 
     typedef void(*learn_fun)(struct unit *u, skill_t sk, int days);
 
-    void learn_skill(struct unit *u, skill_t sk, int days);
-    void reduce_skill_days(struct unit *u, skill_t sk, int days);
+    int learn_skill(struct unit *u, skill_t sk, int days, int studycost);
+    void change_skill_days(struct unit *u, skill_t sk, int days);
 
     void produceexp(struct unit *u, skill_t sk, int n);
     void produceexp_ex(struct unit *u, skill_t sk, int n, learn_fun learn);
diff --git a/src/study.test.c b/src/study.test.c
index b57c921f4..6b529b252 100644
--- a/src/study.test.c
+++ b/src/study.test.c
@@ -364,14 +364,14 @@ void test_learn_skill_single(CuTest *tc) {
     setup_study();
     config_set("study.random_progress", "0");
     u = test_create_unit(test_create_faction(NULL), test_create_region(0, 0, NULL));
-    learn_skill(u, SK_ALCHEMY, STUDYDAYS);
+    CuAssertIntEquals(tc, 0, learn_skill(u, SK_ALCHEMY, STUDYDAYS, 0));
     CuAssertPtrNotNull(tc, sv = u->skills);
     CuAssertIntEquals(tc, SK_ALCHEMY, sv->id);
     CuAssertIntEquals(tc, 1, sv->level);
     CuAssertIntEquals(tc, 2, sv->weeks);
-    learn_skill(u, SK_ALCHEMY, STUDYDAYS);
+    CuAssertIntEquals(tc, 0, learn_skill(u, SK_ALCHEMY, STUDYDAYS, 0));
     CuAssertIntEquals(tc, 1, sv->weeks);
-    learn_skill(u, SK_ALCHEMY, STUDYDAYS * 2);
+    CuAssertIntEquals(tc, 0, learn_skill(u, SK_ALCHEMY, STUDYDAYS * 2, 0));
     CuAssertIntEquals(tc, 2, sv->level);
     CuAssertIntEquals(tc, 2, sv->weeks);
     test_teardown();
@@ -385,14 +385,14 @@ void test_learn_skill_multi(CuTest *tc) {
     config_set("study.random_progress", "0");
     u = test_create_unit(test_create_faction(NULL), test_create_region(0, 0, NULL));
     scale_number(u, 10);
-    learn_skill(u, SK_ALCHEMY, STUDYDAYS * u->number);
+    CuAssertIntEquals(tc, 0, learn_skill(u, SK_ALCHEMY, STUDYDAYS * u->number, 0));
     CuAssertPtrNotNull(tc, sv = u->skills);
     CuAssertIntEquals(tc, SK_ALCHEMY, sv->id);
     CuAssertIntEquals(tc, 1, sv->level);
     CuAssertIntEquals(tc, 2, sv->weeks);
-    learn_skill(u, SK_ALCHEMY, STUDYDAYS * u->number);
+    CuAssertIntEquals(tc, 0, learn_skill(u, SK_ALCHEMY, STUDYDAYS * u->number, 0));
     CuAssertIntEquals(tc, 1, sv->weeks);
-    learn_skill(u, SK_ALCHEMY, STUDYDAYS  * u->number * 2);
+    CuAssertIntEquals(tc, 0, learn_skill(u, SK_ALCHEMY, STUDYDAYS  * u->number * 2, 0));
     CuAssertIntEquals(tc, 2, sv->level);
     CuAssertIntEquals(tc, 2, sv->weeks);
     test_teardown();
diff --git a/src/test_eressea.c b/src/test_eressea.c
index fc5c49d1c..452b847c5 100644
--- a/src/test_eressea.c
+++ b/src/test_eressea.c
@@ -93,7 +93,6 @@ int RunAllTests(int argc, char *argv[])
     /* items */
     ADD_SUITE(xerewards);
     /* kernel */
-    ADD_SUITE(academy);
     ADD_SUITE(alliance);
     ADD_SUITE(ally);
     ADD_SUITE(building);
diff --git a/src/util/rand.c b/src/util/rand.c
index a14f1def4..6e4e88c89 100644
--- a/src/util/rand.c
+++ b/src/util/rand.c
@@ -64,7 +64,7 @@ bool chance(double x)
 {
     if (x >= 1.0)
         return true;
-    return (1-rng_double()) < x;
+    return rng_double() < x;
 }
 
 typedef struct random_source {