Como o MySQL não suporta funções de janela, acho que não há como fazer isso sem uma subconsulta:
SELECT f.id AS forum_id,
f.name AS forum_name,
t.id AS thread_id,
t.topic AS thread_topic,
t.ts AS thread_timestamp,
p.id AS post_id,
p.content AS post_content,
p.ts AS post_timestamp
FROM forums f
JOIN (SELECT t2.forum_id, max(p2.ts) as ts
FROM posts p2
JOIN threads t2 ON p2.thread_id = t2.id
GROUP BY t2.forum_id) max_p ON f.id = max_p.forum_id
JOIN posts p ON max_p.ts = p.ts
JOIN threads t ON f.id = t.forum_id AND p.thread_id = t.id
ORDER BY p.ts
Naturalmente, armazenar em cache os resultados mais recentes permitiria que você fizesse isso sem a penalidade de desempenho de chamar MAX(), mas com os índices corretos, isso não deve ser um problema...
ATUALIZAÇÃO
A maneira mais concisa de incluir os tópicos sem postagens e fóruns sem tópicos seria usar LEFT JOINs em vez de INNER JOINs:
SELECT f.id AS forum_id,
f.name AS forum_name,
t.id AS thread_id,
t.topic AS thread_topic,
t.ts AS thread_timestamp,
p.id AS post_id,
p.content AS post_content,
p.ts AS post_timestamp
FROM forums f
LEFT JOIN (SELECT t2.forum_id, max(COALESCE(p2.ts, t2.ts)) as ts, COUNT(p2.ts) as post_count
FROM threads t2
LEFT JOIN posts p2 ON p2.thread_id = t2.id
GROUP BY t2.forum_id) max_p ON f.id = max_p.forum_id
LEFT JOIN posts p ON max_p.ts = p.ts
LEFT JOIN threads t ON f.id = t.forum_id AND (max_p.post_count = 0 OR p.thread_id = t.id)
ORDER BY p.ts