Ban Assault Knives!

How many tragedies like the horrible events at Ohio State must we allow to occur before we take action? Have we forgotten the bloodshed in Akihabara? How many more innocent, defenseless people must be injured and killed by knives? Knife violence is one of the biggest problems facing the US today, and the proper course of action is clear: we need an Assault Knives Ban.

Now, I know lots of cooking enthusiasts are going to scream bloody murder about how I'm trying to destroy their way of life, but let's face the facts here. Nobody needs knives like these:

Assault Knives

These are assault knives. They are designed to slash and stab. The one on the top has two assault features: a blade longer than 5 inches and a blade that is wider than the handle. The one on the bottom has three assault features: a blade longer than 5 inches and wider than the handle, plus a pointed tip. You do not need either of these knives to cook or eat.

A ban on assault knives would prohibit any knife with a blade longer than 5 inches and any blade that has a pointed tip or is wider than the handle. Rounded tips prevent stabbing, and short blades prevent limit the knives' effectiveness as weapons. A blade no wider than the handle forces a murderer to risk injury to himself (from his hand sliding down over the blade) when attempting to stab someone. Ideally, an Assault Knives Ban would make all schools and Universities knife-free zones, too. Food would be prepared by licensed and trained professional chefs in a high-security environment. Chefs who could demonstrate a clear need would still be able to use assault knives while at work, but these knives would be stored in secure lockers when not in use, and removing one from the food preparation facility would be a felony. The knife security measures in place at such facilities would be regularly audited by local law enforcement.

But don't worry, cooking enthusiasts! Nobody is going to confiscate your existing assault knives. You will have to register them, and you will not be able to transfer them (no, not even as inheritance), but you will be able to keep them for the rest of your life.

The following knives are not assault knives:

"Featureless" Knives

The knife at the top is not an assault knife because it has a rounded tip and a 5 inch blade. Although the middle knife has a pointed tip, it is not an assault knife because its blade is less than 5 inches long. The bottom knife is not an assault knife because it does not have a sharpened blade but instead has an "abrasive ridge".

Cooking enthusiasts can still do everything they want to with these knives. They can cut a pepper or a tomato. They can spread butter. They can carve a turkey.

What they can't do is attack our children at school.

I think we can all agree that now is the time for common-sense knife laws. Let's put an end to the epidemic of knife violence. Tell your Congressional representatives to support an Assault Knives Ban!

Lucid Dreaming

I generally haven't looked forward to dreaming. My dreams tend to be weird, disturbing, frightening, or just plain annoying. However, hoping not to dream is an exercise in futility. So, a few months ago, I decided to tackle the problem in a different way. I started making a habit of attempting to determine (while awake) if I was dreaming. The idea is that if I get in the habit of doing this while I am awake, I will also do it while dreaming. Hopefully, this would then allow me to take control of the dream, or at least wake myself up. I had a few minor successes (e.g. rewinding and altering a dream that had gone in a direction I didn't like), but nothing really substantial. Until now.

Last night, I had a very common type of dream: staying in a house somewhere with relatives. It was more interesting than usual; the house was built into the side of a rocky hill near the sea, with trails around it. Aside from the location, though, the dream was fairly boring, with a bit of weird mixed in. Eventually, though, I found myself in a room I had been in before, but it had changed! The room was some kind of bathroom, with a curious circular shower in the center. The second time around, the shower was still there, but there was a stove next to it! In addition, there was a portable tabletop range sitting on top of the stove. That doesn't make any sense at all! At that point, I realized I was dreaming.

So, I loudly announced that this was a dream. A nearby relative challenged my assertion by picking up a knife and suggesting that if it's really a dream, she could stab me and nothing bad would happen. I said something to the effect of "Sure, go right ahead!" So, she stabbed me in the head. Nothing of note happened. She then put down the knife and wandered off, presumably to wrestle with the horror of her existence as a figment of my imagination. I then picked up the knife and tried to push it through my hand. It didn't go through or even hurt. Yep, definitely a dream!

At this point, I decided that I wanted a more exciting dream. So, I went down to the beach, lept into the air, and flew away. I was quite happy (and a bit surprised) to see that this worked, given that I had failed to transform objects in a previous dream. My flight was a bit floaty at first (I careened into some tree branches at one point), but I eventually tightened it up and flew around for a while. It was fun.

Apparently this world I had dreamed up had some kind of evil authoritarian government, and they didn't like me flying around. So, they sent the police to stop me. Just like a fantasy/action TV show! Seriously, why do they always send the police or the military to stop the demi-god who's ruining their day? Do they really think that's going to work? Well, they sure didn't stop me. I blasted them with Dragonball Z style energy blasts from my hands. I then decided that I was going to go full-on Frieza on these idiots. In retrospect, my choice of "I'm going to deal with this like an evil alien tyrant/real estate speculator" in the face of an evil authoritarian government was pretty ironic.

I eventually found myself in a house somewhere with a relative I had encountered in the first part (who doesn't correspond to anyone in the real world) sitting in a chair. She bragged that she was going to stop me. I figured she had been collaborating with the government and had gotten some kind of super soldier treatment. I guess that other relative didn't tell her that she was in a dream and that I was the dreamer. Oh, well. So, I challenged her assertion. She reiterated, pointing her finger directly at me, saying, "I'm going to stop YOU!" So, I pointed my finger right back at her. And then fired a Frieza-style finger beam right through her chest. Yep, the whole "send the military to deal with the demi-god" thing is working out as well as always!

I then flew away, past a large pool of glowing yellow liquid, which I decided was radioactive waste. I guess in addition to being evil authoritarian bastards, they're also irresponsible. As I continued flying, I spotted a group of detectives on the ground with guns drawn. And by "detectives" I mean they looked like they came straight out of a 30's or 40's crime drama. Well, aside from the fact that they weren't human. They had the coloration and configuration of the Minions from Despicable Me and the body shape of Patrick from Spongebob Squarepants. They were clearly looking for me, but they weren't doing a very good job of it, since they were looking around on the ground, and I was up in the air. So, I grabbed one of them telekinetically, tossed him into the air, and detonated him, just like Frieza did to Krillin. "Behold my terrible power, you pathetic...whatever the hell you are!" I'm starting to think that maybe I was the bad guy in this dream, but then again, I had been content to just fly around until they sent the cops after me.

Shortly after dispatching the bizarro detective, I found myself pulling a blanket over my head, because it was cold in my bedroom and I was awake. It was about 15 minutes before my alarm goes off, so I lay there for a while in a state between wakefulness and dreaming, then got up after my alarm went off.

This experience was fascinating and shed some light on the nature of dreams. The appearance of the police without my explicit desire indicates that my conscious mind was not in full control of the direction the dream took. Yet, I could choose to enable myself to fly. Just how much can I control? Is it a matter of concentration? Maybe the dream started reasserting itself once I stopped focusing on controlling it. Maybe I could have redirected it again after the police showed up, if I had tried to. I'll have to experiment more with this in the future.

I hope to take control of a dream again but hopefully take it in a less violent direction. I'd like to go to space next time, although I'm not sure how to get there, since I have never had a dream that started in space. Can I just forcibly dream up a spaceship and fly away? What are the limits? How far can I bend the rules of "reality"? Will my knowledge of the vast distances and (relatively) low speeds involved in space travel be an impediment when trying to get into space in my dream? How far can I depart from my everyday experiences? Flying around DBZ-style is pretty far from the everyday, but I was still fairly close to the ground, not flying out into space. Can I actually conjure up items? Buildings? So far, I haven't been able to conjure up anything, but I have been able to find new things by moving around. Maybe I just have to decide that I can find the things I want somewhere and then go there instead of trying to conjure them. At the very least, I now have some hope that I can disrupt weird/disturbing/annoying dreams. I'm not so confident that I can disrupt a nightmare, but I feel like I now have a fighting chance.

The Dark Side of AWS

I use AWS to host this blog. I also use it at work. Using AWS for real work has exposed some rather annoying aspects of the service, with one standing head and shoulders above the rest: service limits.

Service limits certainly play an important role, both in helping Amazon plan capacity increases and in helping limit the damage of accidental or malicious provisioning. However, Amazon does not manage limits well at all. How so? Let me explain:

For some resources in some services, you can query your current limits via the API. For some other resources in some other services, you can query your current limits via Trusted Advisor, which costs $100/month and presents the limits in a somewhat awkward manner. In either case, it is not overly onerous to set up some kind of automated monitoring (e.g. Nagios) to alert you before you hit the limit.

For all other resources, the only way to query your current limits is to slam into them full force (i.e. you try to create something and fail because you've reached the limit). At this point, you contact Amazon Support and request a limit increase. To their credit, this generally goes pretty quickly, but when you need a new whatever right now, it's not quick enough.

(Aside: yes, you can put together a list of all default service limits (by hand, since there's no single comprehensive list) and then diligently maintain the list (again, by hand) whenever you get a limit increase. That is not a solution.)

Even this, on its own, would not be enough to piss me off enough to write publicly about it. No, the thing that really PISSES ME RIGHT THE FUCK OFF is Amazon's attitude about it. Simply put, they don't care. At all.

I have on more than one occasion asked Amazon for guidance on monitoring our limits. In short, I want our Nagios instance to alert us when we get close to a limit. This is a reasonable thing to want, right? Isn't it better if I can make a low-priority request for additional whatevers before I run into the limit instead of a panicked request after I've hit the limit?

During my most recent interaction with Amazon Support on this topic, they suggested the following:

https://www.nagios.com/solutions/aws-monitoring/

This page and all related pages are INCREDIBLY light on details. Basically, it amounts to "Pay us a bunch of money for a product that might help you." The demo linked from that page makes ZERO mention of AWS, so I can't evaluate the functionality from there.

I believe that should address your concern with Nagios Monitoring

What is the basis for that belief?

https://aws.amazon.com/search?searchQuery=+Nagios&searchPath=products_and_info&x=0&y=0

This search returns two results. Two. Both of them are security bulletins.

http://aws.amazon.com/premiumsupport/

...and here it is: the reason why they are so reluctant to provide ANY means to proactively deal with AWS service limits! They want to upsell you on a support plan! In our case, the only support plan above the one we currently have is $15000/month. Assuming that this plan would, in fact, get us a person at Amazon to watch our limits for us (the description of the plan does not explicitly say that), this would be a great plan...except for the fact that I would probably be put on some kind of "Idiot List" by the accountants if I even suggested it. And for good reason, too: $15000/month is a significant fraction of our total AWS monthly bill, which is higher than we'd like as it is.

(Oh, and before you tell me to just use Trusted Advisor: I'm not going to manually check Trusted Advisor every so often and hope that it's telling me about all of the limits I might need to know about. I want something AUTOMATED.)

This is, frankly, shameful. AWS is an incredibly useful and comprehensive service, but once you start growing, you repeatedly bump into limits with little to no advance warning. That is, unless you are willing and able to pay $15K per month. I guess if you're running a VC-backed startup with regular infusions of cash, then it's not a big deal. However, if you're running a more modest enterprise or a bootstrapped startup, service limits will be an ever-present thorn in your side.

Caveat emptor.

Hosting a static site with S3 and CloudFront

Trying to get on the SSL bandwagon with a static site on S3? Getting random nonsensical 403s? Here's what you need to do: set the origin for the CloudFront distribution to the static hosting domain for your S3 bucket, NOT the bucket itself! Many thanks to what a n00b! for saving my hair.

Oh, and don't even think about using a 4096-bit key for your SSL certificate. CloudFront only accepts 2048. (And maybe 1024, but don't use that.)

Looking for the place in the AWS Console to upload your certificate? Stop looking. It's not there. Instead, you need to do this:

alex@talos:~# aws iam upload-server-certificate --server-certificate-name some_meaningful_name       \
                                                --certificate-body file://path/to/certificate        \
                                                --private-key file://path/to/key                     \
                                                --certificate-chain file://path/to/bundled/cert/file \
                                                --path /cloudfront/whatever_you_want/

That's a doozy, isn't it? Depending on who you got the SSL certificate from, you might not need to specify the certificate chain.

Defanging the ubuntu user on EC2

If you use standard Ubuntu images on EC2 but don't use the ubuntu user, you may have tried to revoke its sudo permissions by removing it from the sudo group. Have you tested that, though? You may be surprised to learn that it doesn't work. Why not? Simple: the cloud-init package adds a file in /etc/sudoers.d that explicitly gives the ubuntu user sudo access. You probably don't need cloud-init after booting the instance for the first time (unless you plan on making an AMI out of it), so you can fully defang the ubuntu user by purging cloud-init.

UPDATE: You will also need to delete the file in /etc/sudoers.d. Apparently, purging cloud-init doesn't get rid of it, even though dpkg identifies it as belonging to the cloud-init package. WTF?

Rust: Basic Syntax

So, I've been playing with Rust lately, and I've noticed that some really basic things just aren't explained in the docs (or at least not anywhere that's easy to find). This is my attempt to make up for this shortcoming.

Borrowed Parameters

So, you've got a borrowed parameter (perhaps a mutable one for a somewhat C-style API?). Now you want to work with the actual value. How do you do that? If you want to call a function on a struct, that's pretty straightforward: it's the same regardless of borrowing or not. But what if it's something simpler, like an f32? Well, borrowed things are more like C pointers than C++ references. So, you do it like this:

rust_borrow.rs

1
2
3
fn foo(t: &mut f32) {
    *t += 3.14;
}

Yep, it's a dereference operator, just like in C.

Non-Borrowed, Mutable Parameters

rust_mutable_param.rs

1
2
3
fn foo(mut t: f32) {
    t += 2.0;
}

Yes, this can actually be useful on occasion, typically when dealing with a struct that gets moved. That said, the one time so far that I used this construct, I ended up changing it to something less weird.

Types of Borrowed Parameters

Wondering what the difference between mut foo: &f32 and foo: &mut f32 is? Here you go:

rust_silly_mutability.rs

fn foo<'a>(mut t: &'a i32, v: &'a i32) {
    t = v;
    println!("In foo, t = {}", t);
}

fn bar(t: &mut i32) {
    *t += 1;
    println!("In bar, t = {}", t);
}

fn main() {
    let mut t = 0;
    let v = 42;
    foo(&t, &v);
    bar(&mut t);
    println!("In main, t = {}", t);
}

What's that? That didn't help at all? You're even more confused now? OK, let's dissect that a bit. First off, if you're unfamiliar with the &'somerandomthing syntax, that's a lifetime specification. Essentially, if you have &'a var1 and &'a var2, then var1 and var2 will have the same lifetime. You usually don't need to specify this, but sometimes the compiler needs a little help.

So, foo takes a mutable, borrowed reference to an i32 called t and an immutable, borrowed reference to an i32 called v. It then sets t to point to whatever v points to. Thus, foo prints In foo, t = 42.

bar takes a borrowed reference to a mutable i32 called t. It then increments the value that t points to. Thus, bar prints In bar, t = 1.

Since bar took a reference to a mutable value, t has changed in main. Thus, main prints In main, t = 1.

Does mut t: &i32 seem useless to you? It does to me, but I haven't been writing Rust code for very long.

IPv6 on Comcast via Debian

My Internet router at home is low-power computer running Debian. I recently moved, and I now have Comcast as my ISP. (I know, I know. The only alternative here is appallingly slow.) I had heard that Comcast supports IPv6, so I decided to give it a try. It took most of the afternoon and part of th evening, so I thought I would write down what I did here for the benefit of others walking the same path.

Also, on my router, eth0 is the interface on my network, and eth1 is the interface on the Internet.

The Pieces

We need the following:

  • a few settings in sysctl
  • radvd (IPv6 router advertisement daemon; helps clients configure themselves)
  • DHCPv6 client (I use wide-dhcpv6-client)
  • /etc/network/interfaces entry
  • iptables rules (Not strictly necessary, but it's good to lock things down a bit.)

sysctl

In order for this to work, we need a few sysctl parameters.

ipv6/30-ipforward.conf

# For IPv4 forwarding
net.ipv4.ip_forward=1

# For IPv6 forwarding
net.ipv6.conf.all.forwarding=1

# This is needed because a router "shouldn't" accept
# router advertisements in theory, but in practice,
# this kind of router should.
# 0 = Don't accept (we don't want this)
# 1 = Accept if we're not a router (i.e. forwarding is disabled; we don't want this)
# 2 = Accept even if we're a router (we DO want this)
# Without this parameter being 2, we don't get a default route.
net.ipv6.conf.eth1.accept_ra=2

radvd

I needed to run dhcp6c -d -D -f eth1 &> /root/dhcp6c.log (the DHCPv6 client, with output redirected to a logfile) in order to determine my prefix. I saw two different prefixes; I had to guess which one was correct. I will admit that I still don't fully understand this.

ipv6/radvd.conf

interface eth0
{
   AdvSendAdvert on;
   prefix YOUR:PREFIX:HERE::/64
   {
   };
};   

This should probably be automated in some way at some point; my setup will break if Comcast ever gives me a different prefix.

DHCPv6

This actually works, despite having sla-len of 4. However, radvd needs to advertise /64. I suspect the sla-len is just getting (somewhat) harmlessly ignored.

ipv6/dhcp6c.conf

interface eth1
{
  send ia-na 1;
  send ia-pd 0;

  request domain-name-servers;
  request domain-name;

  script "/etc/wide-dhcpv6/dhcp6c-script";
};

id-assoc pd {
  prefix-interface eth0 {
    sla-id 0;
    ifid 1;
    sla-len 4; # This should actually be 0 right now since I couldn't figure out how to get a /60 from Comcast and thus have a /64 for my delegated prefix
  };
};

id-assoc na 1 {
};

interfaces

ipv6/interfaces

# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
# dhcp6c handles IPv6 configuration for this interface.
auto eth0
iface eth0 inet static
        address 192.168.0.1
        netmask 255.255.255.0
        network 192.168.0.0
        broadcast 192.168.0.255

# dhcp6c probably also handles all the IPv6 configuration for this interface,
# but this doesn't hurt.
auto eth1
iface eth1 inet dhcp
iface eth1 inet6 auto

I'm not 100% certain that this is actually needed, given the use of wide-dhcpv6-client, but it doesn't hurt.

iptables

First off, bit of basic iptables info. iptables defines INPUT, OUTPUT, and FORWARD chains; a packet will only ever hit one of these. If the current machine is the destination, then it hits INPUT. If the current machine is the source, it hits OUTPUT. If the source or destination is a different machine, it hits FORWARD. See this StackOverflow question for more information.

I did not know this going in, and I was a bit confused. Also, the first version of these rules that I saw used the state module, while my existing IPv4 rules used conntrack. As it turns out, conntrack is a replacement for state, so I just converted the IPv6 rules to use conntrack.

Without further ado, here are my IPv6 rules:

ipv6/ip6tables

# Adapted from http://madduck.net/docs/ipv6/
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT DROP [0:0]
:in-new - [0:0]

### INPUT chain

# allow all loopback traffic
-A INPUT -i lo -j ACCEPT

# allow all ICMP traffic (see link above for discussion of security implications)
-A INPUT -p icmpv6 -j ACCEPT

# allow packets belonging to an established connection or related to one
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
# packets that are out-of-sequence are silently dropped
-A INPUT -m conntrack --ctstate INVALID -j DROP
# new connections unknown to the kernel are handled in a separate chain
-A INPUT -m conntrack --ctstate NEW -j in-new

# ...and here's that separate chain:

# allow SYN packets for SSH and HTTPS (RELATED,ESTABLISHED above handles it from there)
-A in-new -p tcp -m tcp --dport 22 --syn -j ACCEPT
-A in-new -p tcp -m tcp --dport 443 --syn -j ACCEPT
# allow DHCPv6 traffic
-A in-new -p udp -m udp --dport 546 -j ACCEPT

# log and reject everything else
-A INPUT -m limit --limit 3/min --limit-burst 10 -j LOG --log-prefix "[INPUT6]: "
-A INPUT -j REJECT

### OUTPUT chain

# allow outgoing traffic, explicitly (despite chain policy)
-A OUTPUT -j ACCEPT

### FORWARD chain

# for fowarded traffic, allow outgoing and related incoming
-A FORWARD -i eth0 -o eth1 -j ACCEPT
-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

COMMIT

I applied these like so:

root@aetherius:~# ip6tables-restore < ip6tables
root@aetherius:~# ip6tables-save > /etc/iptables/rules.v6

That last bit saves the rules to a standard location so that they will be applied automatically during boot.

It Works!

At this point, everything seemed to be working! When I replaced that last iptables rule with -A FORWARD -i eth1 -o eth0 -j ACCEPT, I was able to establish a connection from an outside machine directly to my laptop. After setting it back to the one in the listing above, I was no longer able to do so, but I could still establish a connection in the other direction, indicating that my inbound rules are correct.

I hope this helps other people trying to do the same.

Dieting: It Doesn't Have To Suck

If you want to lose weight, you must consume fewer calories than you expend. There is no getting around it. Thinking about exercising harder? That helps, but it won't save you from bad eating habits. The unfortunate fact of the matter is that you have to get your diet in order. Fortunately, it doesn't have to suck.

I'll say it again: you need to eat fewer calories than you expend (on average). Any diet that ever works for anybody does so because it gets them to do this and keep doing it. No special food, eating schedule, or exercise (aside from Olympic-level, full-time-job levels of exercise) will let you avoid this. You really do have to eat less. Sorry. Let me take a moment to expand on the subject of exercise.

I originally dedicated three paragraphs to food in my last post, and there's a reason for that. While exercise is very helpful for weight loss and essential for general health, diet has a far greater effect. Take a look at a 10.3 oz can of mixed nuts. I can eat one of these in a single sitting. It's not even that hard. That's 1700 calories right there. According to a fairly well-regarded calculator, I need to eat 2582 calories per day to maintain my weight. So, that can of nuts is about 65% of my calories for one day. By contrast, if I spend an hour on an exercise bike at pretty high intensity, I will burn about 700 calories. They say you can't outrun your fork, and it's true. Unless you're an Olympic-level athlete in active training (i.e. exercise is your full-time job), you cannot out-exercise a bad diet.

When you think "diet", do you think "eating bland, boring salads all the time"? Well, that's a perfectly valid diet for weight loss, but it's not your only option. In fact, if you want to eat nothing but Twinkies, multi-vitamins (scurvy and rickets are bad), and green beans (inadequate fiber is not a pleasant experience), you can lose weight that way. (Yes, somebody actually did that.) You just have to eat a sufficiently small quantity that your total calorie intake is low enough. That probably won't be very satisfying if done with Twinkies, though. It's much better to reduce your meat and processed food intake and increase your vegetable intake. Why? Well, vegetables are not very calorie-dense compared to meat and processed food. Thus, you can eat a satisfyingly large volume of food without going overboard on calories.

If you think you don't like vegetables, it may be that you just haven't had them prepared to your liking, or you haven't tried the right ones. Try a few different ways of preparing them. Personally, I avoid steaming vegetables; I find that it tends to make them unappetizingly mushy. I prefer to saute my vegetables in olive oil (substitute other oils as desired, and don't use a ridiculous amount), but I also like them lightly coated in oil and herbs and baked. A slow cooker may be a wise investment: it's easy to make a large amount of healthy stew or chili on Sunday and not have to cook again until Saturday (depending on how many people you have to feed, of course).

As for which vegetables, my go-to veggies are carrots, onions, celery, and spinach (or collard greens). With the occasional exception of spinach and onions, I never eat these raw. Especially celery. It just tastes awful to me raw. Carrots aren't much better. I suspect this is due to alkaloids in these vegetables and my personal sensitivity to alkaloids. Fortunately, it doesn't take much cooking to destroy these unpleasant substances, and a bit of oil and a generous helping of herbs will cover up whatever's left. I also like to add beans to my dishes. They add fiber and protein, and they're tasty. Which beans I use depends on what I'm making; different kinds of beans go better with different types of food. For example, I tend to use black beans with poultry and red beans with red meat, but sometimes I also go with edamame or lima beans instead. Edamame have a particularly good carbohydrate-to-protein ratio, if that's important to you.

Also, oils (olive oil is a good one) are not the enemy (but don't go overboard with them), and herbs are your best friends. I honestly cannot emphasize the value of herbs enough. A bit (or a bunch) of oregano, basil, and sage can turn an uninspiring dish into a delicious one, and the amount of calories added is too low to even think about. Add just enough oil to make them stick to whatever you're cooking, and you're good to go! Unless you have high blood pressure, don't shy away from salt, either. Generally avoid sugar, but if a recipe calls for a fairly small amount (e.g. tomato sauce), don't sweat it. I recommend avoiding non-fruit carbohydrates in general, but the composition of your diet really isn't as important as the total amount of calories and whether you can stick with it. It may take weeks, months, or years to reach your goal weight (depending on where you start and where you want to go), and any diet that relies on food that isn't delicious is not going to last.

Lastly, bear in mind that there's no diet that works for everyone, either. The fact that a particular diet worked for me does not mean that the same diet will work for you, simply because you are not me. Therefore, experiment! As long as you are burning more calories than you are eating, you are making progress, so don't be afraid to change your tactics! I've changed my approach a few times for various reasons. It's fine. Figure out what works for you and isn't so onerous that you abandon it immediately. Do make sure you give any new diet a fair shake, though. I'd say that if a diet seems good at first, stick with it for a month or two, then reevaluate. Don't ditch it as soon as the novelty wears off.

Weight Loss: There Is No Magic

I spent the weekend in the company of relatives I don't see very often, and I found myself answering a particular set of questions over and over again. Specifically, people noticed that I had lost a considerable amount of weight since they had seen me last. This is true: over the past three years or so, I have lost about 90 pounds. Many of them wanted to know how I had done it, and I found myself telling the same story again and again. So, I'm going to tell that story here so that more people can see it.

I guess I should start with a bit of background. I was never a particularly fit child, but I wasn't obscenely fat, either. I steadily gained weight through my adolescence, reaching 200 pounds in high school. (Or maybe early college. I don't quite remember.) I didn't gain a tremendous amount of weight in college, though (no "freshman 15" for me), but I certainly didn't lose any, either. Things really started to go off the rails after I graduated. With a decent salary to pay for food, I stuffed myself. I thought nothing of buying a medium bag of peanut M&Ms and devouring it in a single sitting. I usually only did that once a week, but even so, that's a considerable amount of calories. At the same time, I lied to myself. I told myself that I "wasn't that fat" and similar nonsense. I reassured myself with my "not really bad" cholesterol levels and "not going to be diabetic soon" blood sugar when I went to the doctor.

The turning point came after a vacation in Colorado. Nothing dramatic happened. There was no health scare or injury or any such thing. I just looked down at the scale, and it read 235 pounds. For some reason, seeing that number on the scale lead me to decide that I simply couldn't go on like that. I had to change. Somehow, the delusion of health just shattered in that moment. This is the closest you're going to get to magic in this story.

(Well, there was another thing at the back of my mind: a rather planetary friend from high school had become trim and fit. If he could do it, why couldn't I?)

In those days, I would have a beer with supper every night, and also with lunch on the weekends. That was the first thing to go. Next up: junk food. I didn't cut it out entirely, but I only bought it in quantities that would be OK to eat in a single sitting, since that's exactly what I would (and still will) do if I bought it. So, instead of buying a medium bag of peanut M&Ms, I would buy a one of those small 1 or 2 serving bags at the checkout. I also increased the amount of vegetables in my diet, since vegetables are fairly non-calorie-dense. A heaping bowl full of fresh spinach is around 40 calories, for example. That's nothing. Adding more vegetables added satisfying bulk to my meals without adding too many calories. This was enough to get me started.

Within a year or so, I was down some 40 pounds, and I was feeling a bit better. Over the next two years, I lost an additional 50 or so pounds. As of today (August 24, 2015), I'm still going, although my goals have changed. I'm no longer trying to be non-fat; I'm now trying to get my body fat low enough to see my abdominal muscles. The healthier I've gotten, the harder it has been to lose more weight, as it should be. If you're embarking on a similar journey, know that it will get harder as you make progress, but know also that as you see yourself change, you will get more motivated. After I lost the 40-some pounds, I didn't really see all that much of a difference. After losing about 20 more, I could scarcely believe what I saw in the mirror. So, keep track of your weight. Take joy in watching the numbers get smaller, and before long, you'll see the difference.

If you think restricting your calories like that will be less fun than continuing to eat whatever you want, whenever you want, well, you're right. It will be less fun in the short term. (And "short" is relative: this process could take months or years.) So, you need to keep your eyes on the prize and not give up. When you're tempted to eat the wrong foods, remind yourself that you can eat less-healthy food later, after you've lost the weight. Plan a feast to celebrate when you finally reach your goal. (Mine is a big bowl of fried rice with sesame chicken and a pint of Ben and Jerry's. And maybe some cake, too.) Think about how delicious that feast will be, and remember that "cheating" on your diet won't make it come any sooner. If you're tempted to just have that feast anyway, without meeting your goal, think about how much more enjoyable it will be when you can look in the mirror afterward and say, "Oh, yeah. I look GOOD." Oh, and a reasonably-sized treat (e.g. one non-giant cookie or a Snickers bar) once a week won't sink you.

Of course, at some point (well, probably several points), you will slip up. You will go to a party or on a vacation or something, and your weight loss will pause and possibly even reverse a bit. Yes, you can (and probably should) feel angry at yourself for this. You messed up. Go ahead, be angry. After all, if you just let yourself off the hook for it, what's to stop you from just "slipping up a bit" every day? After giving yourself a thorough thrashing, let it go. Yes, that's right. Let it go. If you thrash yourself too much, despair will set in, and you'll go off the rails just as surely as if you had let yourself off the hook. Remember: you lost that weight before, and you can do it again. Just get back on track, and in a week or two, your slip-up will be nothing but an unpleasant memory.

So, what do you do once you've lost the weight and want to keep it from coming back? Simple: calories in = calories out. On average. You can run a calorie deficit during the week and overeat a bit on the weekend. Or you can have a cookie with lunch every day. It's your life. You choose! Just remember: you're in it for the long haul, so make sure you can enjoy it.

Next time: how to eat right without hating your life.

View matrices: a straight answer

I've been following the (mostly) great modern OpenGL tutorial over at learnopengl.com. It's been very helpful so far in laying out just how to do OpenGL the modern way.

However, it is completely wrong about the view matrix. To save others from tearing their hair out over this (no thanks to the extremely unhelpful Wikipedia pages on the matter), I will explain it all here.

First of all, you need to understand what a view matrix is. It's a change of basis transformation, yes, but more precisely, it's a rotation and a translation. However, it's important to get the order right! Translation followed by rotation is NOT the same thing as rotation followed by translation. For example, consider a translation of (3, 0, 3). What does this do to the point (-3, 0, -3)? It moves it to (0, 0, 0), which means that any following rotation will have zero effect on that point! However, if you do the rotation first, then (assuming it's a non-trivial rotation) the point will cease to be (3, 0, 3), so the translation will no longer move it to (0, 0, 0).

So, how does one make a view matrix? Well, according to learnopengl.com, one multiplies a translation matrix by a rotation matrix. In other words, the rotation is applied first, then the translation. This is wrong! You want to reposition your origin first, then rotate. So, the correct multiplication is as follows:

D: camera direction vector (normalized)
R: camera right vector (normalized)
U: camera up vector (normalized)
P: camera position vector

L = | R_x R_y R_z 0 |   | 1 0 0 -P_x |
    | U_x U_y U_z 0 | * | 0 1 0 -P_y |
    | D_x D_y D_z 0 |   | 0 0 1 -P_z |
    | 0   0   0   1 |   | 0 0 0  1   |

In other words, learnopengl.com has it backwards. I suspect this error was not caught because they use the GLM library to generate their view matrix, and it takes care of all this for you. If you're trying to do it all yourself (either for educational reasons or because you aren't using C++), doing what they describe will lead to much wailing an gnashing of teeth.

Here's a quick way to check that their matrix is wrong: consider the case where the camera is at (3, 0, 3) and looking at (0, 0, 0). What is the position of (0, 0, 0) in camera space? If you do the rotation first, then you will get an answer of (-3, 0, -3), which is obviously wrong, since that point does not lie on the camera space's Z axis! This is because the rotation maps (0, 0, 0) to itself, so all you get is a translation. If you do the translation first, then you get (0, 0, -sqrt(18)), which is on the Z axis and the correct distance away (Pythagoreaon Theorem; it's negative because camera space has the positive Z axis point into the "lens").

I hope this clears things up. If you're using C++, I highly recommend using GLM to do it for you.