diff --git a/src/Interrupts.md b/src/Interrupts.md
index 07119967..a7ebebc6 100644
--- a/src/Interrupts.md
+++ b/src/Interrupts.md
@@ -8,7 +8,7 @@
- **`ei`**: Enables interrupt handling (that is, `IME := 1`)
- **`di`**: Disables interrupt handling (that is, `IME := 0`)
- **`reti`**: Enables interrupts and returns (same as `ei` immediately followed by `ret`)
-- **When an [interrupt handler](<#Interrupt Handling>) is executed**: Disables interrupts before `call`ing the interrupt handler
+- **When an [interrupt handler](<#Interrupt handling>) is executed**: Disables interrupts before `call`ing the interrupt handler
`IME` is unset (interrupts are disabled) [when the game starts running](<#0100-0103 — Entry point>).
@@ -53,7 +53,7 @@ may still do that in order to manually request (or discard) interrupts.
Just like real interrupts, a manually requested interrupt isn't serviced
unless/until `IME` and `IE` allow it.
-## Interrupt Handling
+## Interrupt handling
1. The `IF` bit corresponding to this interrupt and the `IME` flag are reset by the CPU.
The former "acknowledges" the interrupt, while the latter prevents any further interrupts
@@ -71,7 +71,7 @@ This consumes one last M-cycle.
The entire process [lasts 5 M-cycles](https://gist.github.com/SonoSooS/c0055300670d678b5ae8433e20bea595#user-content-isr-and-nmi).
-## Interrupt Priorities
+## Interrupt priorities
In the following circumstances it is possible that more than one bit in the IF register is set, requesting more than one interrupt at once:
@@ -85,7 +85,7 @@ is serviced first. The priorities follow the order of the bits in the IE
and IF registers: Bit 0 (VBlank) has the highest priority, and Bit 4
(Joypad) has the lowest priority.
-## Nested Interrupts
+## Nested interrupt handling
The CPU automatically disables all the other interrupts by setting IME=0
when it services an interrupt. Usually IME remains zero until the
diff --git a/src/LCDC.md b/src/LCDC.md
index 400180d6..1cc4e915 100644
--- a/src/LCDC.md
+++ b/src/LCDC.md
@@ -57,11 +57,8 @@ This bit controls whether the window shall be displayed or not.
This bit is overridden on DMG by [bit 0](<#LCDC.0 — BG and Window enable/priority>)
if that bit is clear.
-Changing the value of this register mid-frame triggers a more complex behaviour:
-[see further below](<#FF4A–FF4B — WY, WX: Window Y position, X position plus 7>).
-
-Note that on CGB models, setting this bit to 0 then back to 1 mid-frame
-may cause the second write to be ignored. (TODO: test this.)
+Changing the value of this register mid-frame triggers several more complex behaviours:
+[see the corresponding chapter](<#Window mid-frame behavior>).
### LCDC.4 — BG and Window tile data area
diff --git a/src/Memory_Map.md b/src/Memory_Map.md
index 546cb08e..430213e1 100644
--- a/src/Memory_Map.md
+++ b/src/Memory_Map.md
@@ -29,7 +29,7 @@ $FF04 | $FF07 | DMG | [Timer and divider](<#Timer and Divider Reg
$FF0F | | DMG | [Interrupts](<#FF0F — IF: Interrupt flag>)
$FF10 | $FF26 | DMG | [Audio](<#Audio Registers>)
$FF30 | $FF3F | DMG | [Wave pattern](<#FF30–FF3F — Wave pattern RAM>)
-$FF40 | $FF4B | DMG | LCD [Control](<#FF40 — LCDC: LCD control>), [Status](<#FF41 — STAT: LCD status>), [Position, Scrolling](<#LCD Position and Scrolling>), and [Palettes](<#Palettes>)
+$FF40 | $FF4B | DMG | LCD [Control](<#FF40 — LCDC: LCD control>), [Status](<#FF41 — STAT: LCD status>), [Position, Scrolling](<#Viewport position (Scrolling)>), and [Palettes](<#Palettes>)
$FF4C | $FF4D | CGB | [KEY0](<#FF4C — KEY0/SYS (CGB Mode only): CPU mode select>) and [KEY1](<#FF4D — KEY1/SPD (CGB Mode only): Prepare speed switch>)
$FF4F | | CGB | [VRAM Bank Select](<#FF4F — VBK (CGB Mode only): VRAM bank>)
$FF50 | | DMG | [Boot ROM mapping control](<#Power-Up Sequence>)
diff --git a/src/Rendering.md b/src/Rendering.md
index 4f2baa6b..f5748b09 100644
--- a/src/Rendering.md
+++ b/src/Rendering.md
@@ -6,7 +6,7 @@ The entire frame is not drawn atomically; instead, the image is drawn by the **<
A frame consists of 154 **scanlines**; during the first 144, the screen is drawn top to bottom, left to right.
The main implication of this rendering process is the existence of **raster effects**: modifying some rendering parameters in the middle of rendering.
-The most famous raster effect is modifying the [scrolling registers](<#LCD Position and Scrolling>) between scanlines to create a ["wavy" effect](https://gbdev.io/guides/deadcscroll#effects).
+The most famous raster effect is modifying the [scrolling registers](<#Viewport position (Scrolling)>) between scanlines to create a ["wavy" effect](https://gbdev.io/guides/deadcscroll#effects).
A "**dot**" = one 222 Hz (≅ 4.194 MHz) time unit.
Dots remain the same regardless of whether the CPU is in [Double Speed mode](<#FF4D — KEY1/SPD (CGB Mode only): Prepare speed switch>), so there are 4 dots per Normal Speed M-cycle, and 2 per Double Speed M-cycle.
diff --git a/src/SUMMARY.md b/src/SUMMARY.md
index e2eb5caa..790838a5 100644
--- a/src/SUMMARY.md
+++ b/src/SUMMARY.md
@@ -18,6 +18,7 @@
- [Tile Maps](./Tile_Maps.md)
- [OAM](./OAM.md)
- [OAM DMA Transfer](./OAM_DMA_Transfer.md)
+ - [Window](./Window.md)
- [LCD Control](./LCDC.md)
- [LCD Status Registers](./STAT.md)
- [Scrolling](./Scrolling.md)
diff --git a/src/Scrolling.md b/src/Scrolling.md
index 098edeae..5979b3f8 100644
--- a/src/Scrolling.md
+++ b/src/Scrolling.md
@@ -1,5 +1,4 @@
-
-# LCD Position and Scrolling
+# Viewport position (Scrolling)
These registers can be accessed even during Mode 3, but modifications may not take
effect immediately (see further below).
@@ -22,46 +21,8 @@ Example from the homebrew game *Mindy's Hike*:
-## FF4A–FF4B — WY, WX: Window Y position, X position plus 7
-
-These two registers specify the on-screen coordinates of [the Window](#Window)'s top-left pixel.
-
-The Window is visible (if enabled) when both coordinates are in the ranges
-WX=0..166, WY=0..143 respectively. Values WX=7, WY=0 place the Window at the
-top left of the screen, completely covering the background.
-
-:::warning Warning
-
-WX values 0 and 166 are unreliable due to hardware bugs.
-
-If WX is set to 0, the window will "stutter" horizontally when SCX changes
-(depending on SCX % 8).
-
-If WX is set to 166, the window will span the entirety of the following
-scanline.
-
-:::
-
## Mid-frame behavior
-### Scrolling
-
-The scroll registers are re-read on each [tile fetch](<#Get Tile>), except for the low 3 bits of SCX, which are only read at the beginning of the scanline (for the initial shifting of pixels).
-
-All models before the CGB-D read the Y coordinate once for each bitplane (so a very precisely timed SCY write allows "desyncing" them), but CGB-D and later use the same Y coordinate for both no matter what.
-
-### Window
-
-While the Window should work as just mentioned, writing to WX, WY etc. mid-frame shows a more articulated behavior.
-
-For the window to be displayed on a scanline, the following conditions must be met:
-
-- **WY condition was triggered**: i.e. at some point in this frame the value of WY was equal to LY (checked at the start of Mode 2 only)
-- **WX condition was triggered**: i.e. the current X coordinate being rendered + 7 was equal to WX
-- Window enable bit in LCDC is set
-
-If the WY condition has already been triggered and at the start of a row the window enable bit was set,
-then resetting that bit before the WX condition gets triggered on that row yields a nice window glitch pixel where the window would have been activated.
+The scroll registers are re-read on each [tile fetch](<#Get Tile>), except for the low 3 bits of `SCX`, which are only read at the beginning of the scanline (for the initial shifting of pixels).
-The way the Window selects which line of its tilemap to render may be surprising: the Y position is selected by an internal counter, which is reset to 0 during VBlank and **only** incremented when the Window starts being rendered on a given scanline.
-In particular, this means that hiding the Window mid-frame in any way (via either `WX` or `LCDC`, usually to display a status bar at the top *and* bottom of the screen) will also inhibit incrementing that Y-position counter.
+All models before the CGB-D read the Y coordinate once for each bitplane (so a very precisely timed `SCY` write allows "desyncing" them), but CGB-D and later use the same Y coordinate for both no matter what.
diff --git a/src/Tile_Maps.md b/src/Tile_Maps.md
index b72a4435..3dd57373 100644
--- a/src/Tile_Maps.md
+++ b/src/Tile_Maps.md
@@ -106,7 +106,7 @@ for the definition of "Window visibility".)
:::tip Window Internal Line Counter
-The window keeps an internal line counter that's functionally similar to `LY`, and increments alongside it. However, it only gets incremented when the window is visible, as described [here](<#FF4A–FF4B — WY, WX: Window Y position, X position plus 7>).
+The window keeps an internal line counter that's functionally similar to `LY`, and increments alongside it. However, it only gets incremented when the window is visible, as described [here](<#Window rendering criteria>).
This line counter determines what window line is to be rendered on the current scanline.
diff --git a/src/Window.md b/src/Window.md
new file mode 100644
index 00000000..9c12d9a9
--- /dev/null
+++ b/src/Window.md
@@ -0,0 +1,54 @@
+# Window behavior
+
+## FF4A–FF4B — WY, WX: Window Y position, X position plus 7
+
+These two registers specify the on-screen coordinates of [the Window]'s top-left pixel.
+
+The Window is visible (if enabled) when `WX` and `WY` are in the range \[0; 166\] and \[0; 143\] respectively.
+Values `WX`=7, `WY`=0 place the Window at the top left of the screen, completely covering the background.
+
+## Window mid-frame behavior
+
+While the Window should work as just mentioned, writing to `WX`, `WY` etc. mid-frame displays more articulated behavior.
+There are several aspects of the window that respond differently to various mid-frame interactions; the **tl;dr** is this:
+
+- For the least glitchy results, only write to `WX`, `WY`, and `LCDC` during VBlank (possibly in your [VBlank interrupt handler]); if mid-frame writes are required, prefer writing during HBlank.
+- If intending to hide the Window for part of the screen (e.g. to have a status bar at the *top* of the screen instead of the bottom), hide it by setting `WX` to a high value rather than writing to `LCDC`.
+
+### Window rendering criteria
+
+The PPU keeps track of a “**Y condition**” throughout a frame.
+
+- On each VBlank, the *Y condition* is cleared (becomes false).
+- At the beginning of each scanline, if the value of `WY` is equal to [`LY`], the *Y condition* becomes true (and remains so for subsequent scanlines).
+
+:::tip Note
+
+On GBC, clearing the [Window enable bit] in `LCDC` resets the *Y condition*; `WY` must be set to `LY` or greater for the Window to display again in the current frame.
+
+:::
+
+Additionally, the PPU maintains a counter, initialized to 0 at the beginning of each scanline.
+The counter is incremented for each pixel rendered; however, it also increments 7 times before the first pixel is actually rendered (this covers pixels discarded during the initial "fine scroll" adjustment).
+
+When this counter is equal to `WX`, if the *Y condition* is true and the [Window enable bit] is set in `LCDC`, background rendering is reset, beginning anew from the active row of the Window's tilemap.
+The coordinate of the active Window row is then incremented.
+
+- This process can happen more than once per scanline, making the Window's "tilemap Y coordinate" increase more than once in the scanline.
+ (This is demonstrated by the TODO test ROM.)
+
+ However, this requires "disabling" the Window by briefly clearing its enable bit from `LCDC` first.
+- If this process doesn't happen, the Window's "tilemap Y coordinate" does not increase; so, if the Window is hidden (by any means) on a given scanline, the row of pixels rendered the next time it's shown will be the same as if it had not been hidden in the first place, producing a sort of vertical striped stretching:
+
+ 
+- If `WX` is equal to 0, the Window is switched to before the initial "fine scroll" adjustment, causing it to be shifted left by pixels.
+- On monochrome systems, `WX` = 166 (which would normally show a single Window pixel, along the right edge of the screen) exhibits a bug: the Window spans the entire screen, but offset vertically by one scanline.
+- On monochrome systems, if the Window is disabled via `LCDC`, but the other conditions are met *and* it would have started rendering exactly on a BG tile boundary, then where it would have started rendering, a single pixel with ID 0 (i.e. drawn as the first entry in [the BG palette]) is inserted; this offsets the remainder of the scanline.[^star_trek]
+
+[^star_trek]: This was discovered as affecting the game *Star Trek 25th anniversary*; more information and a test ROM are available [in this thread](https://github.com/LIJI32/SameBoy/issues/278#issuecomment-1189712129).
+
+[the Window]: #Window
+[VBlank interrupt handler]: <#INT $40 — VBlank interrupt>
+[Window enable bit]: <#LCDC.5 — Window enable>
+[`LY`]: <#FF44 — LY: LCD Y coordinate \[read-only\]>
+[the BG palette]: <#FF47 — BGP (Non-CGB Mode only): BG palette data>
diff --git a/src/halt.md b/src/halt.md
index 7e330b4b..cb93daa0 100644
--- a/src/halt.md
+++ b/src/halt.md
@@ -7,7 +7,7 @@ and [`IF`](<#FF0F — IF: Interrupt flag>) is non-zero.
Most commonly, [`IME`](<#IME: Interrupt master enable flag \[write only\]>) is
set. In this case, the CPU simply wakes up, and before executing the instruction
-after the `halt`, the [interrupt handler is called](<#Interrupt Handling>)
+after the `halt`, the [interrupt handler is called](<#Interrupt handling>)
normally.
If `IME` is *not* set, there are two distinct cases, depending on whether an
diff --git a/src/othermbc.md b/src/othermbc.md
index 57df0bf6..33604baa 100644
--- a/src/othermbc.md
+++ b/src/othermbc.md
@@ -55,7 +55,7 @@ the mapper consists of a single standard 74 series logic chip, it has
two unusual properties:
First, unlike a usual MBC, it switches the whole 32 KiB ROM area instead
-of just the $4000-$7FFF area. Therefore, if you want to use [the interrupt vectors](<#Interrupt Handling>)
+of just the $4000-$7FFF area. Therefore, if you want to use [the interrupt vectors](<#Interrupt handling>)
with this cart, you should duplicate them across all banks.
Additionally, since the 74LS377's contents can't be guaranteed when powering on,
the ROM header and some code for switching to a known bank should also