diff --git a/mbchc.mjs b/mbchc.mjs
index 0d99b39..1e0c0e1 100644
--- a/mbchc.mjs
+++ b/mbchc.mjs
@@ -1,10 +1,11 @@
 export {}
 
-if (!window.AsylumGGTSSAddItems) throw new Error('AsylumGGTSSAddItems() not found, aborting MBCHC loading')
+// Zero-width non-joiner, used to break up ligatures, does nothing here, but an empty string is a falsy value
+const MISSING_PLAYER_DIALOG = {Tag: 'MISSING PLAYER DIALOG: ', Text: '\u200C'}
+
 if (window.MBCHC) throw new Error('MBCHC found, aborting loading')
 window.MBCHC = {
 	VERSION: 'dev.9',
-	TARGET_VERSION: 'R99',
 	NEXT_MESSAGE: 1,
 	LOG_MESSAGES: false,
 	RETHROW: false,
@@ -12,7 +13,7 @@ window.MBCHC = {
 	AUTOHACK_ENABLED: false,
 	LAST_HACKED: null,
 	HISTORY_MODE: false,
-	RE_TITLE: /^[a-zA-Z]+$/,
+	// RE_TITLE: /^[a-zA-Z]+$/,
 	RE_PREF_ACTIVITY_ME: /^@/,
 	RE_PREF_ACTIVITY: /^@@/,
 	RE_ACT_CIDS: /^<(\d+)?:(\d+)?>/,
@@ -25,62 +26,62 @@ window.MBCHC = {
 	RGB_MUTE: '#6c2132',
 	RGB_POLLY: '#81b1e7',
 	UTC_OFFSET: new Date().getTimezoneOffset() * 60 * 1000,
-	HIDE_SPECIAL: ['Activity', 'Emoticon'],
-	HIDE_BODY: ['Blush', 'BodyLower', 'BodyUpper', 'Eyebrows', 'Eyes', 'Eyes2', 'Face', 'Fluids', 'HairBack', 'HairFront', 'Hands', 'Head', 'LeftHand', 'Mouth', 'Nipples', 'Pussy', 'RightHand'],
-	HIDE_CLOTHES: [
-		'Cloth',
-		'ClothAccessory',
-		'Necklace',
-		'Suit',
-		'ClothLower',
-		'SuitLower',
-		'Bra',
-		'Corset',
-		'Panties',
-		'Socks',
-		'RightAnklet',
-		'LeftAnklet',
-		'Garters',
-		'Shoes',
-		'Hat',
-		'HairAccessory3',
-		'HairAccessory1',
-		'HairAccessory2',
-		'Gloves',
-		'Bracelet',
-		'Glasses',
-		'Mask',
-		'TailStraps',
-		'Wings',
-	],
-	HIDE_ITEMS: [
-		'ItemMisc',
-		'ItemEars',
-		'ItemHead',
-		'ItemNose',
-		'ItemHood',
-		'ItemAddon',
-		'ItemMouth',
-		'ItemMouth2',
-		'ItemMouth3',
-		'ItemArms',
-		'ItemNeckAccessories',
-		'ItemNeck',
-		'ItemNeckRestraints',
-		'ItemNipples',
-		'ItemNipplesPiercings',
-		'ItemBreast',
-		'ItemTorso',
-		'ItemTorso2',
-		'ItemHands',
-		'ItemPelvis',
-		'ItemVulva',
-		'ItemVulvaPiercings',
-		'ItemDevices',
-		'ItemLegs',
-		'ItemFeet',
-		'ItemBoots',
-	],
+	// HIDE_SPECIAL: ['Activity', 'Emoticon'],
+	// HIDE_BODY: ['Blush', 'BodyLower', 'BodyUpper', 'Eyebrows', 'Eyes', 'Eyes2', 'Face', 'Fluids', 'HairBack', 'HairFront', 'Hands', 'Head', 'LeftHand', 'Mouth', 'Nipples', 'Pussy', 'RightHand'],
+	// HIDE_CLOTHES: [
+	// 	'Cloth',
+	// 	'ClothAccessory',
+	// 	'Necklace',
+	// 	'Suit',
+	// 	'ClothLower',
+	// 	'SuitLower',
+	// 	'Bra',
+	// 	'Corset',
+	// 	'Panties',
+	// 	'Socks',
+	// 	'RightAnklet',
+	// 	'LeftAnklet',
+	// 	'Garters',
+	// 	'Shoes',
+	// 	'Hat',
+	// 	'HairAccessory3',
+	// 	'HairAccessory1',
+	// 	'HairAccessory2',
+	// 	'Gloves',
+	// 	'Bracelet',
+	// 	'Glasses',
+	// 	'Mask',
+	// 	'TailStraps',
+	// 	'Wings',
+	// ],
+	// HIDE_ITEMS: [
+	// 	'ItemMisc',
+	// 	'ItemEars',
+	// 	'ItemHead',
+	// 	'ItemNose',
+	// 	'ItemHood',
+	// 	'ItemAddon',
+	// 	'ItemMouth',
+	// 	'ItemMouth2',
+	// 	'ItemMouth3',
+	// 	'ItemArms',
+	// 	'ItemNeckAccessories',
+	// 	'ItemNeck',
+	// 	'ItemNeckRestraints',
+	// 	'ItemNipples',
+	// 	'ItemNipplesPiercings',
+	// 	'ItemBreast',
+	// 	'ItemTorso',
+	// 	'ItemTorso2',
+	// 	'ItemHands',
+	// 	'ItemPelvis',
+	// 	'ItemVulva',
+	// 	'ItemVulvaPiercings',
+	// 	'ItemDevices',
+	// 	'ItemLegs',
+	// 	'ItemFeet',
+	// 	'ItemBoots',
+	// ],
 	MAP_ACTIONS: { // ActivityFemale3DCG
 		// action
 		'nod|yes': {Head: {self: 'Nod'}},
@@ -182,11 +183,11 @@ window.MBCHC = {
 		[/([^\\])\$/g, '$1\\.?$$'],
 	],
 	SUBCOMMANDS_MBCHC: {
-		versions: {desc: 'show the mod versions across the room', cb: mbchc => mbchc.inform(mbchc.gather_versions().map(c => `
${c.name} (${c.cid}): ${c.version}
`).join(''))},
+		// versions: {desc: 'show the mod versions across the room', cb: mbchc => mbchc.inform(mbchc.gather_versions().map(c => `${c.name} (${c.cid}): ${c.version}
`).join(''))},
 		autohack: {desc: 'toggle the autohack feature', cb: mbchc => mbchc.inform(`Autohack is now ${(mbchc.AUTOHACK_ENABLED = !mbchc.AUTOHACK_ENABLED) ? 'enabled' : 'disabled'}`)}, // eslint-disable-line no-cond-assign
-		disappear: {desc: 'become invisible (requires anal hook -> hair)', cb: mbchc => mbchc.disappear()},
+		// disappear: {desc: 'become invisible (requires anal hook -> hair)', cb: mbchc => mbchc.disappear()},
 		donate: {desc: 'Buy data and send it to recipient', args: {TARGET: {}}, cb: (mbchc, args) => mbchc.donate_data(args[0])},
-		title: {desc: 'set a custom title (WIP)', args: {TITLE: {}}, cb: (mbchc, args) => mbchc.title(args[0])},
+		// title: {desc: 'set a custom title (WIP)', args: {TITLE: {}}, cb: (mbchc, args) => mbchc.title(args[0])},
 		tz: {desc: 'set target\'s UTC offset', args: {OFFSET: {}, '[TARGET]': {}}, cb: (mbchc, args) => mbchc.set_timezone(args)},
 		'purge!': {desc: 'delete MBCHC online saved data', cb(mbchc) {
 			if (window.Player.OnlineSettings.MBCHC) {
@@ -209,10 +210,8 @@ window.MBCHC = {
 				const processed = {self: (actions.self) ? actions.self.split('|').concat(all) : all, others: (actions.others) ? actions.others.split('|').concat(all) : all}
 				for (const zone of zones.split(',')) unwound[`Item${zone}`] = processed
 			}
-
 			for (const verb of verbs.split('|')) this.DO_DATA.verbs[verb] = unwound
 		}
-
 		for (const [ag, zones] of Object.entries(this.MAP_ZONES)) for (const zone of zones) this.DO_DATA.zones[zone] = ag
 	},
 	settings(setting = null) {
@@ -224,7 +223,6 @@ window.MBCHC = {
 			if (!window.Player.OnlineSettings.MBCHC) window.Player.OnlineSettings.MBCHC = {}
 			cb.call(this, window.Player.OnlineSettings.MBCHC)
 		}
-
 		window.ServerAccountUpdate.QueueData({OnlineSettings: window.Player.OnlineSettings})
 	},
 	log(level, message) {
@@ -244,7 +242,6 @@ window.MBCHC = {
 			const rest = result.slice(1)
 			result = first + rest
 		}
-
 		if (options.dot && this.RE_LAST_LETTER.test(result)) result = `${result}.`
 		return (result)
 	},
@@ -331,7 +328,7 @@ window.MBCHC = {
 		if (window.Player.Money < cost) throw new Error('not enough money')
 		window.CharacterChangeMoney(window.Player, -cost)
 		window.ServerSend('ChatRoomChat', {Content: 'ReceiveSuitcaseMoney', Type: 'Hidden', Target: char.cid})
-		window.ChatRoomMessage({Sender: window.Player.cid, Type: 'Action', Content: `You've bought data for $${cost} and sent it to ${char.dn}.`, Dictionary: [{Tag: 'MISSING PLAYER DIALOG: ', Text: ''}]})
+		window.ChatRoomMessage({Sender: window.Player.cid, Type: 'Action', Content: `You've bought data for $${cost} and sent it to ${char.dn}.`, Dictionary: [MISSING_PLAYER_DIALOG]})
 	},
 	run_activity(char, ag, action) {
 		try {
@@ -357,14 +354,13 @@ window.MBCHC = {
 		return ({Tag: `${type}Character`, MemberNumber: cid, Text: this.cid2char(cid).dn})
 	},
 	send_activity(message) {
-		const dict = [{Tag: 'MISSING PLAYER DIALOG: ', Text: '\u200C'}] // Zero-width non-joiner, used to break up ligatures, does nothing here, but an empty string is a falsy value
+		const dict = [MISSING_PLAYER_DIALOG]
 		const cids = message.match(this.RE_ACT_CIDS)
 		if (cids) {
 			message = message.replace(this.RE_ACT_CIDS, '')
 			if (cids[1]) dict.push(this.cid2dict('Source', cids[1]))
 			if (cids[2]) dict.push(this.cid2dict('Target', cids[2]), this.cid2dict('Destination', cids[2]))
 		}
-
 		window.ServerSend('ChatRoomChat', {Type: 'Action', Content: message, Dictionary: dict})
 	},
 	receive(data) {
@@ -377,10 +373,8 @@ window.MBCHC = {
 				if (payload.type === 'greetings') this.hello(char)
 				break
 			}
-
 			default: // If we don't know the type it may be from a newer version
 		}
-
 		return true
 	},
 	hello(char = null) {
@@ -390,22 +384,22 @@ window.MBCHC = {
 		if (char) message.Target = char.cid
 		window.ServerSend('ChatRoomChat', message)
 	},
-	disappear() {
-		const item = window.InventoryGet(window.Player, 'ItemButt')
-		if (!item || !item.Asset || !item.Asset.Name) throw new Error('butt seems empty')
-		if (item.Asset.Name !== 'AnalHook') throw new Error('butt seems occupied by something other than the anal hook')
-		if (!item.Property.Type || item.Property.Type !== 'Hair') throw new Error('anal hook seems not tied to hair')
-		item.Property = {Type: 'Hair', Hide: this.HIDE_ALL}
-		window.CharacterRefresh(window.Player, true, true)
-	},
-	title(title) { // WIP
-		if (this.empty(title)) throw new Error('empty title')
-		title = this.normalise_message(title, {trim: true, up: true, low: true})
-		if (title.length > 16) throw new Error('title too long')
-		if (!this.RE_TITLE.test(title)) throw new Error('invalid title')
-		window.TitleSet(title)
-		// Window.TitleList.push({Name: title, Requirement: () => true}) // check for existing first
-	},
+	// disappear() {
+	// 	const item = window.InventoryGet(window.Player, 'ItemButt')
+	// 	if (!item || !item.Asset || !item.Asset.Name) throw new Error('butt seems empty')
+	// 	if (item.Asset.Name !== 'AnalHook') throw new Error('butt seems occupied by something other than the anal hook')
+	// 	if (!item.Property.Type || item.Property.Type !== 'Hair') throw new Error('anal hook seems not tied to hair')
+	// 	item.Property = {Type: 'Hair', Hide: this.HIDE_ALL}
+	// 	window.CharacterRefresh(window.Player, true, true)
+	// },
+	// title(title) { // WIP
+	// 	if (this.empty(title)) throw new Error('empty title')
+	// 	title = this.normalise_message(title, {trim: true, up: true, low: true})
+	// 	if (title.length > 16) throw new Error('title too long')
+	// 	if (!this.RE_TITLE.test(title)) throw new Error('invalid title')
+	// 	window.TitleSet(title)
+	// 	// Window.TitleList.push({Name: title, Requirement: () => true}) // check for existing first
+	// },
 	copy_fbc_trigger(trigger) {
 		const result = {
 			Type: 'Action',
@@ -418,15 +412,15 @@ window.MBCHC = {
 		this.remove_fbc_hook()
 		delete this.remove_fbc_hook
 		window.bce_ActivityTriggers.push(...window.bce_ActivityTriggers.filter(t => t.Type === 'Emote').map(t => this.copy_fbc_trigger(t)))
-		/* (["anim", "pose"]).forEach(tag => {let cmd = window.Commands.find(c => tag === c.Tag); if (cmd) cmd.AutoComplete = this[`complete_fbc_${tag}`]}) */ // this line explodes, don't ask me why
+		/* (["anim", "pose"]).forEach(tag => {let cmd = window.Commands.find(c => tag === c.Tag); if (cmd) cmd.AutoComplete = this[`complete_fbc_${tag}`]}) */ // this line explodes, because it needs a semicolon in front if it
 		let cmd = window.Commands.find(c => c.Tag === 'anim')
 		if (cmd) cmd.AutoComplete = this.complete_fbc_anim
 		cmd = window.Commands.find(c => c.Tag === 'pose')
 		if (cmd) cmd.AutoComplete = this.complete_fbc_pose
 	},
-	gather_versions() {
-		return (window.ChatRoomCharacter.filter(c => c.MBCHC).map(c => ({name: c.dn, cid: c.cid, version: c.MBCHC.VERSION})))
-	},
+	// gather_versions() {
+	// 	return (window.ChatRoomCharacter.filter(c => c.MBCHC).map(c => ({name: c.dn, cid: c.cid, version: c.MBCHC.VERSION})))
+	// },
 	find_timezone(char) {
 		const timezones = this.settings('timezones')
 		if (timezones && typeof timezones[char.cid] === 'number') return (timezones[char.cid])
@@ -629,15 +623,20 @@ window.MBCHC = {
 		window.ElementValue('InputChat', history[found])
 		window.ChatRoomLastMessageIndex = found
 	},
+	focus_chat_checks() { // we only want to catch chatlog and canvas (no map though) keypresses
+		if (document.activeElement === document.body) return true
+		if (document.activeElement.id !== 'MainCanvas') return false
+		return !window.ChatRoomMapVisible
+	},
 	focus_chat_whitelist(event) {
 		if (event.ctrlKey && event.key === 'v') return true // Ctrl+V should paste
 		return false
 	},
-	focus_chat(event) { // TODO: this is not ideal, but it will have to do for now
+	focus_chat(event) {
 		if (event.repeat) return // Only unique presses please
-		if (document.querySelector('#InputChat')?.style.display !== 'inline') return // Input chat missing
-		if (['InputChat', 'bce-message-input'].includes(document.activeElement.id)) return // Focus already set
+		if (!this.focus_chat_checks()) return
 		if ([event.altKey, event.ctrlKey, event.metaKey].some(Boolean) && !this.focus_chat_whitelist(event)) return // Alt, ctrl and meta should all be false
+		if (document.querySelector('#InputChat')?.style.display !== 'inline') return // Input chat missing
 		window.ElementFocus('InputChat')
 	},
 	loader() {
@@ -653,14 +652,13 @@ window.MBCHC = {
 			{Tag: 'activity', Description: '[Message]: Send a custom activity (or "@@Message", or "@Message" as yourself)', Action: this.command_activity},
 			{Tag: 'do', Description: ': Do an activity, as if clicked on its button ("/do" for help)', Action: this.command_do, AutoComplete: this.complete_do},
 		]
-		this.HIDE_ALL = this.HIDE_SPECIAL.concat(this.HIDE_BODY).concat(this.HIDE_CLOTHES).concat(this.HIDE_ITEMS)
+		// this.HIDE_ALL = this.HIDE_SPECIAL.concat(this.HIDE_BODY).concat(this.HIDE_CLOTHES).concat(this.HIDE_ITEMS)
 		this.CommandsKey = CommandsKey /* eslint-disable-line no-undef */ // window.CommandsKey is undefined
 		this.RE_ACTIVITY = new RegExp(`^${this.CommandsKey}activity `)
 		this.PREF_ACTIVITY = `${this.CommandsKey}activity `
 		this.COMP_HINT = document.createElement('div')
 		this.COMP_HINT.id = 'mbchcCompHint'
 		const css = document.createElement('style')
-		css.type = 'text/css'
 		css.textContent = `
 			#TextAreaChatLog .mbchc, #TextAreaChatLog .mbchc {
 				background-color: ${this.RGB_POLLY};
@@ -715,9 +713,9 @@ window.MBCHC = {
 			}
 		})
 		this.before('ChatRoomDrawCharacterOverlay', (C, CharX, CharY, Zoom, _Pos) => {
-			if (window.ChatRoomHideIconState < 1 && C.MBCHC) {
-				window.DrawRect(CharX + (175 * Zoom), CharY, 50 * Zoom, 50 * Zoom, C.MBCHC.VERSION === window.Player.MBCHC.VERSION ? this.RGB_POLLY : this.RGB_MUTE)
-			}
+			// if (window.ChatRoomHideIconState < 1 && C.MBCHC) {
+			// 	window.DrawRect(CharX + (175 * Zoom), CharY, 50 * Zoom, 50 * Zoom, C.MBCHC.VERSION === window.Player.MBCHC.VERSION ? this.RGB_POLLY : this.RGB_MUTE)
+			// }
 			if (window.ChatRoomHideIconState < 1 && C.MBCHC_LOCAL && typeof C.MBCHC_LOCAL.TZ === 'number') {
 				const hours = new Date(window.CommonTime() + this.UTC_OFFSET + (C.MBCHC_LOCAL.TZ * 60 * 60 * 1000)).getHours()
 				window.DrawTextFit(hours < 10 ? '0' + hours.toString() : hours.toString(), CharX + (200 * Zoom), CharY + (25 * Zoom), 46 * Zoom, 'white', 'black')
@@ -780,13 +778,14 @@ window.MBCHC = {
 		// Footer
 		this.LOADED = true
 		this.log('info', `loaded version ${this.VERSION}`)
-		if (window.GameVersion !== this.TARGET_VERSION) this.log('warn', `Game version doesn't match the target ("${this.TARGET_VERSION}"), beware of incompatibilities`) // TODO: betas are like R86Beta1; cheat?
 		if ((window.CurrentModule === 'Online') && (window.CurrentScreen === 'ChatRoom')) {
 			for (const c of window.ChatRoomCharacter) this.update_char(c)
 			this.player_enters_room()
 		}
 	},
 	preloader() {
+		if (!window.AsylumGGTSSAddItems) throw new Error('AsylumGGTSSAddItems() not found, aborting MBCHC loading')
+		if (!window.bcModSdk) throw new Error('SDK not found, please load with (or after) FUSAM')
 		this.SDK = window.bcModSdk.registerMod({name: 'MBCHC', fullName: 'Mute\'s Bondage Club Hacks Collection', version: this.VERSION, repository: 'https://code.fleshless.org/mute/MBCHC/'})
 		this.before = (name, cb) => this.SDK.hookFunction(name, 0, (nextargs, next) => {
 			try {
@@ -811,3 +810,5 @@ window.MBCHC = {
 }
 
 window.MBCHC.preloader()
+
+// TODO: with the removal of /mbchc versions it makes no sense to keep track of the mod version and the whole hello() infrastructure can go away