diff --git a/game/modules/tome/class/Actor.lua b/game/modules/tome/class/Actor.lua
index 8f35a12bb332bee520dcfbf0c361f8f766880146..626fe4893a85dffa18f8af6426341474a1ce0064 100644
--- a/game/modules/tome/class/Actor.lua
+++ b/game/modules/tome/class/Actor.lua
@@ -3295,25 +3295,393 @@ function _M:die(src, death_note)
 	return true
 end
 
-function _M:learnStats(statorder)
-	self.auto_stat_cnt = self.auto_stat_cnt or 1
+--- Learn stats (stat increment) in a specified order up to a maximum
+-- @param[table] statorder: an orded list of stat ids in which to learn, generally all learned in one character level
+-- @param repeats: maximum number of times to apply the statorder <1>
+function _M:learnStats(statorder, repeats)
+	statorder.idx = statorder.idx or 1
+	repeats = (repeats or 1)*#statorder
 	local nb = 0
 	local max = 60
 
-	-- Allow to go over a natural 60, up to 80 at level 50
+	-- Allow stats to go over a natural 60, up to 80 at level 50
 	if not self.no_auto_high_stats then max = 60 + (self.level * 20 / 50) end
 
-	while self.unused_stats > 0 do
-		if self:getStat(statorder[self.auto_stat_cnt]) < max then
-			self:incIncStat(statorder[self.auto_stat_cnt], 1)
+	while self.unused_stats > 0 and nb < repeats do
+		if self:getStat(statorder[statorder.idx]) < max then
+			self:incIncStat(statorder[statorder.idx], 1)
 			self.unused_stats = self.unused_stats - 1
 		end
-		self.auto_stat_cnt = util.boundWrap(self.auto_stat_cnt + 1, 1, #statorder)
+		statorder.idx = util.boundWrap(statorder.idx + 1, 1, #statorder)
 		nb = nb + 1
-		if nb >= #statorder then break end
 	end
 end
 
+--- Actor learns/advances in a character class, randomly gaining stats and learning talents based on levels in the class
+--	When first called, the actor gains starting stat bonuses and talents and learns talent categories according to the birth class descriptor
+-- Note: this does not handle any special class requirements, such as creating a golem, generating equipment, etc.
+-- @see game.state:applyRandomClass
+-- @param c_data[table] contains info on the class to learn/levelup in:
+-- @field class: name (birth descriptor subclass name) of the character class to learn ("Bulwark", "Solipsist", ...)
+-- @field start_level: starting actor level to begin leveling in the class <1>
+-- @field level_rate: rate levels in character class are gained as % of actor level <100>
+-- @field check_talents_level: set true to enforce character level limits on talent levels (i.e. level 5 at level 50) <nil>
+-- @field level_by_class: set true to use class level rather than actor level when checking talent/stat limits <nil>
+-- @foe;d ignore_special: set true to skip checking talent special requirements
+-- @field auto_sustain: set true to automatically turn on sustained talents learned <nil>
+-- @field tt_focus: talent type focus, higher values cause talent selections to be focused within fewer talent types <3>
+-- @field use_actor_points: set true to apply stat/talent points from base actor levels in addtion to class levels <nil>
+--  The following fields are automatically generated or updated from the class birth descriptor:
+-- @field ttypes[table]: talent trees to learn talents from (updated with birth descriptor)
+--		{talent_type_name = {[1]=known, [2]=mastery_add}, ...}
+-- @field auto_stats: ordered list of stat ids to use when applying unused_stats points
+--		generated from class descriptor by default, set false to disable, see Actor:learnStats
+-- Additional talent inputs for each talent definition t:
+--	t.random_boss_rarity: if defined, the percent chance the talent may be learned each time randomly selected
+function _M:levelupClass(c_data)
+	c_data.last_level = c_data.last_level or 0
+	c_data.start_level = c_data.start_level or 1
+
+	local new_level = math.ceil((self.level - c_data.start_level + 1)*(c_data.level_rate or 100)/100)
+	
+	if new_level <= c_data.last_level then return end
+	print("[Actor:levelupClass]", self.name, "auto level up", c_data.class, c_data.last_level, "-->", new_level, c_data)
+	
+table.set(game, "debug", "levelupClass", {act=self, c_data=c_data}) -- debugging
+
+	local ttypes
+	-- temporarily remove any previous stat/talent points if they won't be used
+	local base_points = {self.unused_stats, self.unused_talents, self.unused_generics}
+	if not c_data.use_actor_points then
+		self.unused_stats, self.unused_talents, self.unused_generics = 0, 0, 0 
+	end
+
+	-- Initialize if needed, updating auto_classes table
+	if c_data.class and not c_data.initialized then -- build talent category list from the class
+		local Birther = require "engine.Birther"
+		local c_def = Birther.birth_descriptor_def.subclass[c_data.class]
+		if not c_def then
+			print("[Actor:levelupClass] ### undefined class:", c_data.class)
+			return
+		end
+
+		local mclasses = Birther.birth_descriptor_def.class
+		local mclass = nil
+		for name, data in pairs(mclasses) do
+			if data.descriptor_choices and data.descriptor_choices.subclass and data.descriptor_choices.subclass[c_def.name] then mclass = data break end
+		end
+		if not mclass then
+			print("[Actor:levelupClass] ###class", c_data.class, "has no parent class type###")
+			return 
+		end
+
+		print(("[Actor:levelupClass] %s %s ## Initialzing auto_class %s (%s) %s%% level_rate from level %s ##"):format(self.uid, self.name, c_data.class, mclass.name, c_data.level_rate, c_data.start_level))
+
+		-- update class descriptor list and build inherent power sources
+		self.descriptor = self.descriptor or {}
+		self.descriptor.classes = self.descriptor.classes or {}
+		table.append(self.descriptor.classes, {c_def.name})
+		
+		-- build inherent power sources and forbidden power sources
+		-- self.forbid_power_source --> self.not_power_source used for classes
+		self.power_source = table.merge(self.power_source or {}, c_def.power_source or {})
+		self.not_power_source = table.merge(self.not_power_source or {}, c_def.not_power_source or {})
+		-- update power source parameters with the new class
+		self.not_power_source, self.power_source = game.state:updatePowers(game.state:attrPowers(self, self.not_power_source), self.power_source)
+		print("  *** power types: not_power_source =", table.concat(table.keys(self.not_power_source),","), "power_source =", table.concat(table.keys(self.power_source),","))
+		
+		-- apply class stat bonuses and set up class auto_stats (in auto_classes)
+		local auto_stats
+		if c_def.stats and c_data.auto_stats ~= false then
+			auto_stats = {}
+			for stat, v in pairs(c_def.stats or {}) do
+				local stat_id = self.stats_def[stat].id
+				self.stats[stat_id] = (self.stats[stat_id] or 10) + v
+				for i = 1, v do auto_stats[#auto_stats+1] = stat_id end
+			end
+			c_data.auto_stats = auto_stats
+		end
+		c_data.auto_stats = c_data.auto_stats or auto_stats
+
+		ttypes = {}
+		for tt, d in pairs(mclass.talents_types or {}) do
+			self:learnTalentType(tt, d[1]) self:setTalentTypeMastery(tt, (self:getTalentTypeMastery(tt) or 1) + d[2])
+			ttypes[tt] = table.clone(d)
+		end
+		for tt, d in pairs(mclass.unlockable_talents_types or {}) do
+			self:learnTalentType(tt, d[1]) self:setTalentTypeMastery(tt, (self:getTalentTypeMastery(tt) or 1) + d[2])
+			ttypes[tt] = table.clone(d)
+		end
+		for tt, d in pairs(c_def.talents_types or {}) do
+			self:learnTalentType(tt, d[1]) self:setTalentTypeMastery(tt, (self:getTalentTypeMastery(tt) or 1) + d[2])
+			ttypes[tt] = table.clone(d)
+		end
+		for tt, d in pairs(c_def.unlockable_talents_types or {}) do
+			self:learnTalentType(tt, d[1]) self:setTalentTypeMastery(tt, (self:getTalentTypeMastery(tt) or 1) + d[2])
+			ttypes[tt] = table.clone(d)
+		end
+		
+		-- set up input talent categories specified (generally for non-class talents)
+		if c_data.ttypes then
+			for tt, d in pairs(c_data.ttypes) do
+				if not self:knowTalentType(tt) then
+					if type(d) ~= "table" then
+						d={true, type(d) == "number" and d or rng.range(1, 3)*0.1}
+					else d=table.clone(d)
+					end
+					self:learnTalentType(tt, d[1])
+					self:setTalentTypeMastery(tt, (self:getTalentTypeMastery(tt) or 1) + d[2])
+					ttypes[tt] = table.mergeAdd(ttypes[tt] or {}, d)
+				end
+			end
+		end
+		if not next(ttypes) then -- if not specified, use all known talent types
+			for tt, known in pairs(self.talents_types) do
+				ttypes[tt] = {known, (self:getTalentTypeMastery(tt) or 1) - 1}
+			end
+		end
+		
+		-- Note: could limit number of talent trees selected here to limit # talents learned
+		
+--print("\t *** auto_levelup initialized talent category choices:", mclass.name , c_def.name , "\n\t")
+		-- set up (semi-random) rarity levels for talent categories
+		-- This tends to focus learned talents within certain trees (usually those with improved mastery)
+		local tt_count = 0
+		local unknown_tt={}
+		local tt_focus = c_data.tt_focus or 3
+		for tt, d in pairs(ttypes) do
+			d.tt = tt
+			tt_count = tt_count + 1
+			d.tt_count = tt_count
+			d.rarity = d.rarity or (1.3 + 0.5*tt_count)/math.max(0.1, self:getTalentTypeMastery(tt)*rng.float(0.1, tt_focus))^2
+--print(("\t *** %-40s  rarity %5.3f"):format(tt, d.rarity))
+			if not d[1] then table.insert(unknown_tt, d) end
+		end
+		c_data.unknown_tt = unknown_tt
+		c_data.ttypes = ttypes
+		
+		-- Assign class starting talents and set them to level up later
+		for tid, v in pairs(c_def.talents or {}) do
+			c_data.auto_talents = c_data.auto_talents or {}
+			local t = self:getTalentFromId(tid)
+			if not t.no_npc_use and (not t.random_boss_rarity or rng.chance(t.random_boss_rarity)) then
+				local every = 0
+				if t.points > 1 then
+					every = math.ceil(50/(t.points * 1.2))
+					table.insert(c_data.auto_talents, {tid=tid, start_level=c_data.start_level, base=v, every=every})
+				end
+				print(("\t ** learning %s birth talent %s %s (every %s levels)"):format(c_data.class, tid, v, every))
+				self:learnTalent(tid, true, v)
+			end
+		end
+		c_data.initialized = true
+	end
+
+	local init_level, learn_tids, learn_stats = c_data.last_level -- for log output summary
+	local to_activate = {}
+	while new_level > c_data.last_level do
+		c_data.last_level = c_data.last_level + 1
+		--print("{Actor:levelupClass] to", c_data.class, c_data.last_level)
+		learn_tids = learn_tids or {}
+		learn_stats = learn_stats or {}
+		-- Gain talent and category points, similar to Actor:levelup: (but with no prodigies)
+		if not self.no_points_on_levelup then
+			self.unused_stats = self.unused_stats + (self.stats_per_level or 3) + self:getRankStatAdjust()
+			self.unused_talents = self.unused_talents + 1
+			self.unused_generics = self.unused_generics + 1
+			if c_data.last_level % 5 == 0 then self.unused_talents = self.unused_talents + 1 end
+			if c_data.last_level % 5 == 0 then self.unused_generics = self.unused_generics - 1 end
+
+			if self.extra_talent_point_every and c_data.last_level % self.extra_talent_point_every == 0 then self.unused_talents = self.unused_talents + 1 end
+			if self.extra_generic_point_every and c_data.last_level % self.extra_generic_point_every == 0 then self.unused_generics = self.unused_generics + 1 end
+
+			-- At levels 10, 20 and 36 and then every 30 levels, we gain a new talent type
+			if c_data.last_level == 10 or c_data.last_level == 20 or c_data.last_level == 36 or (c_data.last_level > 50 and (c_data.last_level - 6) % 30 == 0) then
+				self.unused_talents_types = self.unused_talents_types + 1
+			end
+			-- if c_data.last_level == 30 or c_data.last_level == 42 then self.unused_prodigies = self.unused_prodigies + 1 end
+		elseif type(self.no_points_on_levelup) == "function" then
+			self:no_points_on_levelup()
+		end
+
+		--print((" *** level: %s/%s stats: %s talents: %s generics: %s categories: %s prodigies: %s"):format(c_data.last_level, new_level, self.unused_stats, self.unused_talents, self.unused_generics, self.unused_talents_types, self.unused_prodigies))
+
+		-- automatically level up any auto_talents, (usualy birth talents)
+		if c_data.auto_talents then
+			for i, d in ipairs(c_data.auto_talents) do
+				if c_data.last_level > d.start_level and (c_data.last_level - d.start_level)%d.every == 0 then
+					--print(("\t ** advancing %s auto_talent %s"):format(c_data.class, d.tid))
+					self:learnTalent(d.tid, true)
+				end
+			end
+		end
+
+		ttypes = c_data.ttypes
+
+		-- generate list of possible talent types based on the master list
+		--print("\t *** auto_levelup available talent categories:\n\t", table.concat(table.keys(ttypes), ", "))
+		local tt_choices = {}
+		for tt, d in pairs(ttypes) do
+			table.insert(tt_choices, d)
+		end
+
+		local fails, max_fails = 0, #tt_choices*4
+		-- assign unused category points
+		while self.unused_talents_types >= 1 and fails <= 5 do
+			print("\t *** Assigning", self.unused_talents_types, "category points")
+
+			-- 50% chance to learn a new talent category if possible
+			local tt
+			if rng.percent(50) then	tt = rng.tableRemove(c_data.unknown_tt) end
+			if not tt then tt = rng.table(tt_choices) end
+			
+			if tt then
+				if self:knowTalentType(tt.tt) then
+					print("\t *** auto_levelup IMPROVING TALENT TYPE", tt.tt)
+					local ml = self:getTalentTypeMastery(tt.tt) or 1
+					self:setTalentTypeMastery(tt.tt, ml + (ml <= 1 and 0.2 or 0.1)) -- 0.2 for 1st then 0.1 thereafter
+				else
+					print("\t *** auto_levelup LEARNING TALENT TYPE", tt.tt)
+					self:learnTalentType(tt.tt, true)
+					tt.rarity = tt.rarity/2  -- makes talents within an unlocked talent tree more likely to be learned
+				end
+				--print("\t *** talent type mastery:", tt, self:getTalentTypeMastery(tt))
+				self.unused_talents_types = self.unused_talents_types - 1
+			else fails = fails + 1
+			end
+		end
+		
+		-- learn talents randomly from available talent trees
+		-- Note: could put limit on number of talents learned here (similar to previous method)
+		fails = 0
+		local tt, tt_idx, tt_def
+		local t, t_idx
+		while fails < max_fails and (self.unused_talents >= 1 or self.unused_generics >= 1) do
+			-- pick a talent tree
+			tt, tt_idx = rng.rarityTable(tt_choices)
+			if not tt then break end
+			tt = tt.tt
+			tt_def = tt and self:knowTalentType(tt) and self.talents_types_def[tt]
+			-- begin category loop
+			if tt_def and (tt_def.generic and self.unused_generics >= 1 or not tt_def.generic and self.unused_talents >=1) then
+				--print("\t *** checking category:", tt)
+				-- try to find a talent to learn
+				local t_choices = {}
+				local nb_known = self:numberKnownTalent(tt)
+				-- update talent choices with each talent in the tree that can be learned
+				for i, t in ipairs(tt_def.talents) do
+					if t.no_npc_use or t.not_on_random_boss then
+						nb_known = nb_known + 1 -- treat as known to allow later talents to be learned
+					elseif t.type[2] and nb_known >= t.type[2] - 1 and (not t.random_boss_rarity or rng.percent(t.random_boss_rarity)) then -- check category talents known
+						table.insert(t_choices, t)
+					end
+				end
+				--	print("talent choices for", tt)	for i, t in ipairs(t_choices) do print("\t", i, t.id, t.name) end
+				local ok
+				repeat
+					t, t_idx = rng.table(t_choices)
+					if not t then break end
+					ok = true
+					local tlev = self:getTalentLevelRaw(t) + 1 -- talent level to learn
+					-- check max talent level
+					local max = t.points
+					if max > 1 then
+						max = math.max(max, math.ceil(max*1.2*c_data.last_level/50))
+						if c_data.check_talents_level then -- apply character level limits
+							max = math.min(max, t.points + math.max(0, math.floor((c_data.last_level - 50) / 10)) + (self.talents_inc_cap and self.talents_inc_cap[t.id] or 0))
+						end
+					end
+					if tlev > max then
+						ok = false
+					end
+					
+					if ok and t.require then -- check requirements to learn the talent
+						local req = rawget(t, "require")
+						if type(req) == "function" then req = req(self, t) end
+						if req then
+							if req.level then -- check talent-defined level limits
+								local lev = util.getval(req.level, tlev)
+								if c_data.level_by_class then
+									if c_data.last_level < lev then ok = false end
+								elseif self.level < lev then 
+									ok = false
+								end
+							end
+							if ok and req.talent then -- other prerequisite talents
+								for _, tid in ipairs(req.talent) do
+									if type(tid) == "table" then
+										if type(tid[2]) == "boolean" and tid[2] == false then
+											if self:knowTalent(tid[1]) then ok = false end
+										else
+											if tlev - 1 < tid[2] then ok = false end
+										end
+									else
+										if not self:knowTalent(tid) then ok = false end
+									end
+								end
+							end
+							if ok and req.special and not c_data.ignore_special then -- special checks
+								if not req.special.fct(self, t, 1) then ok = false	end
+							end
+							if ok and req.stat then -- check stat requirements, allocate stat points if needed
+								local need, needed, need_stats = 0, 0, {}
+								for s, v in pairs(req.stat) do
+									need = util.getval(v, tlev) - self:getStat(s)
+									if need > 0 then
+										need_stats[s] = need; needed = needed + need
+									end
+								end
+								if ok and needed <= self.unused_stats then -- allocate stats needed for the talent
+									for s, v in pairs(need_stats) do
+										local pts = self:incIncStat(s, v)
+										learn_stats[s] = (learn_stats[s] or 0) + pts
+										self.unused_stats = self.unused_stats - pts
+									end
+								else ok = false
+								end
+							end
+						end
+					end
+					ok = ok and self:learnTalent(t.id, true)
+					if ok then 
+--						print("\t *** learning "..(tt_def.generic and "GENERIC" or "CLASS").." talent", t.id, ok)
+						learn_tids[t.id] = (learn_tids[t.id] or 0) + 1
+						if tt_def.generic then self.unused_generics = self.unused_generics - 1 else self.unused_talents = self.unused_talents - 1 end
+						if t.mode == "sustained" and c_data.auto_sustain and not self:isTalentActive(t.id) then to_activate[t.id] = true end
+					else table.remove(t_choices, t_idx)
+					end
+				until ok
+				if not ok then -- no more talent choices possible for category
+					-- print("\t *** no talent choices in category:", tt)
+					table.remove(tt_choices, tt_idx)
+				end
+			else -- impossible to learn talents in the category
+				-- print("\t *** rejecting category:", tt)
+				table.remove(tt_choices, tt_idx)
+			end -- end category loop
+			fails = fails + 1
+		end
+	end
+	print("[Actor:levelupClass] ### levelup summary for", self.name, c_data.class, init_level, "-->", c_data.last_level)
+	print("[Actor:levelupClass] ### Talents Learned:")
+	table.print(learn_tids, "\t*\t")
+	print("[Actor:levelupClass] ### Talents forced Stats:", table.concatNice(table.to_strings(learn_stats, "%s: %s"), ", "))
+--	print((" *** auto_level complete: level: %s/%s remaining stats: %s talents: %s generics: %s categories: %s prodigies: %s"):format(c_data.last_level, new_level, self.unused_stats, self.unused_talents, self.unused_generics, self.unused_talents_types, self.unused_prodigies))
+
+	-- allocate any remaining stat points
+	if self.unused_stats > 0 and c_data.auto_stats then
+		print(" *** auto allocating", self.unused_stats, "remaining stat points")
+		self:learnStats(c_data.auto_stats, 5)
+	end
+	
+	-- restore unused stat and talent points
+	if not c_data.use_actor_points then
+		self.unused_stats, self.unused_talents, self.unused_generics = unpack(base_points)
+	end
+	-- turn on designated sustains
+	for tid, _ in pairs(to_activate) do self:forceUseTalent(tid, {ignore_energy=true}) end
+end
+
 function _M:resetToFull()
 	if self.dead then return end
 	self.life = self.max_life
@@ -3348,20 +3716,30 @@ function _M:resolveLevelTalents()
 		end
 		self.learn_tids = nil
 	end
-	if not self.start_level or not self._levelup_talents then return end
+--	if not self.start_level or not self._levelup_talents then return end
 
-	local maxfact = 1  -- Balancing parameter for levels > 50: maxtalent level = actorlevel/50*maxfact * normal max talent level
-	maxfact=math.max(maxfact,self.level/50)
+	if self.start_level and self._levelup_talents then
+		local maxfact = 1  -- Balancing parameter for levels > 50: maxtalent level = actorlevel/50*maxfact * normal max talent level
+		maxfact=math.max(maxfact,self.level/50)
 
-	for tid, info in pairs(self._levelup_talents) do
-		if not info.max or (self.talents[tid] or 0) < math.floor(info.max*maxfact) then
-			local last = info.last or self.start_level
-			if self.level - last >= info.every then
-				self:learnTalent(tid, true)
-				info.last = self.level
+		for tid, info in pairs(self._levelup_talents) do
+			if not info.max or (self.talents[tid] or 0) < math.floor(info.max*maxfact) then
+				local last = info.last or self.start_level
+				if self.level - last >= info.every then
+					self:learnTalent(tid, true)
+					info.last = self.level
+				end
 			end
 		end
 	end
+
+	-- automatically level up classes as defined
+	if self.auto_classes then
+		for i, c_data in ipairs(self.auto_classes) do
+			self:levelupClass(c_data)
+		end
+	end
+
 end
 
 function _M:levelup()
diff --git a/game/modules/tome/class/GameState.lua b/game/modules/tome/class/GameState.lua
index df21e3b4de1e02f99e3285eb97571e76e2d705fa..e9b05c2a30ab954a7c91f9d5efca7b1f12483cd3 100644
--- a/game/modules/tome/class/GameState.lua
+++ b/game/modules/tome/class/GameState.lua
@@ -1517,6 +1517,7 @@ function _M:entityFilterPost(zone, level, type, e, filter)
 				level = lev,
 				nb_rares = filter.random_elite.nb_rares or 1,
 				check_talents_level = true,
+				level_by_class = true,
 				user_post = filter.post,
 				post = function(b, data)
 					if data.level <= 20 then
@@ -1912,85 +1913,56 @@ function _M:createRandomZone(zbase)
 	return zone, boss
 end
 
---- Add one or more character classes to an actor, updating stats, talents, and equipment
+
+--- Add one or more character classes to an actor, updating stats, talents, and equipment,
+--  Updates autoleveling data so that class skills are advanced with level
+--	@see Actor:levelupClass
 --	@param b = actor(boss) to update
 --	@param data = optional parameters:
---	@param data.update_body a table of inventories to add, set true to add a full suite of inventories
---	@param data.force_classes = specific subclasses to apply first, ignoring restrictions
---		{"Rogue", "Necromancer", Corruptor = true, Bulwark = true, ...}
---		applied in order of numerical index, then randomly
---	@param data.nb_classes = random classes to add (in addition to any forced classes) <2>
--- 	@param data.class_filter = function(cdata, b) that must return true for any class picked.
+--	@field data.update_body a table of inventories to add, set true to add a full suite of inventories
+-- 	@field data.start_level: actor level to being leveling in the class(es) <1>
+-- 	@field data.level_rate: level of character class as % of actor level <100>
+--	@field data.force_classes = specific subclasses to apply first, ignoring restrictions
+--		{"Rogue" <=={Rogue = 100}>, {Necromancer = 75}, Corruptor = true <==100>, Bulwark = 50, ...}
+--		applied in order of numerical index, then randomly, numbers are the specified level_rate for each class
+--	@field data.nb_classes = random classes to add (in addition to any forced classes) <2>
+--		fractional classes are applied first with reduced level_rate
+-- 	@field data.class_filter = function(cdata, b) that must return true for any class picked.
 --		(cdata, b = subclass definition in engine.Birther.birth_descriptor_def.subclass, boss (before classes are applied))
---	@param data.no_class_restrictions set true to skip class compatibility checks <nil>
---	@param data.autolevel = autolevel scheme to use for stats (set false to keep current) <"random_boss">
---	@param data.spend_points = spend any unspent stat points (after adding all classes)
---	@param data.add_trees = {["talent tree name 1"]=true/mastery bonus, ["talent tree name 2"]=true/mastery bonus, ..} additional talent trees to learn
---	@param data.check_talents_level set true to enforce talent level restrictions <nil>
---	@param data.auto_sustain set true to activate sustained talents at birth <nil>
---	@param data.forbid_equip set true to not apply class equipment resolvers or equip inventory <nil>
---	@param data.loot_quality = drop table to use for equipment <"boss">
---	@param data.drop_equipment set true to force dropping of equipment <nil>
+--	@field data.no_class_restrictions set true to skip class compatibility checks <nil>
+--	@field data.autolevel = autolevel scheme to use for stats (set false to keep current) <"random_boss">
+--	@field data.spend_points = spend any unspent stat points (after adding all classes)
+--	@field data.add_trees = {["talent tree name 1"]=true/mastery bonus, ["talent tree name 2"]=true/mastery bonus, ..} additional talent trees to learn
+--	@field data.check_talents_level set true to enforce talent level restrictions based on class level <nil>
+--	@field data.auto_sustain set true to activate sustained talents at birth <nil>
+--	@field data.forbid_equip set true to ignore class equipment resolvers (and filters) or equip inventory <nil>
+--	@field data.loot_quality = drop table to use for equipment <"boss">
+--	@field data.drop_equipment set true to force dropping of equipment <nil>
 --	@param instant set true to force instant learning of talents and generating golem <nil>
 function _M:applyRandomClass(b, data, instant)
-	if not data.level then data.level = b.level end
+	if not data.level then data.level = b.level end -- use the level specified if needed
+
+print("[applyRandomClass] instant:", instant, "input data:") table.print(data, "__data__\t") -- debugging
 
 	------------------------------------------------------------
 	-- Apply talents from classes
 	------------------------------------------------------------
 	-- Apply a class
 	local Birther = require "engine.Birther"
-	b.learn_tids = {}
-	local function apply_class(class)
+	local function apply_class(class, level_rate)
 		local mclasses = Birther.birth_descriptor_def.class
 		local mclass = nil
 		for name, data in pairs(mclasses) do
 			if data.descriptor_choices and data.descriptor_choices.subclass and data.descriptor_choices.subclass[class.name] then mclass = data break end
 		end
-		if not mclass then return end
-
-		print("[applyRandomClass]", b.uid, b.name, "Adding class", class.name, mclass.name)
-		-- add class to list and build inherent power sources
-		b.descriptor = b.descriptor or {}
-		b.descriptor.classes = b.descriptor.classes or {}
-		table.append(b.descriptor.classes, {class.name})
-		
-		-- build inherent power sources and forbidden power sources
-		-- b.forbid_power_source --> b.not_power_source used for classes
-		b.power_source = table.merge(b.power_source or {}, class.power_source or {})
-		b.not_power_source = table.merge(b.not_power_source or {}, class.not_power_source or {})
-		-- update power source parameters with the new class
-		b.not_power_source, b.power_source = self:updatePowers(self:attrPowers(b, b.not_power_source), b.power_source)
-		print("   power types: not_power_source =", table.concat(table.keys(b.not_power_source),","), "power_source =", table.concat(table.keys(b.power_source),","))
-
-		-- Update/initialize base stats, set stats auto_leveling
-		if class.stats or b.auto_stats then
-			b.stats, b.auto_stats = b.stats or {}, b.auto_stats or {}
-			for stat, v in pairs(class.stats or {}) do
-				local stat_id = b.stats_def[stat].id
-				b.stats[stat_id] = (b.stats[stat_id] or 10) + v
-				for i = 1, v do b.auto_stats[#b.auto_stats+1] = stat_id end
-			end
-		end
-		if data.autolevel ~= false then b.autolevel = data.autolevel or "random_boss" end
-		
-		-- Class talent categories
-		for tt, d in pairs(mclass.talents_types or {}) do b:learnTalentType(tt, true) b:setTalentTypeMastery(tt, (b:getTalentTypeMastery(tt) or 1) + d[2]) end
-		for tt, d in pairs(mclass.unlockable_talents_types or {}) do b:learnTalentType(tt, true) b:setTalentTypeMastery(tt, (b:getTalentTypeMastery(tt) or 1) + d[2]) end
-		for tt, d in pairs(class.talents_types or {}) do b:learnTalentType(tt, true) b:setTalentTypeMastery(tt, (b:getTalentTypeMastery(tt) or 1) + d[2]) end
-		for tt, d in pairs(class.unlockable_talents_types or {}) do b:learnTalentType(tt, true) b:setTalentTypeMastery(tt, (b:getTalentTypeMastery(tt) or 1) + d[2]) end
-
-		-- Non-class talent categories
-		if data.add_trees then
-			for tt, d in pairs(data.add_trees) do
-				if not b:knowTalentType(tt) then
-					if type(d) ~= "number" then d = rng.range(1, 3)*0.1 end
-					b:learnTalentType(tt, true)
-					b:setTalentTypeMastery(tt, (b:getTalentTypeMastery(tt) or 1) + d)
-				end
-			end
+		if not mclass then
+			print("[applyRandomClass] ### ABORTING ###", b.uid, b.name, "No main class type for", class.name)
+			return
 		end
-		-- Add starting equipment
+
+		print("[applyRandomClass]", b.uid, b.name, "Adding class", class.name, mclass.name, "level_rate", level_rate)
+
+		-- Add starting equipment and update filters as needed
 		local apply_resolvers = function(k, resolver)
 			if type(resolver) == "table" and resolver.__resolver then
 				if resolver.__resolver == "equip" then
@@ -2007,6 +1979,10 @@ function _M:applyRandomClass(b, data, instant)
 						end
 						b[#b+1] = resolver
 					end
+				elseif resolver.__resolver == "auto_equip_filters" then
+					if not data.forbid_equip then
+						b[#b+1] = resolver
+					end
 				elseif resolver._allow_random_boss then -- explicitly allowed resolver
 					b[#b+1] = resolver
 				end
@@ -2024,81 +2000,18 @@ function _M:applyRandomClass(b, data, instant)
 		for k, resolver in pairs(mclass.copy or {}) do apply_resolvers(k, resolver) end
 		for k, resolver in pairs(class.copy or {}) do apply_resolvers(k, resolver) end
 
-		-- Assign a talent resolver for class starting talents (this makes them autoleveling)
-		local tres = nil
-		for k, resolver in pairs(b) do if type(resolver) == "table" and resolver.__resolver and resolver.__resolver == "talents" then tres = resolver break end end
-		if not tres then tres = resolvers.talents{} b[#b+1] = tres end
-		for tid, v in pairs(class.talents or {}) do
-			local t = b:getTalentFromId(tid)
-			if not t.no_npc_use and (not t.random_boss_rarity or rng.chance(t.random_boss_rarity)) then
-				local max = (t.points == 1) and 1 or math.ceil(t.points * 1.2)
-				local step = max / 50
-				tres[1][tid] = v + math.ceil(step * data.level)
-			end
-		end
-
-		-- Select additional talents from the class
-		local known_types = {}
-		for tt, d in pairs(b.talents_types) do
-			known_types[tt] = b:numberKnownTalent(tt)
-		end
-
-		local list = {}
-		for _, t in pairs(b.talents_def) do
-			if b.talents_types[t.type[1]] then
-				if t.no_npc_use or t.not_on_random_boss then
-					known_types[t.type[1]] = known_types[t.type[1]] + 1 -- allows higher tier talents to be learnt
-				else
-					local ok = true
-					if data.check_talents_level and rawget(t, 'require') then
-						local req = t.require
-						if type(req) == "function" then req = req(b, t) end
-						if req and req.level and util.getval(req.level, 1) > math.ceil(data.level/2) then
-							print("Random boss forbade talent because of level", t.name, t.id, data.level)
-							ok = false
-						end
-					end
-					if t.type[1]:find("/other$") then
-						print("Random boss forbase talent because category /other", t.name, t.id, t.type[1])
-						ok = false
-					end
-					if ok then list[t.id] = true end
-				end
-			end
-		end
-
-		local nb = 4 + 0.38*data.level^.75 -- = 11 at level 50
-		nb = math.max(rng.range(math.floor(nb * 0.7), math.ceil(nb * 1.3)), 1)
-		print("Adding "..nb.." random class talents to boss")
-
-		local count, fails = 0, 0
-		while count < nb do
-			local tid = rng.tableIndex(list, b.learn_tids)
-			if not tid or fails > nb * 5 then break end
-			local t = b:getTalentFromId(tid)
-			if t then
-				if t.type[2] and known_types[t.type[1]] < t.type[2] - 1 then -- not enough of talents of type
-					fails = fails + 1
-				else -- ok to add
-					count = count + 1
-					local max = (t.points == 1) and 1 or math.ceil(t.points * 1.2)
-					local step = max / 50
-					local lev = math.ceil(step * data.level)
-					print(count, " * talent:", tid, lev)
-					if instant then -- affected by game difficulty settings
-						if b:getTalentLevelRaw(tid) < lev then b:learnTalent(tid, true, lev - b:getTalentLevelRaw(tid)) end
-						if t.mode == "sustained" and data.auto_sustain then b:forceUseTalent(tid, {ignore_energy=true}) end
-					else  -- applied when added to the level (unaffected by game difficulty settings)
-						b.learn_tids[tid] = lev
-					end
-					known_types[t.type[1]] = known_types[t.type[1]] + 1
-					list[tid] = nil
-				end
-			else list[tid] = nil
-			end
-		end
-		print(" ** Finished adding", count, "of", nb, "random class talents")
-
+		b.auto_classes = b.auto_classes or {}
+		local c_data = {
+			class = class.name,
+			ttypes = data.add_trees, -- adds specified talent trees
+			spend_points = data.spend_points,
+			start_level = data.start_level,
+			level_rate = level_rate or 100,
+			auto_sustain = data.auto_sustain,
+			check_talents_level = data.check_talents_level,
+			level_by_class = data.level_by_class,
+		}
+		table.insert(b.auto_classes, c_data)
 		return true
 	end
 
@@ -2110,17 +2023,28 @@ function _M:applyRandomClass(b, data, instant)
 
 	-- Select classes
 	local classes = Birther.birth_descriptor_def.subclass
-	if data.force_classes then -- apply forced classes first, by index, then in random order
-		local c_list = table.clone(data.force_classes)
+	 -- apply forced classes first, by index, then in random order, extracting specified or implied level rates
+	if data.force_classes then
+		local c_list = table.clone(data.force_classes, true)
 		local force_classes = {}
 		for i, c_name in ipairs(c_list) do
-			force_classes[i] = c_list[i]
+			if type(c_name) == "table" then
+				force_classes[i] = c_list[i]
+			else
+				force_classes[i] = {[c_name]=data.level_rate or 100} -- default 100% level_rate
+			end
 			c_list[i] = nil
 		end
-		table.append(force_classes, table.shuffle(table.keys(c_list)))
-		for i, c_name in ipairs(force_classes) do
+		local rng_fc = {}
+		for c_name, lr in pairs(c_list) do
+			table.insert(rng_fc, {[c_name]=(type(lr) == "number" and lr or data.level_rate or 100)})
+		end
+		
+		table.append(force_classes, table.shuffle(rng_fc))
+		for i, cl in ipairs(force_classes) do
+			local c_name, lr = next(cl)
 			if classes[c_name] then
-				apply_class(table.clone(classes[c_name], true))
+				apply_class(table.clone(classes[c_name], true), lr)
 			else
 				print("  ###Forced class", c_name, "NOT DEFINED###")
 			end
@@ -2132,16 +2056,25 @@ function _M:applyRandomClass(b, data, instant)
 		end
 	end
 	
-	local to_apply = data.nb_classes or 2
+	-- apply random classes
+	local to_apply = data.nb_classes or 1.5 -- 1.5 is one primary class and one secondary class @ 50% stats/talents
+	print("[applyRandomClass] applying", to_apply, "classes at", data.level_rate, "%%")
 	while to_apply > 0 do
 		local c = rng.tableRemove(list)
 		if not c then break end --repeat attempts until list is exhausted
 		if data.no_class_restrictions or self:checkPowers(b, c) then  -- recheck power restricts here to account for any previously picked classes
-			if apply_class(table.clone(c, true)) then to_apply = to_apply - 1 end
+			-- if nb_classes is not an integer, apply partial classes first so that resolvers for later classes take precedence
+			local lr = to_apply - math.floor(to_apply)
+			lr = lr == 0 and 1 or lr
+			if apply_class(table.clone(c, true), math.max(lr*(data.level_rate or 100), 10)) then
+				to_apply = to_apply - lr
+				if instant then b:levelup() end -- force immediate level up of class to appropriate level (learning talents, etc.)
+			end
 		else
 			print("  * class", c.name, " rejected due to power source")
 		end
 	end
+
 	if data.spend_points then -- spend any remaining unspent stat points
 		repeat 
 			local last_stats = b.unused_stats
@@ -2150,28 +2083,30 @@ function _M:applyRandomClass(b, data, instant)
 	end
 end
 
+
 --- Creates a random Boss (or elite) actor
 --	@param base = base actor to add classes/talents to
 --	calls _M:applyRandomClass(b, data, instant) to add classes, talents, and equipment based on class descriptors
 --		handles data.nb_classes, data.force_classes, data.class_filter, ...
 --	optional parameters:
---	@param data.init = function(data, b) to run before generation
---	@param data.level = minimum level range for actor generation <1>
---	@param data.rank = rank <3.5-4>
---	@param data.life_rating = function(b.life_rating) <1.7 * base.life_rating + 4-9>
---	@param data.resources_boost = multiplier for maximum resource pool sizes <3>
---	@param data.talent_cds_factor = multiplier for all talent cooldowns <1>
---	@param data.ai = ai_type <"tactical" if rank>3 or base.ai>
---	@param data.ai_tactic = tactical weights table for the tactical ai <nil - generated based on talents>
---	@param data.no_loot_randart set true to not drop a randart <nil>
---	@param data.on_die set true to run base.rng_boss_on_die and base.rng_boss_on_die_custom on death <nil>
---	@param data.name_scheme <randart_name_rules.default>
---	@param data.post = function(b, data) to run last to finish generation
+--	@field data.init = function(data, b) to run before generation
+--	@field data.level = minimum level range for actor generation <1>
+--	@field data.nb_classes = number of random classes to add <1.75>
+--	@field data.rank = rank <3.5-4>
+--	@field data.life_rating = function(b.life_rating) <1.7 * base.life_rating + 4-9>
+--	@field data.resources_boost = multiplier for maximum resource pool sizes <3>
+--	@field data.talent_cds_factor = multiplier for all talent cooldowns <1>
+--	@field data.ai = ai_type <"tactical" if rank>3 or base.ai>
+--	@field data.ai_tactic = tactical weights table for the tactical ai <nil - generated based on talents>
+--	@field data.no_loot_randart set true to not drop a randart <nil>
+--	@field data.on_die set true to run base.rng_boss_on_die and base.rng_boss_on_die_custom on death <nil>
+--	@field data.name_scheme <randart_name_rules.default>
+--	@field data.post = function(b, data) to run last to finish generation
 function _M:createRandomBoss(base, data)
 	local b = base:clone()
 	data = data or {level=1}
 	if data.init then data.init(data, b) end
-	data.nb_classes = data.nb_classes or 2
+	data.nb_classes = data.nb_classes or 1.75 -- Default one primary class @100% and one secondary class @ 75% level
 
 	------------------------------------------------------------
 	-- Basic stuff, name, rank, ...
@@ -2230,6 +2165,7 @@ function _M:createRandomBoss(base, data)
 	-- Leveling stats
 	b.autolevel = "random_boss"
 	b.auto_stats = {}
+	b.max_level = nil
 
 	-- Update default equipment, if any, to "boss" levels
 	for k, resolver in ipairs(b) do
@@ -2263,12 +2199,14 @@ function _M:createRandomBoss(base, data)
 	------------------------------------------------------------
 	-- Apply talents from classes
 	------------------------------------------------------------
-	self:applyRandomClass(b, data)
-
+	-- apply classes (instant to level up stats/talents before equipment is resolved)
+	self:applyRandomClass(b, data, true)
+	
 	b.rnd_boss_on_added_to_level = b.on_added_to_level
 	b._rndboss_resources_boost = data.resources_boost or 3
 	b._rndboss_talent_cds = data.talent_cds_factor
 	b.on_added_to_level = function(self, ...)
+		self:levelup() -- this triggers processing of auto_classes to learn class talents and gain appropriate stats
 		self:check("birth_create_alchemist_golem")
 		self:check("rnd_boss_on_added_to_level", ...)
 		self.rnd_boss_on_added_to_level = nil
diff --git a/game/modules/tome/class/NPC.lua b/game/modules/tome/class/NPC.lua
index 8cb5f86e1af22253e606b9801b7d590d7e51b403..01f814fbcf3c6dd1a8c7f60b118e4bb8756a6032 100644
--- a/game/modules/tome/class/NPC.lua
+++ b/game/modules/tome/class/NPC.lua
@@ -481,26 +481,15 @@ function _M:addedToLevel(level, x, y)
 		if game.difficulty == game.DIFFICULTY_NIGHTMARE then
 			talent_mult = 1.3
 			life_mult = 1.5
+			nb_classes = util.bound(self.rank - 3.5, 0, 1) -- up to 1 extra class
 		elseif game.difficulty == game.DIFFICULTY_INSANE then
 			talent_mult = 1.8
 			life_mult = 2.0
-			if self.rank >= 3.5 then
-				if self.rank >= 10 then nb_classes = 3
-				elseif self.rank >= 5 then nb_classes = 2
-				else nb_classes = 1
-				end
-			end
+			nb_classes = util.bound(self.rank - 3, 0, 2) -- up to 2 extra classes
 		elseif game.difficulty == game.DIFFICULTY_MADNESS then
 			talent_mult = 2.7
 			life_mult = 3.0
-			if self.rank >= 3.5 then
-				if self.rank >= 10 then nb_classes = 5
-				elseif self.rank >= 5 then nb_classes = 3
-				elseif self.rank >= 4 then nb_classes = 2
-				else nb_classes = 1
-				end
-			end
-	
+			nb_classes = util.bound((self.rank - 3)*1.5, 0, 3) -- up to 3 extra classes
 		end
 		if talent_mult ~= 1 then
 			-- increase level of innate talents
@@ -510,13 +499,16 @@ function _M:addedToLevel(level, x, y)
 					self:learnTalent(tid, true, math.floor(lev*(talent_mult - 1)))
 				end
 			end
-			-- add extra character classes
-			if nb_classes > 0 and not self.randboss and not self.no_difficulty_random_class then
-				-- Note: talent levels from added classes are not adjusted for difficulty
+			-- add the extra character classes (halved for randbosses)
+			if nb_classes > 0 and not self.no_difficulty_random_class then
+				-- Note: talent levels from added classes are not adjusted for difficulty directly
 				-- This means that the NPC's innate talents are generally higher level, preserving its "character"
-				local data = {auto_sustain=true, forbid_equip=false, nb_classes=nb_classes, update_body=true, spend_points=true, autolevel=nb_classes<2 and self.autolevel or "random_boss"}
+				if self.randboss then nb_classes = nb_classes/2 end
+				local data = {auto_sustain=true, forbid_equip=nb_classes<1, nb_classes=nb_classes, update_body=true, spend_points=true, autolevel=nb_classes<2 and self.autolevel or "random_boss"}
 				game.state:applyRandomClass(self, data, true)
+				self[#self+1] = resolvers.talented_ai_tactic("instant") -- regenerate AI TACTICS with the new class(es)
 				self:resolve() self:resolve(nil, true)
+				self:resetToFull()
 			end
 			
 			-- increase maximum life
diff --git a/game/modules/tome/dialogs/debug/RandomActor.lua b/game/modules/tome/dialogs/debug/RandomActor.lua
index a9a07cf22235be7b2578bab3096cc2afe321f619..effd2d2dbd2cfab607760e845d7c0b1d69458a53 100644
--- a/game/modules/tome/dialogs/debug/RandomActor.lua
+++ b/game/modules/tome/dialogs/debug/RandomActor.lua
@@ -52,19 +52,23 @@ lines, fname, lnum = DebugConsole:functionHelp(game.state.createRandomBoss)
 _M.data_help = "#GOLD#DATA HELP#LAST# "..formatHelp(lines, fname, lnum)
 lines, fname, lnum = DebugConsole:functionHelp(game.state.applyRandomClass)
 _M.data_help = _M.data_help.."\n#GOLD#DATA HELP#LAST# "..formatHelp(lines, fname, lnum)
+lines, fname, lnum = DebugConsole:functionHelp(mod.class.Actor.levelupClass)
+_M.data_help = _M.data_help.."\n#GOLD#DATA HELP#LAST# "..formatHelp(lines, fname, lnum)
 
 function _M:init()
 	engine.ui.Dialog.init(self, "DEBUG -- Create Random Actor", 1, 1)
 
 	local tops={0}	self.tops = tops
 	
-	if not _M._base_actor then self:generateBase() end
+--	if not _M._base_actor then self:generateBase() end
 	
 	local dialog_txt = Textzone.new{auto_width=true, auto_height=true, no_color_bleed=true, font=self.font,
 	text=([[Randomly generate actors subject to a filter and/or create random bosses according to a data table.
-Filters and data are interpreted by either game.zone:checkFilter or game.state:createRandomBoss and game.state:applyRandomClass respectively,
-within the _G environment (used by the Lua Console) using the current zone's #LIGHT_GREEN#npc_list#LAST#.  Press #GOLD#'F1'#LAST# for help.
-Mouse over controls for actor preview.  (Actors may be adjusted when placed on to the level.)
+Filters are interpreted by game.zone:checkFilter.
+#ORANGE#Boss Data:#LAST# is interpreted by game.state:createRandomBoss, game.state:applyRandomClass, and Actor.levelupClass.
+Generation is performed within the _G environment (used by the Lua Console) using the current zone's #LIGHT_GREEN#npc_list#LAST#.
+Press #GOLD#'F1'#LAST# for help.
+Mouse over controls for an actor preview (which may be further adjusted when placed on to the level).
 (Press #GOLD#'L'#LAST# to lua inspect or #GOLD#'C'#LAST# to open the character sheet.)
 
 The #LIGHT_BLUE#Base Filter#LAST# is used to filter the actor randomly generated.]]):format(), can_focus=false}
@@ -345,6 +349,7 @@ function _M:generateBase()
 end
 
 -- generate random boss
+-- note: difficulty effects will not be reapplied when a base actor is used
 function _M:generateBoss()
 	local base = _M._base_actor
 	if not base then
diff --git a/game/modules/tome/dialogs/debug/SummonCreature.lua b/game/modules/tome/dialogs/debug/SummonCreature.lua
index 3a58fa4e30837de3f7e1e207a4d382954b9cebfc..e2681afc6eea138a495ed9b17ce2217085e61fab 100644
--- a/game/modules/tome/dialogs/debug/SummonCreature.lua
+++ b/game/modules/tome/dialogs/debug/SummonCreature.lua
@@ -130,6 +130,8 @@ function _M:placeCreature(m)
 					end
 					local Dstring = m.getDisplayString and m:getDisplayString() or ""
 					game.log("#LIGHT_BLUE#Added %s[%s]%s at (%d, %d)", Dstring, m.uid, m.name, x, y)
+					-- special cases
+					if m.alchemy_golem then game.zone:addEntity(game.level, m.alchemy_golem, "actor", util.findFreeGrid(m.x, m.y, 3, false, {[engine.Map.ACTOR]=true})) end
 				end
 			end
 			game:registerDialog(self)
diff --git a/game/modules/tome/resolvers.lua b/game/modules/tome/resolvers.lua
index e761aa18386790e83f6c851c9fe9e84310192da9..284da639ab62c5d7c0548e013b5955f57eae5bdd 100644
--- a/game/modules/tome/resolvers.lua
+++ b/game/modules/tome/resolvers.lua
@@ -199,7 +199,7 @@ function resolvers.resolveObject(e, filter, do_wear, tries)
 					end
 				end
 			end
-			if not worn then print("General Object resolver]", o.uid, o.name, "COULD NOT BE WORN") end
+			if not worn then print("[General Object resolver]", o.uid, o.name, "COULD NOT BE WORN") end
 		end
 		-- if not worn, add to main inventory unless do_wear == false
 		if do_wear ~= false then
@@ -808,7 +808,7 @@ function resolvers.calc.sustains_at_birth(_, e)
 	e.on_added = function(self)
 		for tid, _ in pairs(self.talents) do
 			local t = self:getTalentFromId(tid)
-			if t and t.mode == "sustained" then
+			if t and t.mode == "sustained" and not self:isTalentActive(tid) then
 				self.energy.value = game.energy_to_act
 				self:useTalent(tid, nil, nil, nil, nil, true)
 			end
@@ -913,10 +913,11 @@ end
 --- Resolve tactical ai weights based on talents known
 --	mostly to make sure randbosses have sensible ai_tactic tables
 --	this tends to make npc's slightly more aggressive/defensive depending on their talents
---	@param method = function to be applied to generating the ai_tactic table <not implemented>
--- 	@param tactic_emphasis = average weight of favored tactics <1.5>
---	@param weight_power = smoothing factor to balance out weights <0.5>
---	applied with "on_added_to_level"
+--	@param method = function to be applied to generate the ai_tactic table <generally not implemented>
+--		tactics are updated with "on_added_to_level"
+--		use "instant" to resolve the tactics immediately using the "simple_recursive" method
+-- 	@param tactic_emphasis = average weight of favored tactics, higher values make the NPC more aggressive or defensive <1.5>
+--	@param weight_power = smoothing factor (> 0) to balance out weights <0.5>
 function resolvers.talented_ai_tactic(method, tactic_emphasis, weight_power)
 	local method = method or "simple_recursive"
 	return {__resolver="talented_ai_tactic", method, tactic_emphasis or 1.5, weight_power, __resolve_last=true,
@@ -931,9 +932,10 @@ function resolvers.calc.talented_ai_tactic(t, e)
 	end
 	--print("talented_ai_tactic resolver setting up on_added_to_level function")
 	--print(debug.traceback())
-	e.on_added_to_level = function(e, level, x, y)
+	local on_added = function(e, level, x, y)
 		print("running talented_ai_tactic resolver on_added_to_level function for", e.uid, e.name)
 		local t = e.__ai_tactic_resolver
+		if not t then print("talented_ai_tactic: No resolver table. Aborting") return end
 		e.__ai_tactic_resolver = nil
 		if t.old_on_added_to_level then t.old_on_added_to_level(e, level, x, y) end
 		
@@ -942,7 +944,7 @@ function resolvers.calc.talented_ai_tactic(t, e)
 			return t[1](t, e, level)
 		end
 		-- print("  # talented_ai_tactic resolver function for", e.name, "level=", e.level, e.uid)
-		local tactic_emphasis = t[2] or t.tactic_emphasis or 2 --want average tactic weight to be 2
+		local tactic_emphasis = t[2] or t.tactic_emphasis or 1.5 --desired average tactic weight
 		local weight_power = t[3] or t.weight_power or 0.5 --smooth out tactical weights
 		local tacs_offense = {attack=1, attackarea=1, areaattack=1}
 		local tacs_close = {closein=1, go_melee=1}
@@ -1078,13 +1080,19 @@ function resolvers.calc.talented_ai_tactic(t, e)
 		tactic.tactical_sum=tactical
 		tactic.count = count
 		tactic.level = e.level
-		tactic.type = "computed"
+		tactic.type = "simple_recursive"
 		--- print("### talented_ai_tactic resolver ai_tactic table:")
 		--- for tac, wt in pairs(tactic) do print("    ##", tac, wt) end
 		e.ai_tactic = tactic
 --		e.__ai_tactic_resolver = nil
 		return tactic
 	end
+	if t[1] == "instant" then
+		e.__ai_tactic_resolver = t
+		on_added(e, level or game.level, e.x, e.y)
+	else
+		e.on_added_to_level = on_added
+	end
 end
 
 --- Racial Talents resolver