OFDM symbol acquisition block in GNU Radio gives rotating constellation even if there is no carrier offset. How is this possible?
Got me at that one! The "OFDM symbol acquisition" block is in fact not from gr-digital (where your other OFDM blocks come frome), but from gr-dtv, where it is used to capture DVB-T signals, if I remember correctly. It might be very DVB-specific!
Let us have a look at the dvbt_rx_8k.grc example from gr-dtv (or, at least, the top half):



So your understanding is absolutely right, OFDM symbol acquisition just does some syncing.

This time, I'll leave a bit more up to your own research (as finishing this up as a nice answer will probably take quite some time), and will concentrate on the steps I took to figure out how the block works. I hope this is most helpful to you, and of course helpful for future answer-seekers trying get the general grasp on how to find out how GNU Radio blocks "tick". Looking at your flow graph, I'm pretty sure you've done most of this already, but as I mentioned, I'd like to illustrate the "investigative principle" for others.


I figured out how the underlying block is called. Typically, the default name of a new block of that type gives that away, or the documentation type. So dtv_dvbt_ofdm_sym_acquisition was what I found.
Then I went to the GNU Radio source repo clone and used git to figure out where to look (I'll annotate the result with a bit of hints):



$ git ls-files|grep ofdm_sym_acquisition
gr-dtv/grc/dtv_dvbt_ofdm_sym_acquisition.xml ##.xml: the file describing how this block looks in GRC
gr-dtv/include/gnuradio/dtv/dvbt_ofdm_sym_acquisition.h ##gr-something/include/: public interface, the one accessible and documented in Doxygen
gr-dtv/lib/dvbt/dvbt_ofdm_sym_acquisition_impl.cc ##_impl.cc,h: the actual implementation, which has all the "ugly" details, private variables and stuff
gr-dtv/lib/dvbt/dvbt_ofdm_sym_acquisition_impl.h



For us, the _impl is of most relevance. So I opened it and looked for a work or general_work method right away (for difference between these, refer to the Guided Tutorials part 4)
The (general_)work method is the central workhorse of a GNU Radio block: GNU Radio's scheduler will call it whenever there are input items to process (given that there's enough output buffer space). So I skimmed it. Pasting it here, trying to delete all lines that I didn't care about at first. Find my "mental" annotations in between.



    dvbt_ofdm_sym_acquisition_impl::general_work (int noutput_items,
…
    {


The usual stuff: input items are in in, output goes to out:


        const gr_complex *in = (const gr_complex *) input_items[0];
        gr_complex *out = (gr_complex *) output_items[0];
…


Ah-Ha! We loop over something, let's keep an eye out for the i.

        for (int i = 0; i &lt; noutput_items; i++) {
…


ok, judging by the variables name this is the case of us not having "locked" on to sync sometime. Let's concentrate on this and ignore the other case for now.


          if (!d_initial_acquisition) {


This looks a lot as if the interesting work is done in ml_sync. d_cp_length definitely points to us having to deal with a system that uses cyclic prefixes.


            d_initial_acquisition = ml_sync(&in[d_consumed], 2 * d_fft_length + d_cp_length - 1, d_fft_length + d_cp_length - 1, \
                &d_cp_start, &d_derot[0], &d_to_consume, &d_to_out);
            d_cp_found = d_initial_acquisition;
          }
          else {
…
            }
          }


I decide to break here and take a look at ml_sync.

The name is pretty revealing already; it's probably going to be a Maximum Likelihood Synchronizer. 

So, going up in the source, we find ml_sync. It's pretty well-commented and uses (VOLK = Vector optimized library of kernels) functions that have names like magnitude_squared; I think I'll leave the lecture up to you and give you my quick understanding of it:


volk_32fc_magnitude_squared_32f(&amp;d_norm[low], &amp;in[low], size); calculates the mag² == norm of the input, starting at the low position, saves it in d_norm
The "moving sum" loop calculates the "amount" of correlation and the signal energy in windows of size d_fft_length; that makes sense. A high power signal can have a very high correlation coefficient with something that is not really "similar", so you need to "normalize" later on. Results are stored in d_gamma and d_phi, respectively.
The magnitudes of the windowed-correlation vector elements in d_gamma are stored in d_lambda, and something linearly dependend of d_phi is subtracted from those.
Peak search in d_lambda.
From the peak position, a frequency correction is calculated. From the correlation coefficients, phase-derotation is done.




So what we have here is an autocorrelation-based synchronizer that locks onto the cyclic prefix. Nice! 

Comparing this with Schmidl&amp;Cox that was used in ofdm_rx.grc, calculating the autocorrelation is of course pretty CPU-intense, and we need as much Cyclic Prefix as we can get. However, this might be (is) a suitable synchronizer for the typical broadcast channel, where FFTs are long, and so are prefixes, and the trade-off between being fast at decoding packets and not missing a single packets is biased in favor of a RX-maximizing receiver operation characteristic.

 
To the question why this doesn't work in your setup, I must admit that it's a bit over my head. My intuition tells me that there might be ambiguities due to your 1/4 payload/prefix ratio, and you consistently lock onto the middle of two consecutive frames or so. 

The point is: try!