<commit_before>//
//  Chipset.cpp
//  Clock Signal
//
//  Created by <NAME> on 22/07/2021.
//  Copyright © 2021 <NAME>. All rights reserved.
//

#include "Chipset.hpp"

//#ifndef NDEBUG
//#define NDEBUG
//#endif

#define LOG_PREFIX "[Amiga chipset] "
#include "../../Outputs/Log.hpp"

#include <algorithm>
#include <cassert>

using namespace Amiga;

namespace {

template <typename EnumT, EnumT... T> struct Mask {
	static constexpr uint16_t value = 0;
};

template <typename EnumT, EnumT F, EnumT... T> struct Mask<EnumT, F, T...> {
	static constexpr uint16_t value = uint16_t(F) | Mask<EnumT, T...>::value;
};

template <InterruptFlag... Flags> struct InterruptMask: Mask<InterruptFlag, Flags...> {};
template <DMAFlag... Flags> struct DMAMask: Mask<DMAFlag, Flags...> {};

/// Expands @c source so that b7 is the least-significant bit of the most-significant byte of the result,
/// b6 is the least-significant bit of the next most significant byte, etc. b0 stays in place.
constexpr uint64_t expand_bitplane_byte(uint8_t source) {
	uint64_t result = source;									// 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 abcd efgh
	result = (result | (result << 28)) & 0x0000'000f'0000'000f;	// 0000 0000 0000 0000 0000 0000 0000 abcd 0000 0000 0000 0000 0000 0000 0000 efgh
	result = (result | (result << 14)) & 0x0003'0003'0003'0003;	// 0000 0000 0000 00ab 0000 0000 0000 00cd 0000 0000 0000 00ef 0000 0000 0000 00gh
	result = (result | (result << 7)) & 0x0101'0101'0101'0101;	// 0000 000a 0000 000b 0000 000c 0000 000d 0000 000e 0000 000f 0000 000g 0000 000h
	return result;
}

/// Expands @c source from b15 ... b0 to 000b15 ... 000b0.
constexpr uint64_t expand_sprite_word(uint16_t source) {
	uint64_t result = source;
	result = (result | (result << 24)) & 0x0000'00ff'0000'00ff;
	result = (result | (result << 12)) & 0x000f'000f'000f'000f;
	result = (result | (result << 6)) & 0x0303'0303'0303'0303;
	result = (result | (result << 3)) & 0x1111'1111'1111'1111;
	return result;
}

// A very small selection of test cases.
static_assert(expand_bitplane_byte(0xff) == 0x01'01'01'01'01'01'01'01);
static_assert(expand_bitplane_byte(0x55) == 0x00'01'00'01'00'01'00'01);
static_assert(expand_bitplane_byte(0xaa) == 0x01'00'01'00'01'00'01'00);
static_assert(expand_bitplane_byte(0x00) == 0x00'00'00'00'00'00'00'00);

static_assert(expand_sprite_word(0xffff) == 0x11'11'11'11'11'11'11'11);
static_assert(expand_sprite_word(0x5555) == 0x01'01'01'01'01'01'01'01);
static_assert(expand_sprite_word(0xaaaa) == 0x10'10'10'10'10'10'10'10);
static_assert(expand_sprite_word(0x0000) == 0x00'00'00'00'00'00'00'00);

}

#define DMA_CONSTRUCT *this, reinterpret_cast<uint16_t *>(map.chip_ram.data()), map.chip_ram.size() >> 1

Chipset::Chipset(MemoryMap &map, int input_clock_rate) :
	blitter_(DMA_CONSTRUCT),
	sprites_{
		{DMA_CONSTRUCT},	{DMA_CONSTRUCT},	{DMA_CONSTRUCT},	{DMA_CONSTRUCT},
		{DMA_CONSTRUCT},	{DMA_CONSTRUCT},	{DMA_CONSTRUCT},	{DMA_CONSTRUCT}
	},
	bitplanes_(DMA_CONSTRUCT),
	copper_(DMA_CONSTRUCT),
	audio_(DMA_CONSTRUCT, input_clock_rate),
	crt_(908, 4, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red4Green4Blue4),
	cia_a_handler_(map, disk_controller_, mouse_),
	cia_b_handler_(disk_controller_),
	cia_a(cia_a_handler_),
	cia_b(cia_b_handler_),
	disk_(DMA_CONSTRUCT),
	disk_controller_(Cycles(input_clock_rate), *this, disk_, cia_b),
	keyboard_(cia_a.serial_input) {
	disk_controller_.set_clocking_hint_observer(this);

	joysticks_.emplace_back(new Joystick());
	cia_a_handler_.set_joystick(&joystick(0));

	// Very conservatively crop, to roughly the centre 88% of a frame.
	// This rectange was specifically calibrated around the default Workbench display.
	crt_.set_visible_area(Outputs::Display::Rect(0.05f, 0.055f, 0.88f, 0.88f));
}

#undef DMA_CONSTRUCT

Chipset::Changes Chipset::run_for(HalfCycles length) {
	return run<false>(length);
}

Chipset::Changes Chipset::run_until_cpu_slot() {
	return run<true>();
}

void Chipset::set_cia_interrupts(bool cia_a_interrupt, bool cia_b_interrupt) {
	// TODO: are these really latched, or are they active live?
	// If latched, is it only on a leading edge?
//	interrupt_requests_ &= ~InterruptMask<InterruptFlag::IOPortsAndTimers, InterruptFlag::External>::value;
	interrupt_requests_ |=
		(cia_a_interrupt ? InterruptMask<InterruptFlag::IOPortsAndTimers>::value : 0) |
		(cia_b_interrupt ? InterruptMask<InterruptFlag::External>::value : 0);
	update_interrupts();
}

void Chipset::posit_interrupt(InterruptFlag flag) {
	interrupt_requests_ |= uint16_t(flag);
	update_interrupts();
}

void DMADeviceBase::posit_interrupt(InterruptFlag flag) {
	chipset_.posit_interrupt(flag);
}

template <int cycle> void Chipset::output() {
	// Notes to self on guesses below:
	//
	// Hardware stop is at 0x18;
	// 12/64 * 227 = 42.5625
	//
	// "However, horizontal blanking actually limits the displayable
	// video to 368 low resolution pixel"
	//
	// => 184 windows out of 227 are visible, which concurs.
	//

	// Advance audio.
	audio_.output();

	// Trigger any sprite loads encountered.
	constexpr auto dcycle = cycle << 1;
	for(int c = 0; c < 8; c += 2) {
		if( sprites_[c].visible &&
			dcycle <= sprites_[c].h_start &&
			dcycle+2 > sprites_[c].h_start) {
			sprite_shifters_[c >> 1].load<0>(
				sprites_[c].data[1],
				sprites_[c].data[0],
				sprites_[c].h_start & 1);
		}

		if(	sprites_[c+1].visible &&
			dcycle <= sprites_[c + 1].h_start &&
			dcycle+2 > sprites_[c + 1].h_start) {
			sprite_shifters_[c >> 1].load<1>(
				sprites_[c + 1].data[1],
				sprites_[c + 1].data[0],
				sprites_[c + 1].h_start & 1);
		}
	}

	//
	// Horizontal sync: HC18–HC35;
	// Horizontal blank: HC15–HC53.
	//
	// Beyond that: guesswork.
	//
	// So, from cycle 0:
	//
	//	15 cycles border/pixels;
	//	3 cycles blank;
	//	17 cycles sync;
	//	3 cycles blank;
	//	9 cycles colour burst;
	//	6 cycles blank;
	//	then more border/pixels to end of line.
	//
	// (???)

	constexpr int end_of_pixels	= 15;
	constexpr int blank1		= 3 + end_of_pixels;
	constexpr int sync			= 17 + blank1;
	constexpr int blank2 		= 3 + sync;
	constexpr int burst 		= 9 + blank2;
	constexpr int blank3 		= 6 + burst;
	static_assert(blank3 == 53);

#define LINK(location, action, length)	\
	if(cycle == (location))	{			\
		crt_.action((length) * 4);		\
	}

	if(y_ < vertical_blank_height_) {
		if(!cycle) {
			flush_output();
		}

		// Put three lines of sync at the centre of the vertical blank period.
		// Offset by half a line if interlaced and on an odd frame.
		const int midline = vertical_blank_height_ >> 1;
		if(is_long_field_) {
			if(y_ < midline - 1 || y_ > midline + 2) {
				LINK(blank1, output_blank, blank1);
				LINK(sync, output_sync, sync - blank1);
				LINK(line_length_ - 1, output_blank, line_length_ - 1 - sync);
			} else if(y_ == midline - 1) {
				LINK(113, output_blank, 113);
				LINK(line_length_ - 1, output_sync, line_length_ - 1 - 113);
			} else if(y_ == midline + 2) {
				LINK(113, output_sync, 113);
				LINK(line_length_ - 1, output_blank, line_length_ - 1 - 113);
			} else {
				LINK(blank1, output_sync, blank1);
				LINK(sync, output_blank, sync - blank1);
				LINK(line_length_ - 1, output_sync, line_length_ - 1 - sync);
			}
		} else {
			if(y_ < midline - 1 || y_ > midline + 1) {
				LINK(blank1, output_blank, blank1);
				LINK(sync, output_sync, sync - blank1);
				LINK(line_length_ - 1, output_blank, line_length_ - 1 - sync);
			} else {
				LINK(blank1, output_sync, blank1);
				LINK(sync, output_blank, sync - blank1);
				LINK(line_length_ - 1, output_sync, line_length_ - 1 - sync);
			}
		}
	} else {
		// TODO: incorporate the lowest display window bits elsewhere.
		display_horizontal_ |= cycle == (display_window_start_[0] >> 1);
		display_horizontal_ &= cycle != (display_window_stop_[0] >> 1);

		if(cycle == end_of_pixels) {
			flush_output();
		}

		// Output the correct sequence of blanks, syncs and burst atomically.
		LINK(blank1, output_blank, blank1 - end_of_pixels);
		LINK(sync, output_sync, sync - blank1);
		LINK(blank2, output_blank, blank2 - sync);
		LINK(burst, output_default_colour_burst, burst - blank2);	// TODO: only if colour enabled.
		LINK(blank3, output_blank, blank3 - burst);

		if constexpr (cycle < end_of_pixels || cycle > blank3) {
			const bool is_pixel_display = display_horizontal_ && fetch_vertical_;

			if(
				(is_pixel_display == is_border_) ||
				(is_border_ && border_colour_ != palette_[0])) {
				flush_output();

				is_border_ = !is_pixel_display;
				border_colour_ = palette_[0];
			}

			if(is_pixel_display) {
				if(!pixels_) {
					uint16_t *const new_pixels = reinterpret_cast<uint16_t *>(crt_.begin_data(4 * size_t(line_length_ + end_of_pixels - cycle)));
					if(new_pixels) {
						flush_output();
					}
					pixels_ = new_pixels;
				}

				if(pixels_) {
					// TODO:
					//
					//	(1) dump bit 5 of a six-bitplane playfield if this Chipset doesn't support extra half-bright.
					//	(2) priorities, in general;
					//	(3) collisions;
					//	(4) a much less dense implementation. In particular: map from [1–6]-bit input to present or
					//		absent flags, use those to update collisions and then to index a mask table for compositing.
					//		Which would mean getting through the whole ordeal without branching... as soon as I come up
					//		with a branchless method for priorities. Hmmmm.
					const uint32_t source = bitplane_pixels_.get(is_high_res_);

					// TODO:
					if(dual_playfields_) {
						// Dense: just write both.
						if(even_over_odd_) {
							pixels_[0] = palette_[8 + (source >> 27) & 7];
							pixels_[1] = palette_[8 + (source >> 19) & 7];
							pixels_[2] = palette_[8 + (source >> 11) & 7];
							pixels_[3] = palette_[8 + (source >> 3) & 7];

							if((source >> 24) & 7) pixels_[0] = palette_[(source >> 24) & 7];
							if((source >> 16) & 7) pixels_[1] = palette_[(source >> 16) & 7];
							if((source >> 8) & 7) pixels_[2] = palette_[(source >> 8) & 7];
							if(source & 7) pixels_[3] = palette_[source & 7];
						} else {
							pixels_[0] = palette_[(source >> 24) & 7];
							pixels_[1] = palette_[(source >> 16) & 7];
							pixels_[2] = palette_[(source >> 8) & 7];
							pixels_[3] = palette_[source & 7];

							if((source >> 27) & 7) pixels_[0] = palette_[8 + (source >> 27) & 7];
							if((source >> 19) & 7) pixels_[1] = palette_[8 + (source >> 19) & 7];
							if((source >> 11) & 7) pixels_[2] = palette_[8 + (source >> 11) & 7];
							if((source >> 3) & 7) pixels_[3] = palette_[8 + (source >> 3) & 7];
						}
					} else {
						pixels_[0] = swizzled_palette_[source >> 24];
						pixels_[1] = swizzled_palette_[(source >> 16) & 0xff];
						pixels_[2] = swizzled_palette_[(source >> 8) & 0xff];
						pixels_[3] = swizzled_palette_[source & 0xff];
					}

					for(int c = 3; c >= 0; --c) {
						const auto data = sprite_shifters_[c].get();
						if(!data) continue;
						const int base = (c << 2) + 16;

						if(sprites_[(c << 1) + 1].attached) {
							// Left pixel.
							if(data >> 4) {
								pixels_[0] = pixels_[1] = palette_[16 + (data >> 4)];
							}

							// Right pixel.
							if(data & 15) {
								pixels_[2] = pixels_[3] = palette_[16 + (data & 15)];
							}
						} else {
							// Left pixel.
							if(data >> 6) {
								pixels_[0] = pixels_[1] = palette_[base + (data >> 6)];
							}
							if((data >> 4) & 3) {
								pixels_[0] = pixels_[1] = palette_[base + ((data >> 4)&3)];
							}

							// Right pixel.
							if((data >> 2) & 3) {
								pixels_[2] = pixels_[3] = palette_[base + ((data >> 2)&3)];
							}
							if(data & 3) {
								pixels_[2] = pixels_[3] = palette_[base + (data & 3)];
							}
						}
					}

					pixels_ += 4;
				}<commit_msg>Correct sprite ordering within pairs.<commit_after>//
//  Chipset.cpp
//  Clock Signal
//
//  Created by <NAME> on 22/07/2021.
//  Copyright © 2021 <NAME>. All rights reserved.
//

#include "Chipset.hpp"

//#ifndef NDEBUG
//#define NDEBUG
//#endif

#define LOG_PREFIX "[Amiga chipset] "
#include "../../Outputs/Log.hpp"

#include <algorithm>
#include <cassert>

using namespace Amiga;

namespace {

template <typename EnumT, EnumT... T> struct Mask {
	static constexpr uint16_t value = 0;
};

template <typename EnumT, EnumT F, EnumT... T> struct Mask<EnumT, F, T...> {
	static constexpr uint16_t value = uint16_t(F) | Mask<EnumT, T...>::value;
};

template <InterruptFlag... Flags> struct InterruptMask: Mask<InterruptFlag, Flags...> {};
template <DMAFlag... Flags> struct DMAMask: Mask<DMAFlag, Flags...> {};

/// Expands @c source so that b7 is the least-significant bit of the most-significant byte of the result,
/// b6 is the least-significant bit of the next most significant byte, etc. b0 stays in place.
constexpr uint64_t expand_bitplane_byte(uint8_t source) {
	uint64_t result = source;									// 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 abcd efgh
	result = (result | (result << 28)) & 0x0000'000f'0000'000f;	// 0000 0000 0000 0000 0000 0000 0000 abcd 0000 0000 0000 0000 0000 0000 0000 efgh
	result = (result | (result << 14)) & 0x0003'0003'0003'0003;	// 0000 0000 0000 00ab 0000 0000 0000 00cd 0000 0000 0000 00ef 0000 0000 0000 00gh
	result = (result | (result << 7)) & 0x0101'0101'0101'0101;	// 0000 000a 0000 000b 0000 000c 0000 000d 0000 000e 0000 000f 0000 000g 0000 000h
	return result;
}

/// Expands @c source from b15 ... b0 to 000b15 ... 000b0.
constexpr uint64_t expand_sprite_word(uint16_t source) {
	uint64_t result = source;
	result = (result | (result << 24)) & 0x0000'00ff'0000'00ff;
	result = (result | (result << 12)) & 0x000f'000f'000f'000f;
	result = (result | (result << 6)) & 0x0303'0303'0303'0303;
	result = (result | (result << 3)) & 0x1111'1111'1111'1111;
	return result;
}

// A very small selection of test cases.
static_assert(expand_bitplane_byte(0xff) == 0x01'01'01'01'01'01'01'01);
static_assert(expand_bitplane_byte(0x55) == 0x00'01'00'01'00'01'00'01);
static_assert(expand_bitplane_byte(0xaa) == 0x01'00'01'00'01'00'01'00);
static_assert(expand_bitplane_byte(0x00) == 0x00'00'00'00'00'00'00'00);

static_assert(expand_sprite_word(0xffff) == 0x11'11'11'11'11'11'11'11);
static_assert(expand_sprite_word(0x5555) == 0x01'01'01'01'01'01'01'01);
static_assert(expand_sprite_word(0xaaaa) == 0x10'10'10'10'10'10'10'10);
static_assert(expand_sprite_word(0x0000) == 0x00'00'00'00'00'00'00'00);

}

#define DMA_CONSTRUCT *this, reinterpret_cast<uint16_t *>(map.chip_ram.data()), map.chip_ram.size() >> 1

Chipset::Chipset(MemoryMap &map, int input_clock_rate) :
	blitter_(DMA_CONSTRUCT),
	sprites_{
		{DMA_CONSTRUCT},	{DMA_CONSTRUCT},	{DMA_CONSTRUCT},	{DMA_CONSTRUCT},
		{DMA_CONSTRUCT},	{DMA_CONSTRUCT},	{DMA_CONSTRUCT},	{DMA_CONSTRUCT}
	},
	bitplanes_(DMA_CONSTRUCT),
	copper_(DMA_CONSTRUCT),
	audio_(DMA_CONSTRUCT, input_clock_rate),
	crt_(908, 4, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red4Green4Blue4),
	cia_a_handler_(map, disk_controller_, mouse_),
	cia_b_handler_(disk_controller_),
	cia_a(cia_a_handler_),
	cia_b(cia_b_handler_),
	disk_(DMA_CONSTRUCT),
	disk_controller_(Cycles(input_clock_rate), *this, disk_, cia_b),
	keyboard_(cia_a.serial_input) {
	disk_controller_.set_clocking_hint_observer(this);

	joysticks_.emplace_back(new Joystick());
	cia_a_handler_.set_joystick(&joystick(0));

	// Very conservatively crop, to roughly the centre 88% of a frame.
	// This rectange was specifically calibrated around the default Workbench display.
	crt_.set_visible_area(Outputs::Display::Rect(0.05f, 0.055f, 0.88f, 0.88f));
}

#undef DMA_CONSTRUCT

Chipset::Changes Chipset::run_for(HalfCycles length) {
	return run<false>(length);
}

Chipset::Changes Chipset::run_until_cpu_slot() {
	return run<true>();
}

void Chipset::set_cia_interrupts(bool cia_a_interrupt, bool cia_b_interrupt) {
	// TODO: are these really latched, or are they active live?
	// If latched, is it only on a leading edge?
//	interrupt_requests_ &= ~InterruptMask<InterruptFlag::IOPortsAndTimers, InterruptFlag::External>::value;
	interrupt_requests_ |=
		(cia_a_interrupt ? InterruptMask<InterruptFlag::IOPortsAndTimers>::value : 0) |
		(cia_b_interrupt ? InterruptMask<InterruptFlag::External>::value : 0);
	update_interrupts();
}

void Chipset::posit_interrupt(InterruptFlag flag) {
	interrupt_requests_ |= uint16_t(flag);
	update_interrupts();
}

void DMADeviceBase::posit_interrupt(InterruptFlag flag) {
	chipset_.posit_interrupt(flag);
}

template <int cycle> void Chipset::output() {
	// Notes to self on guesses below:
	//
	// Hardware stop is at 0x18;
	// 12/64 * 227 = 42.5625
	//
	// "However, horizontal blanking actually limits the displayable
	// video to 368 low resolution pixel"
	//
	// => 184 windows out of 227 are visible, which concurs.
	//

	// Advance audio.
	audio_.output();

	// Trigger any sprite loads encountered.
	constexpr auto dcycle = cycle << 1;
	for(int c = 0; c < 8; c += 2) {
		if( sprites_[c].visible &&
			dcycle <= sprites_[c].h_start &&
			dcycle+2 > sprites_[c].h_start) {
			sprite_shifters_[c >> 1].load<0>(
				sprites_[c].data[1],
				sprites_[c].data[0],
				sprites_[c].h_start & 1);
		}

		if(	sprites_[c+1].visible &&
			dcycle <= sprites_[c + 1].h_start &&
			dcycle+2 > sprites_[c + 1].h_start) {
			sprite_shifters_[c >> 1].load<1>(
				sprites_[c + 1].data[1],
				sprites_[c + 1].data[0],
				sprites_[c + 1].h_start & 1);
		}
	}

	//
	// Horizontal sync: HC18–HC35;
	// Horizontal blank: HC15–HC53.
	//
	// Beyond that: guesswork.
	//
	// So, from cycle 0:
	//
	//	15 cycles border/pixels;
	//	3 cycles blank;
	//	17 cycles sync;
	//	3 cycles blank;
	//	9 cycles colour burst;
	//	6 cycles blank;
	//	then more border/pixels to end of line.
	//
	// (???)

	constexpr int end_of_pixels	= 15;
	constexpr int blank1		= 3 + end_of_pixels;
	constexpr int sync			= 17 + blank1;
	constexpr int blank2 		= 3 + sync;
	constexpr int burst 		= 9 + blank2;
	constexpr int blank3 		= 6 + burst;
	static_assert(blank3 == 53);

#define LINK(location, action, length)	\
	if(cycle == (location))	{			\
		crt_.action((length) * 4);		\
	}

	if(y_ < vertical_blank_height_) {
		if(!cycle) {
			flush_output();
		}

		// Put three lines of sync at the centre of the vertical blank period.
		// Offset by half a line if interlaced and on an odd frame.
		const int midline = vertical_blank_height_ >> 1;
		if(is_long_field_) {
			if(y_ < midline - 1 || y_ > midline + 2) {
				LINK(blank1, output_blank, blank1);
				LINK(sync, output_sync, sync - blank1);
				LINK(line_length_ - 1, output_blank, line_length_ - 1 - sync);
			} else if(y_ == midline - 1) {
				LINK(113, output_blank, 113);
				LINK(line_length_ - 1, output_sync, line_length_ - 1 - 113);
			} else if(y_ == midline + 2) {
				LINK(113, output_sync, 113);
				LINK(line_length_ - 1, output_blank, line_length_ - 1 - 113);
			} else {
				LINK(blank1, output_sync, blank1);
				LINK(sync, output_blank, sync - blank1);
				LINK(line_length_ - 1, output_sync, line_length_ - 1 - sync);
			}
		} else {
			if(y_ < midline - 1 || y_ > midline + 1) {
				LINK(blank1, output_blank, blank1);
				LINK(sync, output_sync, sync - blank1);
				LINK(line_length_ - 1, output_blank, line_length_ - 1 - sync);
			} else {
				LINK(blank1, output_sync, blank1);
				LINK(sync, output_blank, sync - blank1);
				LINK(line_length_ - 1, output_sync, line_length_ - 1 - sync);
			}
		}
	} else {
		// TODO: incorporate the lowest display window bits elsewhere.
		display_horizontal_ |= cycle == (display_window_start_[0] >> 1);
		display_horizontal_ &= cycle != (display_window_stop_[0] >> 1);

		if(cycle == end_of_pixels) {
			flush_output();
		}

		// Output the correct sequence of blanks, syncs and burst atomically.
		LINK(blank1, output_blank, blank1 - end_of_pixels);
		LINK(sync, output_sync, sync - blank1);
		LINK(blank2, output_blank, blank2 - sync);
		LINK(burst, output_default_colour_burst, burst - blank2);	// TODO: only if colour enabled.
		LINK(blank3, output_blank, blank3 - burst);

		if constexpr (cycle < end_of_pixels || cycle > blank3) {
			const bool is_pixel_display = display_horizontal_ && fetch_vertical_;

			if(
				(is_pixel_display == is_border_) ||
				(is_border_ && border_colour_ != palette_[0])) {
				flush_output();

				is_border_ = !is_pixel_display;
				border_colour_ = palette_[0];
			}

			if(is_pixel_display) {
				if(!pixels_) {
					uint16_t *const new_pixels = reinterpret_cast<uint16_t *>(crt_.begin_data(4 * size_t(line_length_ + end_of_pixels - cycle)));
					if(new_pixels) {
						flush_output();
					}
					pixels_ = new_pixels;
				}

				if(pixels_) {
					// TODO:
					//
					//	(1) dump bit 5 of a six-bitplane playfield if this Chipset doesn't support extra half-bright.
					//	(2) priorities, in general;
					//	(3) collisions;
					//	(4) a much less dense implementation. In particular: map from [1–6]-bit input to present or
					//		absent flags, use those to update collisions and then to index a mask table for compositing.
					//		Which would mean getting through the whole ordeal without branching... as soon as I come up
					//		with a branchless method for priorities. Hmmmm.
					const uint32_t source = bitplane_pixels_.get(is_high_res_);

					// TODO:
					if(dual_playfields_) {
						// Dense: just write both.
						if(even_over_odd_) {
							pixels_[0] = palette_[8 + (source >> 27) & 7];
							pixels_[1] = palette_[8 + (source >> 19) & 7];
							pixels_[2] = palette_[8 + (source >> 11) & 7];
							pixels_[3] = palette_[8 + (source >> 3) & 7];

							if((source >> 24) & 7) pixels_[0] = palette_[(source >> 24) & 7];
							if((source >> 16) & 7) pixels_[1] = palette_[(source >> 16) & 7];
							if((source >> 8) & 7) pixels_[2] = palette_[(source >> 8) & 7];
							if(source & 7) pixels_[3] = palette_[source & 7];
						} else {
							pixels_[0] = palette_[(source >> 24) & 7];
							pixels_[1] = palette_[(source >> 16) & 7];
							pixels_[2] = palette_[(source >> 8) & 7];
							pixels_[3] = palette_[source & 7];

							if((source >> 27) & 7) pixels_[0] = palette_[8 + (source >> 27) & 7];
							if((source >> 19) & 7) pixels_[1] = palette_[8 + (source >> 19) & 7];
							if((source >> 11) & 7) pixels_[2] = palette_[8 + (source >> 11) & 7];
							if((source >> 3) & 7) pixels_[3] = palette_[8 + (source >> 3) & 7];
						}
					} else {
						pixels_[0] = swizzled_palette_[source >> 24];
						pixels_[1] = swizzled_palette_[(source >> 16) & 0xff];
						pixels_[2] = swizzled_palette_[(source >> 8) & 0xff];
						pixels_[3] = swizzled_palette_[source & 0xff];
					}

					for(int c = 3; c >= 0; --c) {
						const auto data = sprite_shifters_[c].get();
						if(!data) continue;
						const int base = (c << 2) + 16;

						if(sprites_[(c << 1) + 1].attached) {
							// Left pixel.
							if(data >> 4) {
								pixels_[0] = pixels_[1] = palette_[16 + (data >> 4)];
							}

							// Right pixel.
							if(data & 15) {
								pixels_[2] = pixels_[3] = palette_[16 + (data & 15)];
							}
						} else {
							// Left pixel.
							if((data >> 4) & 3) {
								pixels_[0] = pixels_[1] = palette_[base + ((data >> 4)&3)];
							}
							if(data >> 6) {
								pixels_[0] = pixels_[1] = palette_[base + (data >> 6)];
							}

							// Right pixel.
							if(data & 3) {
								pixels_[2] = pixels_[3] = palette_[base + (data & 3)];
							}
							if((data >> 2) & 3) {
								pixels_[2] = pixels_[3] = palette_[base + ((data >> 2)&3)];
							}
						}
					}

					pixels_ += 4;
				}
{'fraction_non_alphanumeric': 0.15003403096818105, 'fraction_numerical': 0.07435766547558278, 'mean_word_length': 3.4040839265642564, 'pattern_counts': {'":': 0, '<': 101, '<?xml version=': 0, '>': 213, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '327123', 'n_tokens_mistral': 11117, 'n_tokens_neox': 8541, 'n_words': 2102}