THEN

Perl:
# moves from here down
# slurp everything this Pokemon can learn, complete with egg moves if necessary
my $moves_rs = $c->model('DBIC::PokemonMoves')->search( {
    pokemon_id => $row->id,
} );

my %moves = (
    level => [],
    egg => [],
    tutor => [],
    machine => [],
    other => [],
);
while (my $move_row = $moves_rs->next) {
    my $table_row = {
        move_id => $move_row->move_id,
        level => $move_row->level,
        versions => { map { $_ => $move_row->level || 1 } split /,/, $move_row->versions },
    };

    my $method = $move_row->method;
    if (!Vee::Utils::in($method, qw/level egg tutor machine/)) {
        $table_row->{method} = $method;
        $method = 'other';
    }

    push @{ $moves{$method} }, $table_row;
}

# sorting go!
@{ $moves{$_} } = sort {
    $a->{level} <=> $b->{level} or
    $MoveData[ $a->{move_id} ]->name cmp $MoveData[ $b->{move_id} ]->name
} @{ $moves{$_} } for qw/ level egg machine /;

# column reduction
my %move_columns = ( );

my @column_pairs = ( [qw/rb y/], [qw/gs c/], [qw/rusa frlg/], [qw/dp pt/] );
my @column_deletions = (1) x @column_pairs;

for my $move (@{ $moves{level} }, @{ $moves{egg} }, @{ $moves{machine} }) {
    for my $p (0 .. $#column_pairs) {
        $column_deletions[$p] = 0 if ($move->{versions}{ $column_pairs[$p][0] } || 0) != ($move->{versions}{ $column_pairs[$p][1] } || 0);
    }
}
for my $p (0 .. $#column_pairs) {
    next unless $generation <= $p;
    $move_columns{ $column_pairs[$p][0] } = 1;
    $move_columns{ $column_pairs[$p][1] } = 1 unless $column_deletions[$p];
}
    
$s->{move_columns} = [ sort { $column_order{$a} <=> $column_order{$b} } keys %move_columns ];
$s->{move_columns_inv} = \%move_columns;

# The following mess is the code to compact the level move display as much
# as humanly possible.  This is the third incarnation of the code, and it
# is the most efficient, well-written, and well-documented version so far.
# I am quite pleased with it.  Enjoy.

# This is for slight ease of typing, as well as reading with coloring.
my $lev_moves = $moves{level};

# Continue to run through the list as long as we are still doing something.
# There should be no possible way that this creates an infinite loop.
my $merges;
do {
    $merges = 0;
    my $i = -1;
    while (++$i <= $#$lev_moves) {  # shouldn't use a for since the size of the array changes
        # find the next row index with the same move id
        my $next_idx = first { $lev_moves->[$_]{move_id} == $lev_moves->[$i]{move_id} } $i + 1 .. $#$lev_moves;
        next if not defined $next_idx;

        # make the following mess easier to read/write
        my $this = $lev_moves->[$i];
        my $next = $lev_moves->[$next_idx];

        # Ensure there are no level-up moves between these two that have the
        # same version, as that would wreck the ordering.
        # Note that there need to be TWO checks; one going in each direction.
        # For example:
        #   1 - Tail Whip
        #   2 - Bubble
        #   - 3 Tail Whip
        # It is illegal to move the first Tail Whip forwards, but the last one
        # can be moved backwards with no problems.
        my ($forward_ok, $backward_ok) = (1, 1);
        my %versions = map { $_ => 1 } keys(%{ $this->{versions} }), keys(%{ $next->{versions} });
        for my $version (keys %versions) {
            for my $mid_idx ($i + 1 .. $next_idx - 1) {
                next if not defined $lev_moves->[$mid_idx]{versions}{$version};
                $forward_ok  = 0 if defined $this->{versions}{$version} &&
                    $lev_moves->[$mid_idx]{versions}{$version} > $this->{versions}{$version};

                $backward_ok = 0 if defined $next->{versions}{$version} &&
                    $lev_moves->[$mid_idx]{versions}{$version} < $next->{versions}{$version};
            }
        }
        next if not $forward_ok and not $backward_ok;

        # merge one row into the other, forward by default (this is arbitrary)
        my ($from, $into, $from_idx) = $forward_ok ? ($this, $next, $i) : ($next, $this, $next_idx);
        for my $version (keys %{ $from->{versions} }) {
            next if exists $into->{versions}{$version};
            $into->{versions}{$version} = $from->{versions}{$version};
            delete $from->{versions}{$version};
            $merges++;
        }
        if (scalar keys %{ $from->{versions} } == 0) {  
            # only move id left, so delete this row and redo to hit the next one
            splice @$lev_moves, $from_idx, 1;
            redo;
        }
    }
} while ($merges);

$s->{moves} = \%moves;
Template Toolkit template:
[% header_icons = [] %]
[% IF generation == 0 %]
[%     IF move_columns_inv.y; header_icons.push('rb'); header_icons.push('y'); ELSE; header_icons.push('rby'); END %]
[% END %]
[% IF generation <= 1 %]
[%     IF move_columns_inv.c; header_icons.push('gs'); header_icons.push('c'); ELSE; header_icons.push('gsc'); END %]
[% END %]
[% IF generation <= 2 %]
[%     IF move_columns_inv.frlg; header_icons.push('rse'); header_icons.push('frlg'); ELSE; header_icons.push('rsefl'); END %]
[% END %]
[% IF generation <= 3 %]
[%     IF move_columns_inv.pt; header_icons.push('dp'); header_icons.push('pt'); ELSE; header_icons.push('dppt'); END %]
[% END %]

[% version_headers = BLOCK %]
    <th class="level"></th>
[%     FOR ver IN header_icons %]
<th class="level">[% Icons.$ver %]</th>
[%     END %]
[% END %]

[% preferred_damage = '' %]
[% IF this.stat_at < this.stat_sa %]
[%     preferred_damage = 'special' %]
[% ELSIF this.stat_at > this.stat_sa %]
[%     preferred_damage = 'physical' %]
[% END %]
[% preferred_types = { ${this.type1} => 1 } %]
[% SET preferred_types.${this.type2} = 1 IF this.type2 %]

<h1>Move List</h1>
<p> <a href="[% c.uri_for('moves/search', { pokemon => this.name }) %]"><img src="/images/see-also.png" alt="See also:"/> This list in the move search</a> </p>
<p> <a href="[% c.uri_for('moves/search', { pokemon => this.name, view => 'contest' }) %]"><img src="/images/see-also.png" alt="See also:"/> Contest list</a> </p>
[% IF preferred_damage %]
<p> [% this.name %]'s [% preferred_damage == 'special' ? 'Special ' : '' %]Attack is higher, so it will inflict more damage with [% preferred_damage %] (<img src="/dex-images/gameui/[% preferred_damage %].png" alt=""/>) moves. </p>
[% END %]
<p> [% this.name %] gets the <acronym title="Same-Type Attack Bonus">STAB</acronym> from [% type_name(this.type1) %]
[%- IF this.type2 %]
 and [% type_name(this.type2) %]
[% END %] moves. </p>

[% UNLESS header_icons.size == 1 %]
<div id="js-move-table-controls"> </div>
[% END %]

<table class="dex-table" cellspacing="0" id="js-movetable">
<!-- XXX TODO ETC: THIS IS UGLY PLEASE NORMALIZE USE OF VERSIONS -->
<tr class="heading">
[% version_headers %]
[% move_header %]
</tr>

[% color = 1 %]

<!-- emerald level moves are all identical to rusa -->
<!--level-->
[% FOREACH move IN moves.level %]
<tr class="color[% color %]">
 <td></td>
[%     FOREACH ver IN move_columns %]
 <td class="level"> [% move.versions.$ver ? ( move.versions.$ver == 1 ? '--' : move.versions.$ver ) : '' %] </td>
[%     END %]
[%+    move_cells(move.move_id) %]
</tr>
[%     color = 3 - color %]
[% END %]

<!--egg-->
[% IF moves.egg.size %]
<tr class="heading"> [% version_headers %] <th colspan="8" class="title"> Egg moves [% IF moves.machine.size %](Pok&eacute;mon can also inherit any TMs they are compatible with)[% END %] </th> </tr>
[%     FOREACH move IN moves.egg %]
[%         move_id = move.move_id %]
<tr class="color[% color %]">
 <td><a href="[% c.uri('Dex::Utils', 'breeding_chains', { pokemon => this.name, move => MoveData.$move_id.name }) %]"><img src="/dex-images/tree.png" alt="Chains" title="Breeding chains"/></a></td>
[%         IF generation == 0 %] <td class="level"> </td>[% IF move_columns_inv.y %] <td class="level"> </td>[% END %][% END %]
[%         IF generation <= 1 %]
 <td class="level"> [% Icons.gs IF move.versions.gs %] </td>
[%             IF move_columns_inv.c %] <td class="level"> [% Icons.c IF move.versions.c %] </td>[% END %]
[%         END %]
[%         IF generation <= 2 %]
 <td class="level"[% ' colspan="2"' IF move_columns_inv.frlg %]> [% Icons.rusa IF move.versions.rusa %] </td>
[%         END %]
[%         IF generation <= 3 %]
 <td class="level"[% ' colspan="2"' IF move_columns_inv.pt %]> [% Icons.dppt IF move.versions.dp %] </td>
[%         END %]
[%+        move_cells(move.move_id) %]
</tr>
[%         color = 3 - color %]
[%     END %]
[% END %]

<!--tutor-->
[% IF moves.tutor.size %]
<tr class="heading"> [% version_headers %] <th colspan="8" class="dextbl_divider"> Tutored moves </th> </tr>
[%     FOREACH move IN moves.tutor %]
[%         move_id = move.move_id %]
<tr class="color[% color %]">
 <td></td>
[%         IF generation == 0 %] <td class="level"> </td>[% IF move_columns_inv.y %] <td class="level"> </td>[% END %][% END %]
[%         IF generation <= 1 %]
[%             IF move_columns_inv.c %] <td class="level"> </td>[% END %]
 <td class="level"> [% Icons.c IF move.versions.c %] </td>
[%         END %]
[%         IF generation <= 2 %]
[%             IF move_columns_inv.frlg %]
 <td class="level"> [% Icons.e IF move.versions.e %] </td> <td class="level"> [% Icons.frlg IF move.versions.frlg %] </td>
[%             ELSE %]
 <td class="level"> [% Icons.e IF move.versions.e; Icons.frlg IF move.versions.frlg %] </td>
[%             END %]
[%         END %]
[%         IF generation <= 3 %]
[%             IF move_columns_inv.pt %]
 <td class="level"> [% Icons.dp IF move.versions.dp %] </td> <td class="level"> [% Icons.pt IF move.versions.pt %] </td>
[%             ELSE %]
 <td class="level"> [% Icons.dp IF move.versions.dp; Icons.pt IF move.versions.pt %] </td>
[%             END %]
[%         END %]
[%         move_cells(move_id) %]
</tr>
[%         color = 3 - color %]
[%     END %]
[% END %]

<!--other-->
[% IF moves.other.size %]
<tr class="heading"> [% version_headers %] <th colspan="8" class="dextbl_divider"> Miscellaneous moves </th> </tr>
[%     FOREACH move IN moves.other %]
[%         move_id = move.move_id %]
<tr class="color[% color %] js-versionless">
 <td></td>
 <td colspan="[% move_columns.size %]" class="level"> [% move.method %] </td>
[%         move_cells(move_id) %]
</tr>
[%         color = 3 - color %]
[%     END %]
[% END %]

<!--machines-->
[%# TODO: isn't this elsewhere? %]
[% machine_versions = { rb => 0, y => 0, gs => 1, c => 1, rusa => 2, frlg => 2, e => 2, dp => 3, pt => 3 } %]
[% base_versions = { rby => 'rb', gsc => 'gs', rse => 'rusa', rsefl => 'rusa', dppt => 'dp' } %]
[% IF moves.machine.size %]
<tr class="heading"> [% version_headers %] <th colspan="8" class="dextbl_divider"> TMs and HMs </th> </tr>
[%     FOR move IN moves.machine %]
[%         move_id = move.move_id %]
<tr class="color[% color %]">
 <td><a href="[% c.uri('Dex::Utils', 'breeding_chains', { pokemon => this.name, move => MoveData.$move_id.name }) %]"><img src="/dex-images/tree.png" alt="Chains" title="Breeding chains"/></a></td>
[%         FOR ver_col IN header_icons %]
[%             ver = base_versions.$ver_col || ver_col %]
    <td class="level"> [% IF move.versions.$ver; tm_short_name(MoveTMs.$move_id.${machine_versions.$ver}); END %] </td>
[%         END %]
[%         move_cells(move_id) %]
</tr>
[%         color = 3 - color %]
[%     END %]
[% END %]
</table>

NOW

Python:
### Moves
# Oh no.
# Moves are grouped by method.
# Within a method is a list of move rows.
# A move row contains a level or other status per version group, plus
# a move id.
# Thus: method => [ (move, { version_group => data, ... }), ... ]
# "data" is a dictionary of whatever per-version information is
# appropriate for this move method, such as a TM number or level.
c.moves = collections.defaultdict(list)
# Grab the rows with a manual query so we can sort thm in about the row
# they go in the table.  This should keep it as compact as possible
q = pokedex_session.query(PokemonMove) \
                   .filter_by(pokemon_id=c.pokemon.id) \
                   .order_by(PokemonMove.level.asc(),
                             PokemonMove.order.asc(),
                             PokemonMove.version_group_id.asc())
for pokemon_move in q:
    method_list = c.moves[pokemon_move.method]
    this_vg = pokemon_move.version_group

    # Create a container for data for this method and version(s)
    vg_data = dict()

    # TMs need to know their own TM number
    for machine in pokemon_move.move.machines:
        if machine.generation == this_vg.generation:
            vg_data['machine'] = machine.machine_number
            break

    # Find the best place to insert a row.
    # In general, we just want the move names in order, so we can just
    # tack rows on and sort them at the end.  However!  Level-up moves
    # must stay in the same order within a version group.  So we have
    # to do some special ordering here.
    # These two vars are the boundaries of where we can find or insert
    # a new row.  Only level-up moves have these restrictions
    lower_bound = None
    upper_bound = None
    if pokemon_move.method.name == 'Level up':
        vg_data['sort'] = (pokemon_move.level, pokemon_move.order)
        vg_data['level'] = pokemon_move.level
        # Level 1 is generally thought of as a special category of starter
        # move, so the table will be easier to read if it indicates this
        if vg_data['level'] == 1:
            vg_data['level'] = ''  # em dash

        # Find the next-lowest and next-highest rows.  Our row must fit
        # between those
        for i, (move, version_group_data) in enumerate(method_list):
            if this_vg not in version_group_data:
                # Can't be a bound; not related to this version!
                continue

            if version_group_data[this_vg]['sort'] > vg_data['sort']:
                if not upper_bound or i < upper_bound:
                    upper_bound = i
            if version_group_data[this_vg]['sort'] < vg_data['sort']:
                if not lower_bound or i > lower_bound:
                    lower_bound = i

    # We're using Python's slice syntax, which includes the lower bound
    # and excludes the upper.  But we want to exclude both, so bump the
    # lower bound
    if lower_bound != None:
        lower_bound += 1

    # Check for a free existing row for this move; if one exists, we
    # can just add our data to that same row
    valid_row = None
    for table_row in method_list[lower_bound:upper_bound]:
        move, version_group_data = table_row
        if move == pokemon_move.move and this_vg not in version_group_data:
            valid_row = table_row
            break
    if valid_row:
        valid_row[1][this_vg] = vg_data
        continue

    # Otherwise, just make a new row and stuff it in.
    # Rows are sorted by level going up, so any future ones wanting to
    # use this row will have higher levels.  Let's put this as close to
    # the end as we can, then, or we risk making multiple rows for the
    # same move unnecessarily
    new_row = pokemon_move.move, { this_vg: vg_data }
    method_list.insert(upper_bound or len(method_list), new_row)

for method, method_list in c.moves.items():
    if method.name == 'Level up':
        continue
    method_list.sort(key=lambda (move, version_group_data): move.name)

# Finally, we want to collapse identical adjacent columns within the
# same generation.
# All we really need to know is what versions are ultimately collapsed
# into each column, so we need a list of lists of version groups:
# [ [ rb, y ], [ gs ], [ c ], ... ]
c.move_columns = []
# We also want to know what columns are the last for a generation, so
# we can put divider lines between gens.  Accumulate indices of these
# columns as we go
c.move_divider_columns = []
# Only even consider versions in which this Pokémon actually exists
q = pokedex_session.query(Generation) \
                   .filter(Generation.id >= c.pokemon.generation_id) \
                   .order_by(Generation.id.asc())
for generation in q:
    last_vg = None
    for i, version_group in enumerate(generation.version_groups):
        if i == 0:
            # Can't collapse this column anywhere!  Just add it as a
            # new column
            c.move_columns.append( [version_group] )
            last_vg = version_group
            continue

        # Test to see if this version group column is identical to the
        # one immediately to its left; if so, we can combine them
        squashable = True
        for method, method_list in c.moves.items():
            # Tutors are special; they will NEVER collapse, so ignore
            # them for now.  When we actually print the table, we'll
            # concatenate all the tutor cells instead of just using the
            # first one like with everything else
            if method.name == 'Tutor':
                continue

            for move, version_group_data in method_list:
                if version_group_data.get(version_group, None) \
                    != version_group_data.get(last_vg, None):
                    squashable = False
                    break
            if not squashable:
                break

        if squashable:
            # Stick this version group in the previous column
            c.move_columns[-1].append(version_group)
        else:
            # Create a new column
            c.move_columns.append( [version_group] )

        last_vg = version_group

    # Remember the last column within the generation
    c.move_divider_columns.append(len(c.move_columns) - 1)

# Used for tutored moves: we want to leave a blank space for collapsed
# columns with tutored versions in them so all the versions line up,
# and to do that we need to know which versions actually have tutored
# moves -- otherwise we'd leave space for R/S, D/P, etc
c.move_tutor_version_groups = []
for method, method_list in c.moves.items():
    if method.name != 'Tutor':
        continue
    for move, version_group_data in method_list:
        c.move_tutor_version_groups.extend(version_group_data.keys())
Mako template:
<h1>Moves</h1>
<table class="dex-moves striped-rows">
## COLUMNS
% for i, column in enumerate(c.move_columns):
% if i in c.move_divider_columns:
<col class="dex-col-version dex-col-last-version">
% else:
<col class="dex-col-version">
% endif
% endfor
## XXX How to sort these "correctly"...?
## HEADERS
% for method, method_list in sorted(c.moves.items(), \
                                    key=lambda (k, v): k.id):
<tr class="header-row">
    % for column in c.move_columns:
    <th class="version">
      % if len(column) == len(column[0].generation.version_groups):
        ## If the entire gen has been collapsed into a single column, just show
        ## the gen icon instead of the messy stack of version icons
        ${h.pokedex.generation_icon(column[0].generation)}
      % else:
        % for i, version_group in enumerate(column):
        % if i != 0:
        <br>
        % endif
        ${h.pokedex.version_icons(*version_group.versions)}
        % endfor
      % endif
    </th>
    % endfor
    <th>Move</th>
    <th>Type</th>
    <th>Dmg</th>
    <th>PP</th>
    <th>Power</th>
    <th>Acc</th>
    <th>Pri</th>
    <th>Effect</th>
</tr>
<tr class="subheader-row">
    <th colspan="${len(c.move_columns) + 8}"><strong>${method.name}</strong>: ${method.description}</th>
</tr>
## DATA
% for move, version_group_data in method_list:
<tr>
  % for column in c.move_columns:
    % if method.name == 'Tutor':
    ## Tutored moves never ever collapse!  Have to merge all the known values,
    ## rather than ignoring all but the first
    <td class="tutored">
        % for version_group in column:
        % if version_group in version_group_data:
        ${h.pokedex.version_icons(*version_group.versions)}
        % elif version_group in c.move_tutor_version_groups:
        <span class="no-tutor">${h.pokedex.version_icons(*version_group.versions)}</span>
        % endif
        % endfor
    </td>
    % elif column[0] not in version_group_data:
    <td></td>
    % elif method.name == 'Level up':
    <td>${version_group_data[column[0]]['level']}</td>
    % elif method.name == 'Machine':
    <% machine_number = version_group_data[column[0]].get('machine', None) %>\
    <td>
      % if not machine_number:
        <% pass %>\
      % elif machine_number > 100:
      ## HM
        <strong>H</strong>${machine_number - 100}
      % else:
        ${"%02d" % machine_number}
      % endif
    </td>
    % elif method.name == 'Egg':
    <td class="dex-moves-egg">${h.pokedex.pokedex_img('icons/egg-cropped.png')}</td>
    % else:
    <td>&bull;</td>
    % endif
  % endfor

    <td><a href="${url(controller='dex', action='moves', name=move.name.lower())}">${move.name}</a></td>
    <td>${h.pokedex.pokedex_img("chrome/damage-classes/%s.png" % move.category)}</td>
    <td>${h.pokedex.type_link(move.type)}</td>
    <td>${move.pp}</td>
    <td>${move.power}</td>
    <td>${move.accuracy}%</td>
    ## Priority is colored red for slow and green for fast
    % if move.effect.priority == 0:
    <td></td>
    % elif move.effect.priority > 0:
    <td class="priority-fast">${move.effect.priority}</td>
    % else:
    <td class="priority-slow">${move.effect.priority}</td>
    % endif
    <td class="effect">${move.effect.short_effect}</td>
</tr>
% endfor
% endfor
</table>