The best kittens, technology, and video games blog in the world.

Sunday, April 26, 2009

Arduino and Wii Nunchuck to control iTunes


Here's my latest project. It involves way too many technologies for the sole purpose of controlling iTunes with Wii Nunchuck. Signal flow is almost uni-directional, so let's go from step by step from the Nunchuck to iTunes.

Wii Nunchuck and I2C

I2C is a very simple protocol back from the 80s. There are four wires. Vcc (traditionally +5V, but lower voltages are used with I2C too) and ground are obvious. Clock and data lines require some more explanation.

I2C is a bus protocol - so multiple devices can operate on the same wires. Even in the simplest configuration we have a master (Arduino board), and a slave (Wii Nunchuck), both of which can read and write to the same lines. And it doesn't take much science to know that when one device writes 0 while other writes 1 to the same line hilarity ensues, also known as a circuit shorted, everyone dies.

The way I2C and many other protocols solve this problem is by using "open drain" design. In open drain no device is allowed to write 1 to the bus - you can only write 0 (connect to the ground), or leave the connection in high impedance state. The bus is connected to Vcc using a pull-up resistor. This way if any device writes 0, bus will be at 0. If none is writing, it will be at 1. That's somewhat unintuitive at first, if it confuses you just check the Internet for explanation.

Fortunately Arduino has hardware support for I2C over its analog input pins 4 and 5, and library for that (called Wire) is included in the distribution. So it would seem that we don't have anything to do... except it all doesn't work.

Now I'm not 100% sure about that, but here's my guess what happens. Arduino analog input pins can be set to output low, output high, high impedance, and high impedance with pull-up (allegedly 20k). For I2C we're interested in output low, and high impedance with pull-up. However - this built-in pull-up seems too weak, or it's not working for some other reason. Directly connecting clock and data lines to Vcc via 10k pull-up resistors (lower resistance = more pull-up, at least until you fry it) makes it all work. Nice way to spend an entire night, isn't it?

So after cutting the Wii Nunchuck wire, the connections are:
  • Nunchuck white (GND) to Arduino GND
  • Nunchuck red (VCC) to Arduino 5V
  • Nunchuck yellow (clock) to Arduino analog input pin 5
  • Nunchuck green (data) to Arduino analog input pin 4
  • Clock line to VCC via 10k resistor
  • Data line to VCC via 10k resistor

Speaking with Wii Nunchuck

Now that we have electric signals handled, we need to engage Wii Nunchuck in a meaningful conversation. It has device ID 0x52 (82), and we need to write 0x40 0x00 (64 0) to initialize it, and then request data in packets of six bytes, and write 0x00 (0) to confirm we got it.

Now I try to avoid word like "retarded" on this blog, but Wii Nunchuck data is "encrypted", and we need to "decrypt" it first:
  • decrypted = (0x17 XOR encrypted) + 0x17
Seriously, whoever came up with this fully deserves this adjective.

After that it's just a straightforward decoding, and we dump data onto serial interface. Here's full code:
#define CPU_FREQ 16000000L
#define TWI_FREQ 100000L

#include <Wire.h>

int diode = HIGH;

void blink() {
digitalWrite(13, diode);
diode = (diode == HIGH) ? LOW : HIGH;
}

void setup() {
Serial.begin(19200);
pinMode(13, OUTPUT);
Wire.begin();
Wire.beginTransmission(0x52);
Wire.send(0x40);
Wire.send(0x00);
Wire.endTransmission();
}

void send_zero() {
Wire.beginTransmission(0x52);
Wire.send(0x00);
Wire.endTransmission();
}

void loop() {
int cnt = 0;
uint8_t outbuf[6];

Wire.requestFrom(0x52, 6);
while(Wire.available()) {
if(cnt < 6) {
outbuf[cnt] = (0x17 ^ Wire.receive()) + 0x17;
}
cnt++;
}

int b = ~outbuf[5] & 3;
int joy_x = outbuf[0]-125;
int joy_y = outbuf[1]-128;
int accel_x = (outbuf[2] << 2 | ((outbuf[5] >> 2) & 0x03)) - 500;
int accel_y = (outbuf[3] << 2 | ((outbuf[5] >> 4) & 0x03)) - 488;
int accel_z = (outbuf[4] << 2 | ((outbuf[5] >> 6) & 0x03)) - 504;

Serial.print("B=");
Serial.print(b);
Serial.print(" XY=");
Serial.print(joy_x);
Serial.print(",");
Serial.print(joy_y);
Serial.print(" XYZ=");
Serial.print(accel_x);
Serial.print(",");
Serial.print(accel_y);
Serial.print(",");
Serial.print(accel_z);
Serial.print("\n");

send_zero();

blink();
delay(50);
}


Controlling iTunes


iTunes is an outrageously bad music player, but at least it has one redeeming quality of being controllable from command line via AppleScript.

By issuing silly commands like
tell application iTunes
next track
end tell
we can control its basic functionality, what will be good enough for us.

We also need to get some information from iTunes. It doesn't have "increase/decrease volume" commands, so we need to find current volume, and then set it to higher/lower values. It also doesn't have a single play/pause command, so we need to figure out what state we're in - if we're playing then pause, otherwise play. Some of the nasty code refactored to OSA class to keep ITunes class a bit cleaner, but not by much.

The code doesn't depend on the rest of the project, so you can use it in your own projects, or just look what it's doing and use it directly from command line.

class OSA
def get(var)
`osascript -e 'tell application "#{name}" to #{var}'`
end
def do!(cmd)
system 'osascript', '-e', %Q[tell application "#{name}"], '-e', cmd, '-e', 'end tell'
end
end

class ITunes < OSA
def name
'iTunes'
end
def get_volume
get('sound volume as integer').to_i
end
def get_state
get('player state as string').chomp
end
def set_volume(v)
system 'osascript', '-e', %Q[tell application "iTunes" to set sound volume to #{v}]
end
def next!
do! 'next track'
end
def prev!
do! 'previous track'
end
def pause!
do! 'pause'
end
def play!
do! 'play'
end
def pause_flip!
if get_state == 'playing'
pause!
else
play!
end
end
def vol_up!
set_volume(get_volume + 5)
end
def vol_down!
set_volume(get_volume - 5)
end
end


Reading data from Arduino


Communication is strictly uni-directional. Arduino writes to virtual serial interface, and we're reading from it as the data arrives. The data is current position of buttons, analog stick, and accelerometer. We're not really interested in any of it directly - we care about button presses and releases, and about stick going up/down/left/right (analog stick is used only as fake D-pad here). So change of state, not state as such.

For buttons it's straight forward. We get a bit for each button, so we just check it. For the stick, there's a small trickery involved. The range on each axis is from about -100 to +100, so we could just set points like +64/-64 from which we count it as a up/down. But what if stick is hold around that level? Sensor is analog (and user's hand is shaking), so if user hold the stick around +64, the reading would go +64, +63, +65, +63, +64, +64, +65 ..., what such naive implementation would treat as plenty of up presses, even though there weren't any.

This is an extremely common problem in electronics, and there's a simple solution - hysteresis. We simply take two slightly different levels for two sides of the same transition. If stick goes above +64 it's up. If it goes below +48 it's neutral. If it's betweer +48 and +64, we just keep it where it was. So unless the user has Parkinsons's and wiggles it a lot, it won't register any spurious clicks.

Other than that the code is pretty straightforward. We throw away first five readings because electronics tend to take some time after power-up to stabilize. And with every reading we set state to what we read, put all transitions into @cur_events instance variable, and yield.

require 'rubygems'
require 'serialport'

class SerialDevice
def get_device
while true
dev = Dir["/dev/cu.usb*"][0]
return dev if dev
sleep 1
end
end
def initialize
@sp = SerialPort.new(get_device, 19200)
end
end

class Nunchuck < SerialDevice
attr_reader :cur_events, :b, :sx, :sy, :sxs, :sys
attr_accessor :ax, :ay, :az
def initialize
super
@b,@sx,@sy,@ax,@ay,@az,@sxs,@sxs = 0,0,0,0,0,0,0,0
end
def each_state
skip = 5
while line = @sp.readline
unless line =~ /B=(\d) XY=(\S+),(\S+) XYZ=(\S+),(\S+),(\S+)/
puts line
next
end
if skip > 0
skip -= 1
next
end
@cur_events = []
self.b, self.sx, self.sy, self.ax, self.ay, self.az = [$1,$2,$3,$4,$5,$6].map{|x| x.to_i}
yield
end
end
def b=(nv)
@cur_events << :press_lo if nv & ~@b & 1 != 0
@cur_events << :release_lo if @b & ~nv & 1 != 0
@cur_events << :press_hi if nv & ~@b & 2 != 0
@cur_events << :release_hi if @b & ~nv & 2 != 0
@b = nv
end
def hysteresis(new_measurement, old_state, *ranges)
while true
state, bounds = ranges.shift, ranges.shift
if bounds.nil? or
(new_measurement < bounds.first) or
(new_measurement < bounds.last and old_state <= state)
return state
end
end
end
def sxs=(nv)
@cur_events << :sxs_change if nv != @sxs
@sxs = nv
end
def sys=(nv)
@cur_events << :sys_change if nv != @sys
@sys = nv
end
def sx=(nv)
self.sxs = hysteresis(nv, sxs, -1, -64..-48, 0, 48..64, 1)
@sx = nv
end
def sy=(nv)
self.sys = hysteresis(nv, sys, -1, -64..-48, 0, 48..64, 1)
@sy = nv
end
def raw
[@b,@sx,@sy,@ax,@ay,@az]
end
end


All of it together


The final bit of code is trivial. We just instantiate ITunes and Nunchuck objects, and connect them in obvious way:
  • Press C to play/pause
  • Analog stick right/left for next/previous
  • Analog stick up/down to increase/decrease volume
This style of volume control sucks, but if I tried to run two osascript commands on every reading (if stick is above +64 increase volume, if under -64 decrease volume), it would be too slow, at least in this naive implementation, and you'd only see there would be a pretty big lag between moving the stick and iTunes reacting.

i = ITunes.new
n = Nunchuck.new
n.each_state do
if n.cur_events.include?(:sxs_change)
if n.sxs == 1
i.next!
elsif n.sxs == -1
i.prev!
end
end
if n.cur_events.include?(:sys_change)
if n.sys == 1
i.vol_up!
elsif n.sys == -1
i.vol_down!
end
end
if n.cur_events.include?(:press_hi)
i.pause_flip!
end
p(n.raw + n.cur_events)
end

Tuesday, April 14, 2009

Wicked Cool Ruby Scripts are not so wicked cool

I got a review copy of Wicked Cool Ruby Scripts by Steve Pugh. I had my hopes up, as I quite enjoy reading cookbook-style books on programming - they are digestible in small pieces, what works great with my Internet-induced attention deficit, and they are mines of useful tidbits of programming knowledge.

Unfortunately Wicked Cool Ruby Scripts didn't do it for me. The subtitle says "Useful scripts that solve difficult problems", but most of the scripts were targeting trivial toy problems instead, like playing rock, paper, scissors with a computer (why would anybody do that), or reimplementing grep... not terribly useful. I'd say maybe 20% of the scripts do something useful that isn't a one-liner.

Now that on its own wouldn't be enough to give the book a bad review - I might have written a few Library-of-Congress-fuls of Ruby scripts already, so basics are obviously boring to me, but there are more beginners around than people like me, so beginner books are very useful to them. But there's a second problem that bothers me a lot - it's not said anywhere but the book is clearly targeted at Windows system administrators. The scripts instead of following Unix conventions like input from STDIN and arguments, output to STDOUT, errors to STDERR and so on, ask for all the input interactively or load it from predefined files, dump errors on STDOUT, and save output to predefined files.

For example here's a script which prints all IP addresses between a starting and ending one. They way it's implemented in the book is:

class IP
# Code here is perfectly fine
end

print "Input Starting IP Address"
start_ip = gets.strip

print "Input Ending IP Address: "
end_ip = gets.strip

i = IP.new(start_ip)

ofile = File.open("ips.txt", "w")
ofile.puts i.succ! until i == end_ip
ofile.close


But that's horrible! This script is only useful for anything when manually operated. The entire point of scripts is that they can be building blocks of bigger scripts!

The proper way would be:
class IP
# ...
end

raise "Usage: #{$0} start_ip end_ip" unless ARGV.size == 2
start_ip, end_ip = *ARGV

i = IP.new(start_ip)
puts i.succ! until i == end_ip


That is - input from command line arguments, output to stdandard out. Unlike script in the book, this can be used as a building block for something bigger.

I'd say the book was a great idea, but a wasted opportunity. I'd only recommend it for beginner Windows administrators, for everybody else it's either too basic, or teaches some seriously bad practice.

Sunday, April 12, 2009

Arduino microcontroller and Ruby to display song lyrics



I got myself an Arduino, Seeeduino version to be more exact, and I'll build a robot based on it. I'm using the word "robot" very vaguely, I just want to make a bunch of fun projects taking advantage of the microcontroller.

The first non-trivial project I did was displaying lyrics for music played from a computer. I'll get to the interesting bits in due time, let's start with the basics.

Lyrics


The first problem was finding lyrics with timing information. I really wanted it to play Still Alive from Portal, but I couldn't find any timed lyrics. And I was far too lazy to find accurate timing myself. Then I remembered - many Stepmania songs already come with timed lyrics. So I wrote a parser to their LRC format. It is much more complicated than you'd expect - we only have two lines of 16 characters each, and even silly songs are much longer than that. So there's quite a bit of logic necessary to break longer lines into pairs of 16-character fragments, and extrapolate their timing. API for this library is very simple: LRC.new(file_handle).each_line{|top_line, bottom_line, time| ...}.

class Line
attr_reader :text, :start_time, :end_time
def initialize(text, start_time, end_time)
@text, @start_time, @end_time = text, start_time, end_time
end
def to_s
"#{@text} [#{@start_time}..#{@end_time}]"
end
def text_fragments
unless @text_fragments
@text_fragments = []
text = @text.sub(/\|/, ' ')
while text != ""
text.sub!(/\A\s*/, "")
if text.sub!(/\A(.{1,16}(\b|\Z))/, "")
@text_fragments << $1
else text.sub!(/\A(.{1,16})/, "")
@text_fragments << $1
end
end
end
@text_fragments
end
def text_fragment_pairs
pairs = ((text_fragments.size+1) / 2)
pairs = 1 if pairs == 0
pairs
end
def duration
end_time ? end_time - start_time : 2.0
end
def each
fragments = text_fragments
time_shift = duration / text_fragment_pairs
time = start_time
while fragments.size > 0
yield(fragments.shift||"", fragments.shift||"", time)
time += time_shift
end
end
end

class Lrc
def initialize(fh)
entries = []
fh.each{|line|
line.sub!(/\s*\Z/, "")
line =~ /\A\[(.*?)\](.*?)\Z/ or raise "Cannot parse: #{$1}"
time, txt = $1, $2
next unless time =~ /\A(\d+):(\d+\.\d+)\Z/
entries << [$1.to_i*60+$2.to_f, txt]
}
@lines = []
entries.size.times{|i|
cur, nxt = entries[i], entries[i+1]||[nil]
@lines << Line.new(cur[1], cur[0], nxt[0])
}
end
def print!
@lines.each{|x| puts x}
end
def each_line(&blk)
@lines.each{|line|
line.each(&blk)
}
end
end


Seeeduino board


I'll get back to the computer-side software, let's go to the hardware for a moment. Seeeduino board is awesomely easy to use, it uses mini-USB connection for power (it can take battery power too), for uploading programs, and for emulated serial communication. No extra cabling necessary.



Top pins are digital I/O 0 to 13 (some also work as PWM / analog output, but we're not using this feature here), bottom pins are analog input and power pins. On the left there's USB mini-B, external power in, and three switches - manual/auto reset (set to auto), 5V/3.3V (set to 5V, as we need 5V on output), and USB/external power (there is no external power so it works as a power switch).

There are a few interesting diodes, that you can see blinking on the movie. Red RX and TX are for USB communication - RX is blinking every time we receive another lyrics line, we're not sending anything back here. The green diode is connected to digital pin 13, you can conveniently use it a pulse to verify that your program is actually running and not hanging up somewhere.

LCD display


This is surprisingly complicated piece of equipment. It turns out almost all one and two line character LCD displays in the world follow the same HD44780 standard. Which is quite complex. Fortunately there's a library for Arduino for handling them, whih solves most of our problems.

The first big complication is that it needs 3 different voltages - 5V for electronics, 4V for LCD backlight, and unspecified voltage for liquid crystals which regulated contrast.

We already have 5V of course, that's what USB uses, and what all the TTL uses. Backlight requires 4V plus minus 0.5V according to datasheets, but that's a lie, it's barely visible around 3.5V. I tried to cheat and put some resistor between its positive pole and 5V, but that didn't really work, I would need a very small resistor as its actual power not just reference voltage. I finally figured out how to get 4V with a proper mix of batteries - two 1.2V rechargables, and one 1.5V alkaline, giving it a grand total of, well 3.84V at the moment, I probably need to recharge them. Surprisingly AAA batteries work well enough in AA battery holder without any extra hacks.

I might be wrong, but the third voltage, needed to drive liquid crystals, seems to be just reference voltage, and not actually used to power anything. Setting it too low makes liquid crystals all black, setting it too high makes them all transparent. The idea is to have on ones black, and off ones transparent. It turns out putting 5kohm between ground and contrast pin (achieved by a pair of 10kohm resistors in parallel) works well enough. It seems to drive it to about 0.96V. I'm not sure what's the optimal level, but 1kohm and 10kohm are too extreme for convenient viewing.

The LCD circuit has 8 data pins, only 4 of which are used, R/W pin, which is grounded, so we only ever write there, and two extra control pins.

Data pins are connected to Arduino pins 7-10, control pins to Arduino pins 2 and 12, all for no particular reason, these are just library defaults.

Timing


I first wanted to put timing and lyrics into program driving Arduino, but there are two big timing issues. First, song on computer and lyrics need to start at the same time - what could be achieved either by some Arduino-computer communication, or by me pressing two buttons at the same time. Ugly but possible. The second problem is that Arduino doesn't seem to have any real-time clock, or instruction timer. I might be wrong about that, there wasn't any obvious one in documentation. It has good delay function, but driving LCD requires waiting for it, and we would need to do some nasty guessing how long it took. There's also the third problem that every new song would require uploading new Arduino program.

So I decided instead to send lyrics from computer to Arduino as they are played. It's quite nice because the same program and circuit can be repurposed as a Twitter client or anything else I want.

Arduino program


The program is unbelievably easy. We initialize USB serial line at 9600 baud, initialize LCD, and set pin 13 (driving that green diode) to output mode.

Then if any character is available on serial line - if it's NUL/LF/CR we treat it as clear screen, otherwise we display it. The display thinks it has 40 characters per line, so we can fill it with spaces and we don't need a command to move cursor to the second line. Clearing the screen resets the cursor.

If there's no serial data available we blink the diode and wait 100ms. All the heavy lifting is provided by LCD4Bit and Serial libraries.

And yes - this is C++. I forgot to mention. It doesn't have main() function. As Arduino can run only one program, and it never exits, it has two functions. setup() to start it up, and loop() which is run in a loop automatically. There are some complications here, somehow resetting the serial line causes setup() to rerun, I'm not sure why. Maybe it is a segfault even, it's C++ after all. And LCD doesn't become operational immediately, it takes a second or so and if we write in this time data is going to be ignored. But let's just skip that for now.

#include <LCD4Bit.h>

LCD4Bit lcd = LCD4Bit(2);

void setup() {
Serial.begin(9600);
pinMode(13, OUTPUT);
lcd.init();
lcd.printIn("Ready to play");
}

int diode = HIGH;

void loop() {
int val;

if(Serial.available()) {
val = Serial.read();
if(val == 0 || val == 10 || val == 13)
lcd.clear();
else
lcd.print(val);
} else {
digitalWrite(13, diode);
diode = (diode == HIGH) ? LOW : HIGH;
delay(100);
}
}


Ruby lyrics driver


Ruby gem serialport provides all our serial communication needs. Just create an object using SerialPort.new("/dev/cu.usbserial-A9009rh4", 9600) (name of serial over USB device will differ), and use #write(data) to write there.

There are just a few more complications, to get good camera shots we need to wait a few seconds between initializing serial communication and starting to send data. 3s would be enough, but it defaults to 15s so I can position the camera etc.

And we want mplayer to start without any delay and without any garbage on the output, so -really-quiet -hardframedrop -nocache and redirecting everything to /dev/null

require 'rubygems'
require 'serialport'
require 'lrc_extract'

class Arduino
def initialize
@sp = SerialPort.new(Dir["/dev/cu.usb*"][0], 9600)
end
def print(text0, text1)
@sp.write("\n"+text0+(" " * (40-text0.size))+text1)
end
def countdown(i)
i.times{|j|
@sp.write("\n#{i-j}")
sleep 1
}
@sp.write("\nGO!")
end
end

class Song
def initialize(song_dir)
@mp3_file = Dir["#{song_dir}/*.mp3"][0]
@lrc_file = Dir["#{song_dir}/*.lrc"][0]
raise "No mp3 file in #{song_dir}" unless @mp3_file
raise "No lrc file in #{song_dir}" unless @lrc_file
@lrc = Lrc.new(open(@lrc_file))
end
def each_line(&blk)
@lrc.each_line(&blk)
end
def fork_mp3_player!
unless pid = fork
exec "mplayer -really-quiet -hardframedrop -nocache '#{@mp3_file}' >/dev/null </dev/null 2>/dev/null"
end
pid
end
end

class Timer
def initialize
@start_time = Time.now
end
def sleep_until(time)
cur_time = Time.now - @start_time
to_sleep = time - cur_time
sleep(to_sleep) if to_sleep > 0
end
end

a = Arduino.new
song = Song.new(ARGV[0] || "songs/Dance Dance Revolution 6th Mix -Max-/WWW.BLONDE GIRL (MOMO MIX)")
a.countdown((ARGV[1] || 15).to_i)

begin
pid = song.fork_mp3_player!
timer = Timer.new
song.each_line{|text0, text1, line_time|
timer.sleep_until(line_time)
puts "#{text0} #{text1}"
a.print(text0, text1)
}
Process.waitpid pid
rescue
system "kill", "-9", "#{pid}"
raise
end


What next


If someone has Still Alive lyrics with timing, I'll upload a video of that to Youtube. I thought about Twitter client or something like that, but 32 characters make even 140 seem very very long. The display is also quite slow, so smoothly scrolling letters probably won't work too well. It might be because we use it in write only mode, so instead of waiting for busy flag to become false we simply wait maximum necessary delay. That's just a guess.

Anyway, I have plenty of things to connect to Arduino, if anything interesting ever comes out of it I'll let you know.

Thursday, April 09, 2009

Medieval 2 Total War Concentrated Vanilla 0.06

Brown & Pink Birthday Cake by PinkCakeBox from flickr (CC-NC-ND)After some playtesting, well playing really, the testing was just a side effect, here's another release of Concentrated Vanilla. Previous posts: 0.02, 0.04.

This release tries its best at making cavalry less overpowered. The problem isn't really how strong knights are - they're supposed to be given right conditions, just how cheap and versatile they are - all knight army beats balanced army of twice the price in almost every situation.

The first thing that needed to be done was making sieges harder.

  • Walls and gates are 5x stronger. Towers are not. This leads to many interesting tactics where siege engines are used just as a part of strategy, not for one unit of ballistas to take down the walls of a fortress.
  • Towers fire 50% faster, and most importantly the distance needed to activate the tower is now 8x bigger. So a single unit can activate half of the towers in a minor city, and even relatively modest garrison can keep every single tower in a citadel active. The radius used to be almost infinite in previous versions, but I think it's strategically richer this way.
  • Missile infantry (but not cavalry) has twice the ammo. This is useful in field battles too, but mostly in siege defenses.
  • Unlike in previous versions, rams are no longer specially nerfed, gates are strong enough now that it's not really needed.
  • To limit the spy spam loophole, spies cost 2x more to recruit and upkeep.
  • Not really about sieges, but bodyguards are now 1hp, half size, and all cavalry is 50% more expensive in recruitment and upkeep (per soldier, so bodyguard unit ends up 25% cheaper). This makes faction-specific units much more important, and factions more varied.
Campaign balance was significantly changed. Probably the biggest problem with vanilla, well other than the retarded AI, is that a vast compact-shaped empire is the only way to go. So you blitz to get large number of cities, and then by turn 20 nobody can realistically challenge you, unless they band up together for a crusade or something else exceptional like that. I wanted to go for quality not quantity.
  • Everybody moves 75% faster on campaign map. This together with stronger settlement defenses means it's much easier to have empire in non-compact shape. It now makes sense to cherry-pick interesting settlements instead of going for the closest ones because quality didn't matter, and to defend them all later by quickly transporting soldiers from your recruitment centres.
  • King's purse is 2x bigger. Not a big deal to big countries, but it helps small ones a lot.
  • All buildings take 1 turn to build. Except for mines they are all 50% more expensive. This means you can spend your money on developing your existing settlements, instead of just to buying armies and expanding like in vanilla.
  • Merchant trade 50% more valuable.
  • Settlement trade 50% more valuable.
  • Mines are 3x more expensive, and return 3x more money. This all tremendously increases profitability of good and well-developed settlements. In vanilla it was mostly about quantity.
  • Tax effects on settlement growth 2x bigger, so low taxes are +1%, very high are -2%, not +0.5% / -1% like in vanilla.
And finally:
  • Pirates and brigands spawn 4x less often, but stronger by a similar factor. It reduces annoyance significantly without changing balance.
So here's version 0.06. You can also grab older versions if you want.