It turns out that this was another issue caused by critical pipeline code being defined in a sample plugin that I outlined in this question.
Basically, the cart plugin itself only registers two blocks:
ClearCartTotals, with the rest of the functionality added by the AdventureWorks sample plugin. The reasons for this are unknown, but since neither specifies an order argument when registering their pipeline blocks it's basically luck (read: order of the dependencies during build) that makes the remaining blocks get added after the Clear* blocks. When I moved the blocks from the AdventureWorks sample to our own project, that luck ran out so the totals were being calculated and then subsequently cleared.
The solution is to register the blocks with an order specified that's greater than the default of
services.Sitecore().Pipelines(config => config
.ConfigurePipeline<ICalculateCartLinesPipeline>(builder => builder
order: 1001 // This fixes the issue