# Inigo Lapwood - Full Site Content > This document contains the full text content of inigolapwood.com to assist AI models and agents in understanding Inigo Lapwood's background, projects, and work. --- ## Home *URL: https://inigolapwood.com/* Hi! I'm Inigo. Welcome to my digital garden shed. It's where I collect together some of the various things I work on and enjoy. Many of those things involve data, and the way different data interconnect. In that spirit I've provided a little map, showing how projects on this site relate to each other. Feel free to explore around it. As you explore the map, the database of links below will update to reflect your position on the map, and show you the most relevant links. You can also filter with a selectable tag system to jump directly where you want. If exploring a d3 physics node map isn't your idea of a good time (inexplicably) just scroll down to one of the four subject hub pages at the top of the database section below. They're little introductory summaries: about the four big subjects into which I pour most of my time at the moment. Thanks for taking an interest. If you'd like to get in touch, drop me an email. I'm almost always happy to have a chat! --- ## Artship *URL: https://inigolapwood.com/artship.html* **Artship: another floating arts centre** — The history of the last surviving pre-war diesel coaster in the UK and its restoration into a new public space. With her keel laid in 1938, Artship is one of the very first diesel coasters. There used to be thousands of these ships. She's now the only one left: the last surviving diesel coaster in the UK from before the war. But it's still within living memory that these ships used to fill the docks. My great grandad was a docker - he used to unload these exact ships in this exact dock throughout the 40s and 50s. *[Image: Artship in the docks by Canary Wharf, alongside an archive photo of a similar coaster in the docks in the 1950s]* At that time they were the lifeblood of short-sea shipping in Europe - they were a critical connection between Britain and the continent, and part of the industry and trade that brought Europe back together after the war. We found the ship in Flensburg, Germany, and brought her through the Baltic and across the North Sea under her own power - finally arriving in London December 2025. She's a tangible piece of the docks' history of industry and labour, and a symbol of something larger - London's connection to the rest of the world. W're working on Artship's restoration at the moment. When open, she'll operate in conjunction with Theatreship - as one arts centre split across the two ships. The cargo hold is an incredibly beautiful, cavernous, space - with the riveted steel ribs on display all the way through. It feels like being in the belly of a giant industrial whale. When open, the hold will serve as an exhibition and installation space for larger scale works of art, and as a space for live music and experimental sound. In total the ship will have a public capacity of around 700 people As the project unfolds I'll add more to this site about Artship over time. For now you can visit the main Artship site, or read about our sister ship Theatreship. Or use the link explorer below to find other closely (and more tangentially) related topics. --- ## Theatreship *URL: https://inigolapwood.com/theatreship.html* **Theatreship: a floating arts centre** - A 1913 converted cargo ship serving as a theatre, cinema, and grassroots music venue in Canary Wharf. Theatreship does exactly what it says on the tin - it's a theatre on a ship! Specifically, a new theatre, cinema, and grassroots music venue onboard a converted historic cargo ship. The ship itself was built in 1913, so 113 years old this year! She was trading cargo continuously for over 100 of those years. The project was set up out of a deep love for historic ships, and what those ships represent: programming onboard takes seriously the idea of what it is to be a boat in a port. We're a vessel - so we try to be just that - an open space that holds other people's ideas. Practically speaking this means we end up with programming that's so much more imaginative, creative, and inventive that what I could ever think to programme myself. Being in a dock is important too: for centuries the docks have been the entry point to London for ideas from all over the world. It's that special alchemy - different ideas landing in one place and mixing, and the entirely new creations emerging from that process - that's part of what makes East London so special. In our own small way we try to continue that dockland alchemy onboard, providing a shared space for people, ideas, and art forms to arrive and mix, and celebrating what emerges from that chemistry. As a historic ship we try to provide a connection to a shared past. The one thing that unites everyone in East London today is this place: it's a tautology, but everyone here is currently here. We hope that Theatreship reflects that sense of place - it's a tangible physical link to our shared past - and, open to the public now, it forms a shared present too. I'll add more to this site about Theatreship over time. For now you can read about the history of Theatreship, visit the main Theatreship site, or read about our sister ship Artship. Or use the link explorer below to find other closely (and tangentially) related topics. --- ## YourStaffKnow (YSK) Hub *URL: https://inigolapwood.com/YSK_hub.html* **YourStaffKnow / R-ai:** — Democratising decision-making in large organisations through small data, semantic NLP, and information theory. Or why it's important to really take seriously listening to what people know. YourStaffKnow (now R-ai) was set up with a very simple purpose: to help democratise decision making in large organisations. In part because that's a good thing in itself, but also because we believed that that leads to better decisions being made. The core idea was that, often, the people with the most important information in a company are the people in touch with day to day operations, lower down in a hierarchical structure. The people making the major strategic decisions are often at the top, and sometimes they can be insulated from that critical information. They're insulated, partly because in very large organisations, it can be difficult for junior employees to feedback directly to the executive team, and partly because if everyone did feedback, there'd be an enormous, unmanageable, quantity of information to process. The goal was to bypass the social hierarchy by talking to everyone equally and anonymously, and to bypass the information processing bottleneck with automated analysis. The technology I worked on allowed us to talk to every member of an organisation, and then synthesise that into quantitative data and actionable advice, often in the direct words of staff themselves. The underlying tech focussed on small data rather than big data. The underlying thought was that Shannon information theory is a really powerful tool for understanding quantity of information, but when it comes to language a lot of what's relevant isn't quantity, but meaning. Different bits of information are more important or relevant than other bits, partly because of their relation to us, and partly because of their relation to other bits of information. In this way the work owed more of a conceptual debt to Wittgenstein than to more traditional titans in computer science. Our solution therefore focussed on thoughtful feature encoding so that the semantic relationships between linguistic tokens were captured. This then fed through an interacting set of clustering algorithms and feed-forward neural networks (acting as classifiers). The approach allowed us to get powerful results on very small datasets, and meant we could promise absolute anonymity - data was never kept and fed into an all-hungry LLM - the analysis was local, fast, and bespoke. I loved the work - it felt like a really interesting problem, and it meant a lot to feel like this process - properly listening to people, made a difference. We were really happy with the impact the company had, we got to work with some fantastic organisations, Danone, B-LAB, USAID, Kraft, and the FCO, and we received some really lovely testimonials. We were credited with helping to achieve 'a growth in turnover of $150M to $1B' by Lorna Davis, then CEO of Kraft China, and of catalysing a 'turnaround to double digit growth' by Thomas Kunz, then global head of Danone Dairy/Danone Waters. It felt like an incredible vindication to see companies benefit so significantly from paying careful attention to the trapped knowledge in their teams, and we're really glad we were able to play a role in emancipating that knowledge. After 5 years as lead developer, I was appointed as a co-director of the company in 2024. In 2026 I made the difficult decision to exit the company - partly to focus on public benefit applications of the technology and partly to free up time for my crippling addiction to boats! I still really believe in the R-ai mission, it was a big part of my life for a long time. I keep a friendly toe dipped in the R-ai waters in a consultancy capacity, and I still use some of the principles I developed at the company in my advisory work with the Canal and River Trust and National Historic Ships. If you've got a natural language information processing problem you'd like to discuss please do get in touch! I'll almost always make time for a chat about it. If you'd like to know more about R-ai you can head to their main site here, if you'd like to read about some of those public benefit applications, have a look at my advisory work. If you'd like to read about some other, more frivolous coding projects, checkout this wordle solver. Or just explore the map below to find other connections. :) --- ## Boomtown *URL: https://inigolapwood.com/boomtown.html* **Boomtown Festival overview:** — Organising chaos - building a city in three weeks - and a bit about their brilliant model of how to accomplish impossible things through collaborative endeavour Boomtown festival is a mad and brilliant project. It's the world's largest immersive theatre event. Every year some 70,000 people descend on a valley in the South Downs, and inhabit a living city, constructed of beautiful sets, and populated with a cast of over 1000 actors and performers. For the past three years I've been one of their Theatrical Ground Crew Directors - my role being to populate the city with those performers and make sure that people's interractions with them run smoothly and cohesively. As a task it's a really interesting one - it's part performance, part narrative design, and part human traffic flow. I particularly enjoyed working on 'in-world' narrative reasons or game mechanics to move people through the site - it gave us really fine-grained control over the district and the wider site, without breaking immersion or the game. As a project I also think Boomtown is an absolutely fantastic case study in collaborative working. Over about a three week period a whole city emerges out of the ground - like a mad mushroom. As an organisational structure it delivers this with a huge amount of trust - delegating significant responsibility and creative freedom to a community of collaborators, but providing a structure and a world in which these individual creative choices can be integrated. It relies critically on a brilliant team of core organisers who can see the links between these different collaborators and connect them together. This way of working: of devolved responsibility with careful integration is one that I think lots of organisations can learn from - and this idea, of an entity being the connective tissue that holds a wide variety of brilliant and independent stakeholders together is part of what I like ideologically about the waterways too. They - like the Boomtown city, provide a connective link to bring people together, but enough creative freedom and opportunity for each individual project to thrive. To read more about connecting people through the waterways - go to Theatreship or Advisory work. To read more about organisational structures go to YourStaffKnow. To read more about traffic flow, go to a defence of bus lanes (and the humble mini-roundabout). Alternatively explore the link map below to find other connections to this page. --- ## Advisory Work *URL: https://inigolapwood.com/YSK_hub.html* **Advisory & Board Work: Maritime Heritage** — guidance and policy work for National Historic Ships, the Canal and River Trust I now spend quite a lot of time in advisory work. I really believe very deeply in the importance of the waterways. For me personally they represented a real life-line - a home, a community, and a place to express my labour. I also believe deeply in the social importance of a connection to a shared history. It's an idea that undergirds a lot of my work at Theatreship and Artship. I currently sit on the Council of Experts for National Historic Ships UK, and the London and South East Advisory Board for the Canal and River Trust - I find these both really helpful forums to engage with. Recent/current projects: I recently worked on restructuring the National Historic Ships register. Historically the online register was absolutely excellent as a database structure for human users - information was well-structured and easy to drill down to by clicking through filters. This meant though that much of the data was 'hidden' behind clicks. The way in which people engage with information on the internet is changing. The invisibility of this data to LLMs meant that queries about historic ships were being met with hallucinated responses. NHS-UK is the custodian of the registers. They hold this really significant repository of data on the historic ships in the UK. I worked in a voluntary capacity to aggregate the database, and reformat it into a separate directory structure, formatted in schema markup to make it maximally available to machine readers. I'm also currently exploring setting up an All-Party Parliamentary Group for mobile heritage (planes, trains & automobiles... &, importantly to me, ships!). Built heritage has significant statutory protection in the UK, but it was a recent discovery for me that floating vessels have almost none. In fact a ship only receives statutory protection in the planning process when it sinks! It's then listed as a designated wreck. It's a perverse incentive that operating vessels are more at risk than submerged ones. I'd like to work on setting up an APPG, informed by cross-sector partners and collaborators, to go about finding sensible ways to fill this regulatory gap. Britain's industrial heritage is a hugely significant part of our cultural story - and we're losing many of the examples of that heritage at an alarming rate. I'd like to see processes put in place to help stem that tide - somewhere between the protections provided for listed buildings, and the protections provided for pieces of nationally significant artwork. If you'd like to get involved or support with setting this up please do get in touch! --- ## Mini Roundabouts *URL: https://inigolapwood.com/mini_roundabouts.html* **A defense of bus lanes (and the humble mini-roundabout)** — Traffic engineering, the 'stoppler' effect, and why changing human behaviour is often better than building new infrastructure. My beloved grandad, Ken Huddart, was a traffic engineer. He dedicated much of his life to traffic safety, and, throughout his tenure at the GLC, was a particular proponent of the introduction of bus lanes and mini roundabouts. This unfortunately makes him one of the most hated men in Britain - few people whose names are entirely unknown are so regularly cursed! I think that bus lanes and mini-roundabouts are absolutely genius though - and I think the way in which they're genius suggests a useful approach to solving other entirely unrelated problems, which is why I thought I'd add a quick note about them. Fundamentally Ken treated traffic not as an infrastructure problem but as a behaviour problem, and mini-roundabouts and bus lanes both reflect this thinking. Both of them try to relieve congestion, but rather than building new physical infrastructure and new roads to relieve the pressure, they just apply a little bit of paint to a communicate a different way of using the existing infrastructure to accomplish the same effect. The mini roundabout takes an intersection that would normally require stopping, and turns it into one that permits continuous flow. Stopping creates a sort of doppler effect of stopping (which I'm calling a 'stoppler' effect) - you stop, and then the slight delay in reaction time means the car behind you stops just that little bit slower - and so on back up the chain. Relatedly, even on roads where no incident has occurred, this wave of brake-lights rippling back and causing ever compounding time-delays can create major traffic jams. This is why you get massive jams when an accident happens on the other side of a dual carriage way, even when the side you're driving on is completely clear - that small initial slow down of people slowing to look, echoes back up the line, becoming a significant stopped period by the time you're several hundred cars back. By directing people to continuous flow rather than necessitated stopping, the whole system moves faster. Likewise with the bus lane - it seems counter-intuitive that reducing the road area available cars would actually speed up global traffic flow, but it's true. Cars and buses use the roads in very different ways - cars want to stop as infrequently as possible - getting from A to B, buses by their nature, must stop frequently to service their routes. As above - the stoppler effect means that introducing stops introduces significant knock on delays. By separating out the two different use-cases the cars are protected from this, and able to move in a homogenous flow. Average journey times decrease significantly, in places by 16%! and all just with a painted line. In summary, I think people should be nicer to Ken (even if they have no idea who he is). In my mind he was a great man and a tireless public servant. I also think his way of thinking suggests a useful paradigm - rather than leaping in and trying to solve a first-order problem, it's worth taking a step back and look at the way that people are interacting with the problem. Sometimes just communicating carefully in a different way can change people's behaviour and go a significant way to resolving the issue, without having to build the M25 - whatever your M25 might be... --- ## Wordle *URL: https://inigolapwood.com/wordle.html* **Wordle Solver** — a couple of different algorithmic approaches to taking the fun out of the game I came up with a couple of different options. This version is a modified MiniMax, with a lookahead model that manages to reduce the risk of hitting trap words: like when you get _ight and have no option but to blindly guess might, sight, fight etc. It solves hard-mode wordle for 99.57% of possible wordle words, and averages 3.62 guesses. It fails on boxer, foyer, gaunt, watch, caste, tight, shame, ratty, found, mound. It's quite a computationally intensive solution, but it's still more computationally efficient than me. wordle_recursive_minimax.py Click here to copy it to test for yourself ```python import os import time # Core logic def load_words(filename): if not os.path.exists(filename): return [] with open(filename, 'r', encoding='utf-8') as file: return [line.strip().lower() for line in file if len(line.strip()) == 5] def get_feedback(guess, target): """Wordle colour engine.""" feedback = ['B'] * 5 t_chars = list(target) g_chars = list(guess) # Greens for i in range(5): if g_chars[i] == t_chars[i]: feedback[i] = 'G' t_chars[i] = g_chars[i] = None # Yellows for i in range(5): if g_chars[i] is not None and g_chars[i] in t_chars: feedback[i] = 'Y' t_chars[t_chars.index(g_chars[i])] = None return "".join(feedback) # Recursive lookahead def get_buckets(guess, pool): """Groups a pool of words by the feedback pattern they generate.""" buckets = {} for target in pool: pattern = get_feedback(guess, target) if pattern not in buckets: buckets[pattern] = [] buckets[pattern].append(target) return buckets def is_guaranteed_solve(guess, pool, turns_left): """ Recursive check: Can this guess solve every word in the pool within the remaining turns? """ if turns_left == 1: return len(pool) == 1 and pool[0] == guess buckets = get_buckets(guess, pool) # For every possible colour pattern we might get back... for pattern, sub_pool in buckets.items(): if pattern == "GGGGG": continue # We must find at least one word in the next turn that can handle this sub-pool. success = False # In hard mode, we can only guess from the sub_pool itself for next_guess in sub_pool: if is_guaranteed_solve(next_guess, sub_pool, turns_left - 1): success = True break if not success: return False return True def get_best_hard_mode_guess(current_pool, attempts_made): """ Decision Engine: 1. If pool is large, use minimax-ish (split into smallest buckets) 2. If pool is small (<12), use Recursive Safety to guarantee 100% """ turns_left = 6 - attempts_made if len(current_pool) <= 2: return current_pool[0] # Step A: Safety First (for trap words & Harold Lloyd) # If pool is small, check if any word guarantees a win if len(current_pool) < 12: for guess in current_pool: if is_guaranteed_solve(guess, current_pool, turns_left): return guess # Step B: minimax (for general efficiency) best_word = "" min_worst_case = float('inf') for guess in current_pool: buckets = get_buckets(guess, current_pool) worst_case = max(len(b) for b in buckets.values()) # Tie-breaker: If worst-cases are equal, pick the word that creates the most total buckets (a vaguely entropy based idea) if worst_case < min_worst_case: min_worst_case = worst_case best_word = guess elif worst_case == min_worst_case: if len(buckets) > len(get_buckets(best_word, current_pool)): best_word = guess return best_word # Validation tester def run_final_test(): targets = load_words('possible_targets.txt') if not targets: print("Error: possible_targets.txt not found.") return print("-" * 45) print(" recursive validation ") print("-" * 45) # I initially liked 'raise' as a first guess - near-optimal plus gives the satisfaction of a potential correct first guess, but I've since run the maths on this and think 'salet' reduces the space more, at the cost of a hole in one. I've left blank for now for you to play around with. first_guess = "" print(f"Opening word: {first_guess.upper()}\n") stats = {i: 0 for i in range(1, 8)} failures = [] start_time = time.time() for i, secret_word in enumerate(targets): current_pool = targets.copy() attempts = 1 while attempts <= 6: if attempts == 1: guess = first_guess else: guess = get_best_hard_mode_guess(current_pool, attempts - 1) pattern = get_feedback(guess, secret_word) if pattern == "GGGGG": stats[attempts] += 1 break # Hard Mode filtering current_pool = [w for w in current_pool if get_feedback(guess, w) == pattern] attempts += 1 else: stats[7] += 1 failures.append(secret_word) print(f"FAILED: {secret_word.upper()}") if (i + 1) % 50 == 0: rate = ((i + 1 - stats[7]) / (i + 1)) * 100 print(f"Checked {i+1}/{len(targets)}... Current Win Rate: {rate:.2f}%") # Final report total = len(targets) duration = time.time() - start_time print("\n" + " FINAL PERFORMANCE SUMMARY ".center(45, "=")) print(f"Win Rate: {((total - stats[7])/total)*100:.2f}%") print(f"Average Guesses: {sum(k*stats[k] for k in range(1,7))/(total-stats[7]):.3f}") print(f"Total Time: {duration:.1f}s") print(f"Failures: {', '.join(failures) if failures else 'None'}") print("=" * 45) if __name__ == "__main__": run_final_test() The second version is a modified Markov model - I wanted to try to incorporate statistical features of letter order in English words to adjust a probability estimate for greater accuracy to beat the final 10 words. It performs with significantly lower accuracy than the simple minimax lookahead. wordle_hybrid_markov.py Click here to copy it to test for yourself Python import os import time # File loading def load_words(filename): if not os.path.exists(filename): return [] with open(filename, 'r', encoding='utf-8') as file: return [line.strip().lower() for line in file if len(line.strip()) == 5] # Positional splitting (minimax-ish) def build_split_matrix(possible_targets): total_targets = len(possible_targets) split_matrix = [{chr(i): 0 for i in range(97, 123)} for _ in range(5)] for pos in range(5): for char_code in range(97, 123): char = chr(char_code) green, yellow, gray = 0, 0, 0 for target in possible_targets: if target[pos] == char: green += 1 elif char in target: yellow += 1 else: gray += 1 worst_case_bucket = max(green, yellow, gray) split_matrix[pos][char] = total_targets - worst_case_bucket return split_matrix # Markov flow (bigrams) def build_markov_matrix(possible_targets): markov_matrix = [{chr(a): {chr(b): 0 for b in range(97, 123)} for a in range(97, 123)} for _ in range(4)] for word in possible_targets: for i in range(4): char1 = word[i] char2 = word[i+1] markov_matrix[i][char1][char2] += 1 return markov_matrix # Decision engine: Hybrid scoring def score_guess_hybrid(guess, split_matrix, markov_matrix, markov_weight): score = 0 seen_chars = set() for pos, char in enumerate(guess): score += split_matrix[pos][char] if char in seen_chars: score -= (split_matrix[pos][char] * 0.5) seen_chars.add(char) markov_bonus = 0 for i in range(4): char1 = guess[i] char2 = guess[i+1] markov_bonus += markov_matrix[i][char1][char2] return score + (markov_bonus * markov_weight) def get_best_hybrid_guess(valid_guesses, possible_targets, markov_weight): if len(possible_targets) == 1: return possible_targets[0] split_matrix = build_split_matrix(possible_targets) markov_matrix = build_markov_matrix(possible_targets) best_guess = "" max_score = -1 for guess in valid_guesses: score = score_guess_hybrid(guess, split_matrix, markov_matrix, markov_weight) if score > max_score or (score == max_score and guess in possible_targets): max_score = score best_guess = guess return best_guess # Game engine rules def get_feedback(guess, target): """Wordle colour engine.""" feedback = ['B'] * 5 t_chars = list(target) g_chars = list(guess) # Greens for i in range(5): if g_chars[i] == t_chars[i]: feedback[i] = 'G' t_chars[i] = g_chars[i] = None # Yellows for i in range(5): if g_chars[i] is not None and g_chars[i] in t_chars: feedback[i] = 'Y' t_chars[t_chars.index(g_chars[i])] = None return "".join(feedback) # Simulation loop (sequential and caching) def simulate_single_game(target_word, valid_guesses, all_targets, weight, cached_first_guess): possible_targets = all_targets.copy() attempts = 1 while attempts <= 6: # Use the pre-calculated guess on turn 1 to save massive compute time. if attempts == 1: guess = cached_first_guess else: guess = get_best_hybrid_guess(valid_guesses, possible_targets, weight) if guess == target_word: return attempts feedback = get_feedback(guess, target_word) possible_targets = [w for w in possible_targets if get_feedback(guess, w) == feedback] if not possible_targets: return 7 attempts += 1 return 7 # Sequential execution manager def run_simulation(valid_guesses, all_targets): weights_to_test = [0.0, 0.5, 1.0, 1.5, 2.0] # Testing a subset of words (e.g., 50) so the sequential loop doesn't take hours. # Change to all_targets if you want to run the full, slow dictionary test. test_batch = all_targets print("opening the tuning lab:") print(f"Testing {len(test_batch)} words across {len(weights_to_test)} configurations.") for weight in weights_to_test: start_time = time.time() print(f"Testing Markov Weight: {weight}") print("Pre-calculating optimal first guess...") # Calculate the opening guess once for this weight first_guess = get_best_hybrid_guess(valid_guesses, all_targets, weight) print(f"Optimal opening word for weight {weight} is: {first_guess.upper()}") print("Running sequential simulation...") results = [] for target in test_batch: score = simulate_single_game(target, valid_guesses, all_targets, weight, first_guess) results.append(score) # Tally the results. losses = sum(1 for score in results if score > 6) total_guesses = sum(score if score <= 6 else 7 for score in results) average_score = total_guesses / len(test_batch) elapsed_time = time.time() - start_time win_rate = ((len(test_batch) - losses) / len(test_batch)) * 100 print(f"Average Guesses: {average_score:.3f}") print(f"Win Rate: {win_rate:.1f}%") print(f"Time Taken: {elapsed_time:.1f} seconds\n") if __name__ == "__main__": targets = load_words('possible_targets.txt') guesses = load_words('valid_guesses.txt') if targets and guesses: guesses = list(set(guesses + targets)) run_simulation(guesses, targets) else: print("Missing dictionary files possible_targets.txt and valid_guesses.txt, did you forget to run this in the right folder again?") The second version is a modified Markov model - I wanted to try to incorporate statistical features of letter order in English words to adjust a probability estimate for greater accuracy to beat the final 10 words. It performs with significantly lower accuracy than the simple minimax lookahead. wordle_hybrid_markov.py Click here to copy it to test for yourself ```python import os import time # File loading def load_words(filename): if not os.path.exists(filename): return [] with open(filename, 'r', encoding='utf-8') as file: return [line.strip().lower() for line in file if len(line.strip()) == 5] # Positional splitting (minimax-ish) def build_split_matrix(possible_targets): total_targets = len(possible_targets) split_matrix = [{chr(i): 0 for i in range(97, 123)} for _ in range(5)] for pos in range(5): for char_code in range(97, 123): char = chr(char_code) green, yellow, gray = 0, 0, 0 for target in possible_targets: if target[pos] == char: green += 1 elif char in target: yellow += 1 else: gray += 1 worst_case_bucket = max(green, yellow, gray) split_matrix[pos][char] = total_targets - worst_case_bucket return split_matrix # Markov flow (bigrams) def build_markov_matrix(possible_targets): markov_matrix = [{chr(a): {chr(b): 0 for b in range(97, 123)} for a in range(97, 123)} for _ in range(4)] for word in possible_targets: for i in range(4): char1 = word[i] char2 = word[i+1] markov_matrix[i][char1][char2] += 1 return markov_matrix # Decision engine: Hybrid scoring def score_guess_hybrid(guess, split_matrix, markov_matrix, markov_weight): score = 0 seen_chars = set() for pos, char in enumerate(guess): score += split_matrix[pos][char] if char in seen_chars: score -= (split_matrix[pos][char] * 0.5) seen_chars.add(char) markov_bonus = 0 for i in range(4): char1 = guess[i] char2 = guess[i+1] markov_bonus += markov_matrix[i][char1][char2] return score + (markov_bonus * markov_weight) def get_best_hybrid_guess(valid_guesses, possible_targets, markov_weight): if len(possible_targets) == 1: return possible_targets[0] split_matrix = build_split_matrix(possible_targets) markov_matrix = build_markov_matrix(possible_targets) best_guess = "" max_score = -1 for guess in valid_guesses: score = score_guess_hybrid(guess, split_matrix, markov_matrix, markov_weight) if score > max_score or (score == max_score and guess in possible_targets): max_score = score best_guess = guess return best_guess # Game engine rules def get_feedback(guess, target): """Wordle colour engine.""" feedback = ['B'] * 5 t_chars = list(target) g_chars = list(guess) # Greens for i in range(5): if g_chars[i] == t_chars[i]: feedback[i] = 'G' t_chars[i] = g_chars[i] = None # Yellows for i in range(5): if g_chars[i] is not None and g_chars[i] in t_chars: feedback[i] = 'Y' t_chars[t_chars.index(g_chars[i])] = None return "".join(feedback) # Simulation loop (sequential and caching) def simulate_single_game(target_word, valid_guesses, all_targets, weight, cached_first_guess): possible_targets = all_targets.copy() attempts = 1 while attempts <= 6: # Use the pre-calculated guess on turn 1 to save massive compute time. if attempts == 1: guess = cached_first_guess else: guess = get_best_hybrid_guess(valid_guesses, possible_targets, weight) if guess == target_word: return attempts feedback = get_feedback(guess, target_word) possible_targets = [w for w in possible_targets if get_feedback(guess, w) == feedback] if not possible_targets: return 7 attempts += 1 return 7 # Sequential execution manager def run_simulation(valid_guesses, all_targets): weights_to_test = [0.0, 0.5, 1.0, 1.5, 2.0] # Testing a subset of words (e.g., 50) so the sequential loop doesn't take hours. # Change to all_targets if you want to run the full, slow dictionary test. test_batch = all_targets print("opening the tuning lab:") print(f"Testing {len(test_batch)} words across {len(weights_to_test)} configurations.") for weight in weights_to_test: start_time = time.time() print(f"Testing Markov Weight: {weight}") print("Pre-calculating optimal first guess...") # Calculate the opening guess once for this weight first_guess = get_best_hybrid_guess(valid_guesses, all_targets, weight) print(f"Optimal opening word for weight {weight} is: {first_guess.upper()}") print("Running sequential simulation...") results = [] for target in test_batch: score = simulate_single_game(target, valid_guesses, all_targets, weight, first_guess) results.append(score) # Tally the results. losses = sum(1 for score in results if score > 6) total_guesses = sum(score if score <= 6 else 7 for score in results) average_score = total_guesses / len(test_batch) elapsed_time = time.time() - start_time win_rate = ((len(test_batch) - losses) / len(test_batch)) * 100 print(f"Average Guesses: {average_score:.3f}") print(f"Win Rate: {win_rate:.1f}%") print(f"Time Taken: {elapsed_time:.1f} seconds\n") if __name__ == "__main__": targets = load_words('possible_targets.txt') guesses = load_words('valid_guesses.txt') if targets and guesses: guesses = list(set(guesses + targets)) run_simulation(guesses, targets) else: print("Missing dictionary files possible_targets.txt and valid_guesses.txt, did you forget to run this in the right folder again?") ``` I've since accepted boxer, foyer, and gaunt as my bêtes noires and am satisfied with a 99.57% success rate. Unfortunately a (mostly) reliable machine solution has now taken all the fun out the game and I haven't played wordle since. --- ## Aircon *URL: https://inigolapwood.com/aircon.html* **Aircon efficiency** — or some helpful tips on how to make cheap mobile units perform as promised in a hurry Last night onboard Theatreship we had an event with some brilliant musicians, including pedal-steel guitarist BJ Cole. BJ is a genius - his own work is deeply careful and experimental, and over a musical career that’s stretched back 60 years he’s played with everyone from Elton John to Brian Eno. In short he’s a living legend. …but last night it was 37 degrees in Canary Wharf, and Theatreship is fundamentally a metal tube. You’ll appreciate the urgent need for air-conditioning so that BJ retained both parts of that moniker. We were quoted over £40,000 for a same-day professional install which was well beyond our means. Our means in this instance being about £200, 24 hours, and whatever we could find in nearby shops. We've only really been able to deliver a project of Theatreship’s scale by thinking hard about ways to repurpose what we have (that and the enormous generosity of the people who put in their time. In this particular case, a huge thanks to Trev, who managed to cut 6 perfect 150mm holes while up on a baking aluminium cargo roof. He's a hero with a hole saw). A couple of years ago we’d bought three mobile air-conditioning units from Amazon - rated at 14,000 BTUs each. These had proven wildly ineffective and had been sitting in our stock cave ever since, but I wondered if they could be modified to resolve the issues. This was our analysis of why they were so ineffective, and the steps we took to resolve it. I’ve written this up because these are incredibly easy modifications to make that made an enormous difference to the efficiency of cheap units, so I hope it might be helpful to other people similarly underwhelmed by mobile aircon units, given the heatwave for the rest of this week. Problem 1: waste heat removal. An aircon unit works a bit like a fridge. It compresses a refrigerant in one place, runs this compressed hot refrigerant through coils so it condenses and forms a liquid, pumps it to another place, and then lets it expand again. As it expands it changes phase (back into a gas) absorbing a huge amount of ambient heat to do so. What this means is that like a fridge, it has a hot side and a cold side. Unlike a fridge though, where the cold side is in an insulated box (the fridge), both sides are in the same space, the room. The aircon relies on ducting to move the heat away. Unless this heat is moved out of the room, the overall effect is simply to warm the room by exactly the power consumed by the unit (in our case around 1.5kW). Much cleverer men than me have explained why this is elsewhere, so I won’t repeat their work. The factory supplied ducting is a plastic tube - as the waste heat moves through this tube it radiates heat back into the room. Spot readings on the tube showed nearly 60 degrees. The tube had a 6” diameter, so over the supplied 2m length of the tube that’s just under 1m^2 of surface area at 60 degrees. This is compounded by the fact that the inlet vents sucking air into the unit are right next to this outlet pipe, meaning room air is consistently sucked over this duct. The overall transfer coefficient here looks to be about 28W/m^2K, so at a delta-T of 35 degrees we’re getting almost 1kW of heat going back into the room per unit. Solution - I reduced the exhaust run to 1m, and insulated the exhaust. I used superfoil SF6, partly because that’s what I could get in Screwfix that morning, and partly because that provides a particularly good protection for radiative heat, and also isn’t itchy. With this fitted the lagged exterior read 27 degrees - reducing the delta-T to 3 degrees, and by halving the length we halved the heat transfer area, so the heat going back into the room reduced to around 40W. Problem 2 - negative air pressure. The air that’s being thrown out through the exhaust in problem 1 has to be drawn from somewhere. On these mobile units, the air is drawn from the room. 500m^3 is exhausted from the units - that means that you’re throwing away 500 cubic metres of air that you’ve just put a lot of effort into cooling straight out the exhaust every hour (that’s all the air in the room - twice. For each unit). That creates a partial vacuum (which nature, anthropomorphised, abhors) - air travels along that pressure differential, sucked in from outdoors - so we’re replacing our chilled air with 37 degree air. At 500m^3/hr, we’re bringing in 580kg of warm air each hour. At a 12 degree delta-T, that’s approx 2kWh of heat - the equivalent of having an additional 2kW space heater on per unit. Solution 2 - we ran a second duct from outside into the compressor cooling inlet vents. This means the vacuum drawn by the compressor cooling fans draws the air from outside rather than inside, keeping our cooled air in the room and reducing the vacuum (and therefore influx of warm air) to 0. I was lucky in that our units happened to have a big gap between the casing and the compressor coils, so it acted as its own plenum. We could tape up the vents and just cut through the plastic to fit the hose in, with that void space providing space for the air to expand back out so there weren’t local cold spots on the coils just where the duct entered. If your unit isn’t built like this you could also fairly easily make a box to tape around the inlet vents to provide this expansion void. Problem 3 - air circulation - cold air is denser than hot air (which is why we get the wind that Theatreship used to be powered by). Mobile aircon units sit on the floor and rely on fans to move the cold air around the room, but in a room the size of Theatreship, they’re not sufficient. Rather than simply put more power (and therefore more heat and noise) into the room in the form of large fans, we just moved the aircon units up to the ceiling to allow convection to do the job for us. This was also a happy side-effect of shortening the hoses. The other benefit of this was taking advantage of the temperature difference in the room. Due to heat rising, the cooling effect of the dock water on the hull, and the heating effect of the sun on the roof, with no other air circulation there’s about a 5 degree temperature difference between the top of the room by the ceiling and the bottom of the room by the floor. Moving them from the bottom to the top meant the units had to work less hard, because the hotter air increases the temperature difference between the air and the coils, meaning the heat transfers faster. It also meant they weren’t sitting in their own cold spots. The combination of the removal of cold spots that would trigger the thermostat cut off and the reduced pressure on the machine, which prevented them from overheating, meant the machines didn’t cycle so often (or even at all - they remained on continuously, where previously they were on at most 80% of the time). 14,000 btu is approximately 4.1kW. With 1kW lost to problem 1, 2kW lost to problem 2, and 20% lost to cycle time in problem 3, we were originally running at just around 20% of their rated capacity. They were only removing a net of under 1kW heat from the space per unit. These alterations took less than a day, and allowed us to quintuple the efficiency of the units. The three of them together went from providing 2.6kW of cooling to 12kW of cooling - and all for the price of some hose, some insulation, and some time. I’m delighted to say BJ survived the night. --- ## Rust *URL: https://inigolapwood.com/rust.html* **Rust Removal solution** - A non-toxic easy-to-make rust-eating solution for submerging engine parts. Involving chelation and reverse engineering Evaporust. Please don't sue me. Rust is the absolute bane of my life. I've lived on steel boats for 15 years now. They're immersed in (and sometimes under) water, which, in combination with dissolved and atmospheric oxygen, steadily eats away at the steel. Rust on boats is a bit like Japanese Knotweed in gardens: once it's there, it's almost impossible to eradicate. If you get even a little bit of it left at the bottom of a pit root and paint over it, the tiny amount of trapped moisture and oxygen keeps the reaction going. Hydrated iron(III) oxide, the bright orange horror that blossoms out of every crevice on the boats, takes up approximately 10x the volume of the base iron. This means it expands as it rusts, pushing off the covering paint layers and revealing more fresh steel underneath for the reaction to continue. This is what causes the ominous bubbles (followed by the weeping blisters) that you see on most vessels. I tried to come up with a cost-effective solution to remove this microscopic pit-root rust. You can get off-the-shelf rust removers. Evaporust is a particularly good example, but its recipe is a trade secret and it's quite expensive, so I tried to reverse engineer it. I have no idea if this resultant solution is at all chemically similar to Evaporust, but it does at least work at a fraction of the price. I share this hoping that as many people see it as possible (except for the lawyers at Evaporust HQ), and that eventually through shared endeavour humanity might one day triumph against my true nemesis. The 5% EDTA Solution The solution uses EDTA at 5% concentration. I titrate this with sodium hydroxide to a pH of between 8 and 9, and add a small amount of surfactant (Fairy Liquid!). I leave engine parts to soak in this and find them completely free of rust within about a week. The EDTA is a chelating agent—from the Greek Chele, meaning claw. They teach you about ionic and covalent bonds in school, but they usually skip the exact mechanism happening here: coordinate bonding. EDTA is what chemists call a hexadentate (six-toothed) ligand. (Which is not very many teeth for a person, but a lot of teeth for a ligand). It relies on its physical geometry to form a multi-point structural lock. The molecule wraps around the iron ion and bites it from six different angles simultaneously, caging it completely. This 'claw' preferentially binds to the iron, ripping it right out of the rust matrix and holding it harmlessly in suspension. I've put some before and after photos of the heads on a dover block ford. These, as you can probably see, had been underwater for some time. *[Image: Ford Dover Rocker rail rusted (Before)]* *[Image: Ford Dover Rocker rail unrusted after treatment with EDTA solution (After)]* --- ## Motorbike *URL: https://inigolapwood.com/motorbike.html* **A homemade 'motorbike'** — the perils of optimisation The v1 motorised bicycle (or motorbike for short) I made used a 2-stroke engine scavenged from a chainsaw. The clutch design on that used a second brake lever to actuate a cable, bringing two disks together as a primitive friction clutch. The advantage was that you didn't have to pull-start the engine: you pedal the bike up to speed, then pull the 'brake' lever and the engine splutters into life. The disadvantage is that I'm easily confused, and it's very easy in a moment of absent-mindedness to start the engine and lurch forwards when you intended to brake. That bicycle got stolen, and then returned to me by a local homeless friend, who said he'd been given it by the thief after they burned their leg on the exhaust. When it was stolen a second time, I moved onto the v2 version (pictured below). *[Image: 'Motorbike' View 1]* *[Image: 'Motorbike' View 2]* *[Image: 'Motorbike' View 3]* This v2 used a cheap GXH50 clone instead. It's a 4-stroke, with decent low-end torque, and I went for an off-the-shelf centrifugal clutch design over the bodged friction disk. This meant you had to pull-start the engine, but it prevented the complications of pulling the wrong lever. I re-welded the exhaust run this time to tuck it under the reduction gear to avoid leg-burning. I also added a bell. When this bike got stolen, it didn't get returned to me, so I can only assume the exhaust modification was successful. --- ## Broadside Hacks Folk Club Residency *URL: https://inigolapwood.com/* **The folk renaissance and TimeOut's #7** — One of the programming partnerships we've most loved onboard has been with Broadside Hacks. They've been programming a regular weekly night with us for over a year. We feel their attitude to folk music is a great fit for the ships - recovering the cultural traditions, but grounding them in a contemporary community, in great experimental music, and in a fundamentally good time. We were delighted to see their nights onboard recognised by TimeOut as #7 in their list of the best nights out in London. --- ## Wharf Life: Live Scores & Silent Film *URL: https://wharf-life.com/interviews/theatreships-man-with-a-movie-camera-night-shows-venues-value/* Another really lovely article - reviewing our BFI-supported season of classic silent films paired with new live contemporary scores. We loved this season - the night Jon came to was soviet classic 'A Man with a Movie Camera' paired with a new live score by fiddle player Owen Spafford, and pedal-steel guitarist Joe Harvey-Whyte. --- ## Wharf Life: How Theatreship and Artship carry cultural cargo *URL: https://wharf-life.com/interviews/isle-of-dogs-theatreship-artship-canary-wharf-inigo-lapwood-canal-and-river-trust/* A lovely article from Jon Massey about the project when we first opened. --- ## Wharf Life: Artship Set to Join Theatreship in Cultural Flotilla *URL: https://wharf-life.com/interviews/artship-set-to-join-theatreship-on-the-isle-of-dogs-in-cultural-flotilla/* A lovely interview exploring the ideas (and adventures) involved in expanding the Theatreship project with Artship. --- ## Londonist: Onboard Artship, As Canary Wharf's Floating Cultural Venue Expands *URL: https://londonist.com/london/museums-and-galleries/artship* Matt from Londonist came to visit us to see the ship. --- ## Talk of the Thames Podcast: Working on the River *URL: https://podcasts.apple.com/us/podcast/talk-of-the-thames/id1516787198* Panel discussion on working on the Thames and preserving heritage. --- ## Talk of the Thames Podcast: Songs, Treasures & Time *URL: https://podcasts.apple.com/us/podcast/talk-of-the-thames/id1516787198* Live panel recorded aboard Theatreship exploring cultural connections to the river. --- ## Theatreship: the North Sea Passage *URL: https://www.youtube.com/watch?v=lAlx2qa-S7c* A 7 minute mini-doc of our journey across the North Sea with Theatreship. We were lucky to have Joel Court from Floating Films onboard for the crossing, with his dedication, creative eye, and excellent at-sea drone-catching skills. He made this beautiful mini-doc about the journey and our plans for what happens next. --- ## High Point Podcast EP012: The Theatreship Project *URL: https://www.youtube.com/watch?v=TRWMu2AYf-g* A chat with Atelier Crescendo about some of the technical features of the project in the early days. --- ## Official Site: Artship London *URL: https://artship.co.uk/* Public Site & Programming. --- ## Official Site: Theatreship *URL: https://theatreship.co.uk/* Public Site & Box Office --- ## YourStaffKnow (now R-ai) Official Site *URL: https://r-ai.co.uk* Official site. Natural language processing for solving information bottlenecks in large organisations.