Have posts that belong to multiple categories, exclude some categories from homepage

Your problem is your query, but before I cover that, there are some concerns that are getting in the way of seeing the problem.

Firstly, every single line has . It’s like hanging up and dialling the number again at the end of every sentence in a telephone conversation, and it’s so much more effort to type and read. So lets fix that:

<?php
$query = new WP_Query( 'cat=-32,-99' );
if ( $query->have_posts() ) :
    while ( $query->have_posts() ) :
        $query->the_post();
        if (! in_category ('-32')) ;
        x_get_view( 'ethos', 'content', get_post_format() );
        endif;
    endwhile;
    wp_reset_postdata();
else :
    ?> <p><?php _e( 'Sorry, no posts matched your criteria.' ); ?></p><?php
endif;

This is much easier to read, and reveals a new bug you were unaware of. You’ve been using the shorthand syntax here:

if (! in_category ('-32')) ;
x_get_view( 'ethos', 'content', get_post_format() );
endif;

But notice the if statement has a ; not a :? This evaluates to:

if ( ! in_category( '-32' ) ) {
    // do nothing
}
x_get_view( 'ethos', 'content', get_post_format() );// this always happens

You can also pass -32 rather than '-32' into in_category. I strongly recommend using if () {} syntax as it’s more widespread, better supported in tools and editors, and easier to type. Don’t make extra work for yourself.

You’re also checking if the post is inside the category -32, do you mean to check against 32?

Moving on to your original problem:

$query = new WP_Query( 'cat=-32,-99' );

Here cat is being used, however this isn’t the best way to define it, and technically cat should take a single parameter. Instead define it this way:

$query = new WP_Query( array(
    'category__not_in' => array( 32, 99 )
) );

My final concern is that you’ve hardcoded the IDs of the category terms. This code will fail spectacularly if you ever tried an import/export. This is a bad case of magic numbers

Instead, perhaps use the category slugs instead:

$example_term = get_term_by( 'name', 'example1', 'category' );
$example_term_2 = get_term_by( 'name', 'example2', 'category' );
$query = new WP_Query( array(
    'category__not_in' => array( $example_term->term_id, $example_term_2->term_id )
) );

While category slugs/names are still bad, they’re much more portable and robust than category term IDs. Ideally you would pull these from an option value or a post meta value instead.

The Final and Most Important Issue

All of this, is exactly the same as doing this:

query_posts( array(
    'category__not_in' => array( 32, 99 )
) );

You’ve discarded all the work WordPress did then did it a second time yourself.Instead, you should be using the pre_get_posts filter to tell WordPress what you actually wanted, putting it all together giving us:

add_filter( 'pre_get_posts', function ( $query ) {
    if ( $query->is_home() && $query->is_main_query() ) {
        $example_term = get_term_by( 'name', 'example1', 'category' );
        $example_term_2 = get_term_by( 'name', 'example2', 'category' );
        $query->set( 'category__not_in', $example_term->term_id, $example_term_2->term_id );
    }
});

This allows you to use the main loop rather than a custom WP_Query loop, simplifying your original code even further

Further reading: