terça-feira, 17 de março de 2020

Cross-compiling Rust To Linux On Mac

In my last blog post I said I wanted to spend some time learning new things. The first of those is Rust. I had previously tried learning it, but got distracted before I got very far.

Since one of the things I'd use Rust for is web pages, I decided to learn how to compile to WebAssembly, how to interface with Javascript, and how to use WebSockets. At home, I use a Mac to work on my web projects, so for Rust I am compiling a native server and a wasm client. But I also wanted to try running this on redblobgames.com, which is a Linux server. How should I compile to Linux? My first thought was to use my Linux machine at home. I can install the Rust compiler there and compile the server on that machine. Alternatively, I could use a virtual machine running Linux. Both of these options seemed slightly annoying.

I've been curious how much work it would take to cross-compile, and I found this great post from Tim Ryan. My setup is simpler than his, so I didn't need everything he did. I started with these commands from his blog post:

rustup target add x86_64-unknown-linux-musl brew install FiloSottile/musl-cross/musl-cross mkdir -p .cargo cat >>.cargo/config <<EOF [target.x86_64-unknown-linux-musl] linker = "x86_64-linux-musl-gcc" EOF 

I then compiled for Linux:

TARGET_CC=x86_64-linux-musl-gcc cargo build --release --target=x86_64-unknown-linux-musl 

Unfortunately this failed with an error about OpenSSL. Tim's post has a solution to this. Before implementing that complicated solution I realized that I should't need SSL/TLS anyway. My server talks regular websockets, not secure websockets, and then I use nginx to proxy them into secure websockets. So I disabled the secure websockets with this in Cargo.toml, the file that has the Rust project configuration:

[target.'cfg(target_arch = "x86_64")'.dependencies] tungstenite = { version = "0.9", default-features = false, features = [] } 

At first I tried features = [] but that wasn't good enough. I needed to also use default-features = false to disable the TLS. With this, the binary built, and I was able to run it on Linux!

So now I have a Makefile that builds the wasm client, the Mac server for local testing, and the Linux server for production. Fun!

BUILD = build  RS_SRC = $(shell find src -type f -name '*.rs') Cargo.toml WASM = target/wasm32-unknown-unknown/debug/rust_chat_server.wasm  run-server: target/debug/chat_server  # local testing server  RUST_BACKTRACE=1 cargo run --bin chat_server  target/debug/chat_server: $(RS_SRC)  # production server  cargo build --bin chat_server  target/x86_64-unknown-linux-musl/release/chat_server: $(RS_SRC)  TARGET_CC=x86_64-linux-musl-gcc cargo build \      --release --target=x86_64-unknown-linux-musl  $(WASM): $(RS_SRC)  cargo build --lib --target wasm32-unknown-unknown  $(BUILD)/rust_chat_server_bg.wasm: $(WASM) index.html  wasm-bindgen --target no-modules $< --out-dir $(BUILD)  mkdir -p $(BUILD)  cp index.html $(BUILD)/ 

My Cargo.toml file is kind of terrible but it works so far for building the three outputs:

[package] name = "rust_chat_server" version = "0.1.0" authors = ["Amit Patel <redblobgames@gmail.com>"] edition = "2018"  [lib.'cfg(target_arch = "wasm32")'] crate-type = ["cdylib"]  [[bin]] name = "chat_server" path = "src/chat_server.rs"  [dependencies] wasm-bindgen = "0.2" serde = { version = "1.0", features = ["derive"] } bincode = "1.2"  [target.'cfg(target_arch = "x86_64")'.dependencies] tungstenite = { version = "0.9", default-features = false, features = [] } 

That's it for now. I'm not a big fan of writing client-server code in large part because I want my pages to still work in thirty years, and that's best if there's no server component. But I want to spend time this year learning things for myself rather than trying to produce useful tutorials, so I'm going to explore this.

Tim's blog post was a huge help. Without it, I would've compiled the server on Linux. Thanks Tim!

I've placed it on github.

domingo, 15 de março de 2020

The Nuisance Of Link Surfing


Photo by mikael altemark. Some rights reserved. Source: Flickr


This essay's going to be rather short, so I'll just cut to the chase. An awful habit is developing among the essays, articles, blogs, op-eds, or whatever you want to call them, on the Internet. Allow me to explain. Say you are reading an article about the history of the peanut butter and banana sandwich, when the author of said piece, claims that the popular dish was invented by Elvis Presley. Instead of detailing where they found this fact, or giving us the passage, they hyperlink the statement to their source. Confronted with this, you can either click on the hyperlink to verify the truth of their claim, or you can trust them to be honest and not bother. Of course, no one on the Internet can be trusted these days, so you click, but when to click? Do you click immediately and interrupt the flow of the article or wait until you finish the article, while it dangles among the sentences, tantalizing you. Eventually, when you do click, 9 times out of 10, you reach another Internet source, and 9 times out of 10, they'll have numerous claims where the hyperlinks abound. This eventually becomes a rather time consuming and irritating game, chasing source after source in search of the original. It may take hours, and by the time you finish, you may have forgotten what the original article was about.



I like to call the cat-and-mouse that many news junkies are familiar with, "link-surfing." Unfortunately, I can't say that I coined the term, because Urban Dictionary thought of it first, "link surfing: Traversing the Web by clicking on links within web pages. This technique is often used on encyclopedia sites like Wikipedia" (Gunderson). If only this habit could be regulated to websites like Wikipedia, but alas, it has infected the annals of our best magazines and newspapers. Since people prefer to get their information for free (I sure do) I imagine that much of the press is gearing themselves towards an Internet audience. As such, they no longer bother with quotes or paraphrases. You're expected to either follow the link or take them at face value. This, with all due respect, comes off as lazy. This type of format certainly isn't admissible for college papers or non-fiction Pulitzer Prize winners. I've always believed that a text should be self-contained. All the relevant information necessary to understanding the point of your piece should all be within the text itself. Your sources should be appendices to your arguments. Following all those links to verify the correctness of your claims is simply too much work for the average reader to do in one sitting. Some of us have lives outside of the Internet. We can't be bothered to go link-surfing all day. I don't know if these Internet writers actually expect us to click on all of their links. In a way, their professionalism is appears as not much different from the rumor mill.

By the way, I often find that some of these links are ultimately useless. They may lead to magazine that requires a subscription, or an academic study that requires a subscription, or an Error 404, or a Wikipedia article. Some of you may wonder why I listed the Wikipedia article as useless. Well, to its credit, the free encyclopedia has plenty of information and sources listed. In my opinion, the website's entries are only useful insofar as you are able to check their sources. Some require that you go to the library, while others are dead links or uncheckable. Checking the links, again, takes up too much time, and knowing that Wikipedia can be edited by anyone, its claims deserve the highest scrutiny. Will we really be able to verify each and every claim? In other words, not a very reliable source.

This may sound a bit old-fashioned, but I call for an appeal to the past. I realize that not every single claim made needs to be referenced or footnoted, but the big ones do. For those big ones, don't cheapen yourself by only linking the words to another site. Quote or paraphrase it, like you were taught. If you can't find it on the Internet, then fine, source a book if you must. I understand that Internet connection has made information a lot easier to find, and that's a good thing. However, save the rest of us some time, and muscle up your arguments inside of the text, instead of relying solely on links, that frankly, most of us won't even bother to click on. Also, don't source Wikipedia. That makes you look like a slacker.


Bibliography

Gunderson, Bob. "link surfing." Urban Dictionary. May 1, 2007. Web. http://www.urbandictionary.com/define.php?term=link%20surfing








quinta-feira, 5 de março de 2020

Tech Book Face Off: Effective Python Vs. Data Science From Scratch

I must confess, I've used Python for quite some time without really learning most of the language. It's my go-to language for modeling embedded systems problems and doing data analysis, but I've picked up the language mostly through googling what I need and reading the abbreviated introductions of Python data science books. It was time to remedy that situation with the first book in this face-off: Effective Python: 59 Specific Ways to Write Better Python by Brett Slatkin. I didn't want a straight learn-a-programming-language book for this exercise because I already knew the basics and just wanted more depth. For the second book, I wanted to explore how machine learning libraries are actually implemented, so I picked up Data Science from Scratch: First Principles with Python by Joel Grus. These books don't seem directly related other than that they both use Python, but they are both books that look into how to use Python to write programs in an idiomatic way. Effective Python focuses more on the idiomatic part, and Data Science from Scratch focuses more on the writing programs part.

Effective Python front coverVS.Data Science from Scratch front cover

Effective Python

I thought I had learned a decent amount of Python already, but this book shows that Python is much more than list comprehensions and remembering self everywhere inside classes. My prior knowledge on the subjects in the first couple chapters was fifty-fifty at best, and it went down from there. Slatkin packed this book with useful information and advice on how to use Python to its fullest potential, and it is worthwhile for anyone with only basic knowledge of the language to read through it.

The book is split into eight chapters with the title's 59 Python tips grouped into logical topics. The first chapter covers the basic syntax and library functions that anyone who has used the language for more than a few weeks will know, but the advice on how to best use these building blocks is where the book is most helpful. Things like avoiding using start, end, and stride all at once in slices or using enumerate instead of range are good recommendations that will make your Python code much cleaner and more understandable.

Sometimes the advice gets a bit far-fetched, though. For example when recommending to spell out the process of setting default function arguments, Slatkin proposed this method:

def get_first_int(values, key, default=0):
    found = values.get(key, [''])
    if found[0]:
        found = int(found[0])
    else:
        found = default
    return found
Over this possibility using the or operator short-circuit behavior:
def get_first_int(values, key, default=0):
    found = values.get(key, [''])[0]
    return int(found or default)
He claimed that the first was more understandable, but I just found it more verbose. I actually prefer the second version. This example was the exception, though. I agreed and was impressed with nearly all of the rest of his advice.

The second chapter covered all things functions, including how to write generators and enforce keyword-only arguments. The next chapter, logically, moved into classes and inheritance, followed by metaclasses and attributes in the fourth chapter. What I liked about the items in these chapters was that Slatkin assumes the reader already knows the basic syntax so he spends his time describing how to use the more advanced features of Python most effectively. His advice is clear and direct so it's easy to follow and put to use.

Next up is chapter 5 on concurrency and parallelism. This chapter was great for understanding when to use threads, processes, and the other concurrency features of Python. It turns out that threads and processes have unique behavior (beyond processes just being heavier weight threads) because of the global interpreter lock (GIL):
The GIL has an important negative side effect. With programs written in languages like C++ or Java, having multiple threads of execution means your program could utilize multiple CPU cores at the same time. Although Python supports multiple threads of execution, the GIL causes only one of them to make forward progress at a time. This means that when you reach for threads to do parallel computation and speed up your Python programs, you will be sorely disappointed.
If you want to get true parallelism out of Python, you have to use processes or futures. Good to know. Even though this chapter was fairly short, it was full of useful advice like this, and it was possibly the most interesting part of the book.

The next chapter covered built-in modules, and specifically how to use some of the more complex parts of the standard library, like how to define decorators with functools.wraps, how to make some sense of datetime and time zones, and how to get precision right with decimal. Maybe these aren't the most interesting of topics, but they're necessary to get right.

Chapter 7 covers how to structure and document Python modules properly when you're collaborating with the rest of the community. These things probably aren't useful to everyone, but for those programmers working on open source libraries it's helpful to adhere to common conventions. The last chapter wraps up with advice for developing, debugging, and testing production level code. Since Python is a dynamic language with no static type checking, it's imperative to test any code you write. Slatkin relates a story about how one programmer he knew swore off ever using Python again because of a SyntaxError exception that was raised in a running production program, and he had this to say about it:
But I have to wonder, why wasn't the code tested before the program was deployed to production? Type safety isn't everything. You should always test your code, regardless of what language it's written in. However, I'll admit that the big difference between Python and many other languages is that the only way to have any confidence in a Python program is by writing tests. There is no veil of static type checking to make you feel safe.
I would have to agree. Every program needs to be tested because syntax errors should definitely be caught before releasing to production, and type errors are a small subset of all runtime errors that can occur in a program. If I was depending on the compiler to catch all of the bugs in my programs, I would have a heckuva lot more bugs causing problems in production. Not having a compiler to catch certain classes of errors shouldn't be a reason to give up the big productivity benefits of working in a dynamic language like Python.

I thoroughly enjoyed learning how to write better Python programs through the collection of pro tips in this book. Each tip was focused, relevant, and clear, and they all add up to a great advanced level book on Python. Even better, the next time I need to remember how to do concurrency or parallelism or how to write a proper function with keyword arguments, I'll know exactly where to look. If you want to learn how to write Python code the Pythonic way, I'd highly recommend reading through this book.

Data Science from Scratch

I didn't expect to enjoy this book quite as much as I did. I went into it expecting to learn about how to implement the fundamental tools of the trade for data science, and that was indeed what I got out of the book. But I also got a lighthearted, entertaining, and surprisingly easy-to-read tour of the basics of machine learning using Python. Joel Grus has a matter-of-fact writing style and a dry wit that I immediately took to and thoroughly enjoyed. These qualities made a potentially complex and confusing topic much easier to understand, and humorous to boot, like having an excellent tour guide in a museum that can explain medieval culture in detail while cracking jokes about how toilet paper wasn't invented until the 1850s.

Of course, like so many programming books, this book starts off with a primer on the Python language. I skipped this chapter and the next on drawing graphs, since I've had just about enough of language primers by now, especially for languages that I kind of already know. The real "from scratch" parts of the book start with chapter 4 on linear algebra, where Grus establishes the basic functions necessary for doing computations on vectors and matrices. The functions and classes shown throughout the book are well worth typing out in your own Python notebook or project folder and running through an interpreter, since they are constantly being used to build up tooling in later chapters from the more fundamental tools developed in earlier chapters. The progression of development from this chapter on linear algebra all the way to the end was excellent, and it flowed smoothly and logically over the course of the book.

The next few chapters were on statistics, probability, and their use with hypothesis testing and inference. Sometimes Grus glossed over important points here, like when explaining standard deviations he failed to mention that this metric only applies to (or at least applies best to) normal distributions. Distributions that deviate too much from the normal curve will not have meaningful standard deviations. I'm willing to cut him some slack, though, because he is covering things quickly and makes it clear that his goal is to show roughly what all of this stuff looks like in simple Python code, not to make everything rigorous and perfect. For instance, here's his gentle reminder on method in the probability chapter:
One could, were one so inclined, get really deep into the philosophy of what probability theory means. (This is best done over beers.) We won't be doing that.
He finishes up the introductory groundwork with a chapter on gradient descent, which is used extensively in the later machine learning algorithms. Then there are a couple chapters on gathering, cleaning, and munging data. He has some opinions about some API authors choice of data format:
Sometimes an API provider hates you and only provides responses in XML.
And he has some good expectation setting for the beginner data scientist:
After you've identified the questions you're trying to answer and have gotten your hands on some data, you might be tempted to dive in and immediately start building models and getting answers. But you should resist this urge. Your first step should be to explore your data.
Data is never exactly in the form that you need to do what you want to do with it, so while the gathering and the munging is tedious, it's a necessary skill that separates the great data scientist from the merely mediocre. Once we're done learning how to whip our data into shape, it's off to the races, which is great because we're now halfway through this book.

The chapters on machine learning models, starting with chapter 12, are excellent. While Grus does not go into intricate detail on how to make the fastest, most efficient MLMs (machine learning models, not multi-level marketing), that is not the point. His objective is to show as clearly as possible what each of these algorithms looks like and that it is possible to understand how they work when shown in their essence. The models include k-nearest neighbors, naive bayes, linear regression, multiple regression, logistic regression, decision trees, neural networks, and clustering. Each of these models is actually conceptually simple, and the models can be described in dozens of lines of code or less. These implementations may be doggedly slow for large data sets, but they're great for understanding the underlying ideas of each algorithm.

Threaded through each of these chapters are examples of how to use each of the statistical and machine learning tools that is being developed. These examples are presented within the context of the tasks given to a new data scientist who is an employee of a budding social media startup for…well…data scientists. I just have to say that it is truly amazing how many VPs a young startup can support, and I feel awfully sorry for this stalwart data scientist fulfilling all of their requests. This silliness definitely keeps the book moving along.

The next few chapters delve a bit deeper into some interesting problems in data science: natural language processing, network analysis (or graph algorithms), and recommender systems. These chapters were just as great as the others, and by now we've built up our data science tooling pretty well from the original basics of linear algebra and statistics. The one thing we haven't really talked about, yet, is databases. That's the topic of the 23rd chapter, where we implement some of the basic operations of SQL in Python in the most naive way possible. Once again it's surprising to see how little code is needed to implement things like SELECT or INNER JOIN as long as we don't give a flying hoot about performance.

Grus wraps things up with an explanation of the great and all-powerfull MapReduce, and shows the basics of how it would be implemented with mapper and reducer functions and the plumbing to string it together. He does not get into how to distribute this implementation to a compute cluster, but that's the topic of other more complicated books. This one's done from scratch so like everything else, it's just the basics. That was all fine with me because the basics are really important, and knowing the basics well can lead you to a much deeper understanding of the more complex concepts much faster than if you were to try to dive into the deep end without knowing the basic strokes. This book provides that foundation, and it does it with flair. I highly recommend giving it a read.


Both Effective Python and Data Science from Scratch were excellent books, and together they could give a programmer a solid foundation in Python and data science as long as they already have some experience in the language. With that being said, Data Science from Scratch will not provide the knowledge on how to use the powerful data analysis and machine learning libraries like numpy, pandas, scikit-learn, and tensorflow. For that, you'll have to look elsewhere, but the advanced, idiomatic Python and fundamental data science principles are well covered between these two books.

UPDATE - March 2020


It's been an entire year since I've last written one of these update posts, and man is there a lot to tell you about. From a plethora of small improvements behind the scenes to the introduction to our very own YouTube channel (not to mention the many games I've updated and improved), there's a lot to cover. Without further ado, let's jump in...

Read more »

A Fear Of Flying They Call It


Image in Public Domain.



Being the easily impressionable student that I am, I decided to take on the collegiate tradition of studying abroad. It's a common cliche to hear alumni gush about how studying abroad changed their life, and will change yours, too. The salesmen sure know how to pitch, but I can't say I was completely sold.



I study Spanish, by the way. No, it didn't come out of a great passion for the language, or anything noble like that. In my freshman year of high school I had to select two electives. I chose Spanish and Wood Shop, since they seemed to be the easiest grades. Sure enough, they were. I intended to stay for only two years in Spanish, but stayed longer for the fiestas. Yes, I'm sleazy.

A few scholarships later, I found myself at the airport, ready to go. Well, not so ready. My proficiency in Spanish was crap. I'd only taken a cursory glance at the map, so I getting lost was inevitable. My destination was Santander, Spain. A city I'd never heard of before.

The luxurious plane trip did well to calm my nerves. I have always been pensive about flying, having heard the stories of cramped seats, crowded bathrooms, and crappy airplane food. I didn't worry too much about airsickness (since I'm not prone to vomiting), but I grasped my sick bag should Pazuzu suddenly feel the urge to possess me. I expected lifting off to be like riding on a roller coaster (did I forget mention I don't like those?) yet flying through the air hardly felt any different that riding in a car. Better even. My fears about airplanes were assuaged halfway between the in-flight movie and risotto. This was the Blackjack of Setzer Gabbani. Yet, alas, no flight lasts forever.

In the book of Exodus, Moses names his first son with Zipporah, "Gershon", while in exile from Egyptian royalty. In Hebrew, "Gershon" means "stranger in a strange land." In Spain, I thought my name was "Gershon", but in Spain, my name was "mud."

My problems started as soon as I landed in the Madrid airport. The place was a labyrinth and with no David Bowie to guide me, either. After studiously running around in circles for about two and a half hours, I finally found my plane...just about to take off! The flight crew had to stop the departure for me to get on. I scrambled into my seat, sweaty, delirious, and paranoid.

I took a taxi to my host mother's apartment, knowing my habit for getting lost. The Spanish was mostly basic, "Hola", "¿Que tal?", "Estoy bien", etc. I think those cheap formalities would've sufficed, but I overreached my hand and chewed off more than I could swallow. She gave me a slightly confused look. To this day, I wonder what it was that I said. A cat named Rita also lived there. Cats speak the same language in Spain.

I soon had to meet up with my classmates at "Ayuntamiento" which is Spanish for "town hall." I stepped into the streets nervously, my hands jammed into my pockets for fear of thieves. I tried desperately not to look a tourist, but that veneer faded as soon as I brought out my map of the city. I was lost for two hours. A fat lot of good the map did. At the end of my struggle, I gave in and searched out a taxi, but the cab driver nearly laughed me out the vehicle. It turns out that Ayuntamiento was only a few minutes away.

The next day was hardly any better. Classes began at 8:30, so I woke up at 6:00, knowing that there would be a long walk ahead of me. The school was somewhere on the other side of the city, and I had no idea what it looked like. I figured at the time that a university would be easy to spot. Well, you know what they say about assumptions.

The trek was tiring, to say the least. It often had me going uphill through the various neighborhoods and alleyways. I recalled watching The Flash on the plane. How I would've loved to have had Barry Allen's super-speed at the time. Though if I did, I might've missed out on many of the aesthetics. The shops and dwellings of Santander were melded to fit into the rising landscape. Laundry hung on clotheslines outside of the windows, while pigeons scurried on the grounds, pecking for bread crumbs. By the orange hues of sunrise, it all looked at times as if I had wandered into a painting. Though I doubt if a late student would get extra credit for cultural appreciation.

La Universidad de Cantabria was far smaller than I had anticipated, though I suppose that was for the best. If it had been any larger, I'd probably get lost there, too. The university, small though it was, would become something of a second home for me. The think with relish on the countless hours I would spend outside of the cafeteria, listening to quirky stories NPR, memorizing Spanish vocabulary, or eating what was left of my pig liver sandwich.

Perhaps it was the Sea of Cantabria that kept me (relatively) sane throughout all of that initial madness. My host mother had an apartment near the sea, so it sort of functioned as my North Star. I need only know where the sea is, and I'd (eventually) find my way home. It was a great, wide blue that glittered in the sunlight, its waves licking the shore.

I suppose there's something poetic in the sea, though I can't tell you exactly what it is.




quarta-feira, 4 de março de 2020

BYZANTINE: THE BETRAYAL


Istanbul, a city steeped in history and intrigue; that is the setting for Stormfront Studio's 1997 game Byzantine: The Betrayal. With the Discovery Channel heavily involved in its production, the exceptional conspiracy thriller at the heart of the story also doubles as an impressive travelogue of the Turkish capital that'll make anyone want to visit.

Read more »

Storium Theory: Don't Count Yourself Out

Today, I'd like to spend a bit of time on something that I've seen here and there on Storium - cases where a person perhaps goes a bit too Weak with a Weakness play...and takes themselves out of a scene.

It feels right - it feels better than right, doesn't it? Isn't it a great expression of a Weakness to not just suffer a setback, not just suffer some kind of injury, but actually get knocked out or otherwise removed from play for a bit?

Well...it is, and it isn't.

Let's start off with the good: This is, undoubtedly, an example of a player being very willing to show his character suffering for his Weakness. That's great, and that's an attitude that I hope people learn on Storium.

But actually taking yourself out...getting knocked out, getting poisoned to the extent that you can't move, draining yourself so much of energy that you're too exhausted to go on...there are times these things are appropriate, but they need to be used with restraint.

Otherwise, they can mess with the flow of a story.

I've seen it happen: A battle happens, one part of a longer sequence of events all taking place in a short period of time. One player plays a Weakness card and portrays themselves as exhausted, or poisoned, or just plain knocked out cold. Then the battle ends.

Now a few things can happen:
  • Somehow, the player character recovers very quickly, and keeps on going, so the story can keep moving. That's unsatisfying, as it feels like a major moment is just brushed over and somewhat ignored.
  • The narrator puts in a point where the group can rest for a while to make it seem more realistic that the player character takes time to recover from his state...but now the story loses momentum.
  • The story goes on, and the player character is put someplace safe to recover in the meantime, which means:
    • The player misses out on part of the story, or...
    • The narrator has to come up with things that happen around wherever the player character was left that they can work with in their current state.
I'm not particularly fond of any of these options. If you address a Weakness play that severe, you're going to find your story either feeling like it doesn't address the weakness properly, losing its momentum, or going on temporarily without a player (which can be harmful both to that player's experience with the story, and to the game as a whole, given how Storium tends to work).

 Now...you can make this work. You can. A good narrator who is used to letting the players have heavy story control, especially, can make this work out great. Maybe the group has to stop to allow the player character to recover, but in the process, the problem they're trying to address worsens. Or maybe that's the next challenge: Can they find things to help the player quickly enough that the next challenges don't worsen in the meantime?

But the thing is, even though you can make this work...it just oftentimes ends up more trouble than it is worth to put in this strong of a Weakness play. You can get some great, great character moments and a strong feeling of struggle and danger and fading hope from injury or tiredness that still leaves the character able to go on to new fights.

Sometimes, that can be even stronger. If a character is knocked out, the group might stop to deal with that and then go on once he's recovered. If that happens, the knockout doesn't add as much tension to the story. If a character is injured or tired but still goes on, though, we see that in everything they do for the next few scenes - they're in a bad way, one arm too hurt to hold up their shield, or dragging themselves forward in the battle. It's powerful.

Stopping the story's progress can add some tension if managed properly, but continuing despite your wounds always adds tension.

I'm not saying that you should absolutely avoid major consequences as part of Weakness plays, mind! There are times when it is appropriate, entirely appropriate. Particularly late in the story, particularly when the narrator has told you this is the final battle, you might pull out these powerful Weakness plays. Because there - there - it won't make the story pause or lose momentum. It'll emphasize just how bad things are currently, just how much of a struggle it is for the characters...but there's nothing ahead that the players need to address.

Keep yourselves within the bounds of the challenge, but that is when you should be safer using some of these powerful Weakness concepts.

Before that? Before that, earlier in the story, don't go that far! Go lighter with what goes wrong...but use what goes wrong more. Pick up some kind of injury and carry it with you for the next few scenes, showing your character struggling with it. Show the tiredness that you're feeling, even as you struggle to move on.

Or almost encounter one of those points...and leave things open. Let someone else save you from them. Those can be amazing moments in a tale too.

Weakness plays don't have to be about solid, absolute consequences for your character. They can be about the threat of those consequences. They can be about what might happen if the battle isn't turned around. If you knock your character out, he's knocked out. That's certain. It's done. But if you put in the possibility, you're giving someone else the chance to write. And that's what Storium is all about - working with each other to write interesting or fun stories.

So I suggest pulling back from the solid a bit - work with possibilities. Get hurt and need help. Or, need help to avoid getting hurt. Get tired and struggle. Need someone's shoulder to lean on. Need someone to fight by your side. But unless the story really, truly demands it, don't get taken out entirely. That cuts off possibilities - like bluntly leaving in the middle of a conversation.

And if you're going to take yourself out at all, try not to do it while you still have card plays left for the scene! Hopefully narrators are taking my advice to not use up all their points, but still, it's useful if players keep open the option to play all their cards if needed.

Now, one more note here: I've already said that late in the story you might be fine pulling out heavier, solid consequences as part of a Weakness play if that's your thing. There are points earlier in the story where you might do that too. The narrator might even set them up specifically. If he does...go with it, obviously! If the Weak outcome is "you're all knocked out," well, that's what the outcome is, so do the normal thing with a Weak outcome if you get it and write your spin on that outcome! You get knocked out.

The narrator, obviously, will have planned for that possibility since he put it in the outcomes, right?

Similarly, you might find a situation where it feels like the consequences should be higher - where the outcomes suggest this battle or event is more dangerous than normal, or where you're already portraying your character in a bad way and it just feels like they should have something more solid happen to them on your next Weakness play. And that's fine...I just suggest that you consider talking to the narrator first. Remember, you're writing collaboratively: Check, when you're thinking about having something this major happen to your character, and work with the narrator to figure out how it can happen but still keep the story moving (and not exclude you as a player).

This isn't a solid "don't ever do this." I want to be clear on that. What it is is a caution: Be careful of how and when you do this. When a hero is solidly defeated in battle or exhausts herself in the service of a cause, that can be a great story moment. It's just important to make sure that it's the right time - and that it doesn't derail the story's momentum or leave it short of players in the process.