Files
AnachronautandClaude Opus 5 17c8da111f A window: a layer that does not scroll
The map moves and this does not, which the map alone cannot express. The
scroll registers move ALL of it, so a score printed into the map slides
away, and one printed into whichever rows the view happens to be showing
jumps a pixel at a time as the fine offset changes.

Port 0x3D is how many rows tall and 0x3E is which row it starts at. Nought
tall is no window, so a cleared screen has none and every program written
before this means what it meant. A start row is a register because a
status bar along the bottom is as common as one along the top.

IT HAS ITS OWN MEMORY, and that is the argument for it. The cheaper design
draws the top rows of the MAP without the scroll applied - no new memory,
one register - and makes those rows part of the playfield's ring, so a
game that scrolls vertically has to route its world around its own
scoreboard for ever. The point of a status bar is that it is not somewhere
in the level. Lunar Porter does not scroll vertically today and will the
moment an orbit is a thing you can reach.

0xC000 in the screen bank, which the map does not reach: it ends at
0xBFFF. Same cells, same tiles, same pages, same schemes. Being in the
screen bank makes it per screen, so flipping the buffer flips the status
bar with it - what a double buffered game wants, and surprising the other
way round.

Drawn over everything, sprites included. A sprite that could cover the
fuel gauge would be a bug in every game that had both.

Tile modes only. In bitmap mode the picture is using that memory, so a
bitmap program pins things to the screen with sprites, which are in screen
coordinates for the same reason.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-09-03 00:19:09 -04:00

494 lines
25 KiB
C

// video.h
// The Voyager's video device.
// Written by Anachronaut
#ifndef VIDEO_H
#define VIDEO_H
#include <stdint.h>
// ---- What this is ----
//
// A tile engine. The CPU writes cell indices and the device expands them into pixels, which
// is the difference between a screen costing 2,000 bytes a frame and 64,000 - and at a
// megahertz that is the difference between a screen and no screen at all.
//
// It follows that COLOUR DEPTH IS FREE AT FRAME TIME. The map is the same size whether the
// tiles behind it are one bit deep or eight, because the depth lives in tile memory, which
// is written once when a program loads and not sixty times a second. So the tiles are eight
// bits: an 8x8 cell is 64 pixels and each one picks independently out of 256 colours, with
// no per-cell limit of the kind that made a Spectrum two and C64 multicolour four.
//
// ---- The device brings memory, in two banks ----
//
// Registered the way the disk's buffer is, so the screen costs a program nothing in Data
// Memory and keeps what is in it between frames. A program blits the region that changed
// and the rest stays as it was, which is the whole reason this is memory rather than a
// window onto a port.
//
// TWO BANKS AND NOT ONE, because the two halves of a screen are written at completely
// different rates. Tiles and colours are an ATLAS: put there when a program loads and then
// left alone. The map is a SCREEN: rewritten as often as anything moves. Sharing one bank
// made them compete for 64K they did not both need, and it had a worse consequence than
// being cramped - a bitmap took the whole bank, so ENTERING BITMAP MODE DESTROYED THE FONT.
// A program could not draw a picture and then say anything about it.
//
// Split, each gets a whole 64K and neither can tread on the other. A bitmap now overwrites
// the map, which is the same memory meaning a different thing in a different mode and is
// exactly what it should overwrite. The tiles behind the text survive it.
//
// Each bank is the same size, and every address below says which of the two it is in.
#define VIDEO_MEMORY_BYTES 0x10000
// ---- In the ATLAS bank ----
//
// Tile memory: 256 tiles of 8x8, one byte a pixel.
#define VIDEO_TILE_BASE 0x0000
#define VIDEO_TILE_BYTES 64
#define VIDEO_TILE_COUNT 256
// ---- Four pages of them ----
//
// A tile number is a byte and a byte reaches 256, which is not many once a font has taken
// 135 of them and a game wants a character, a background and a wall. So two bits of the
// ATTRIBUTE say which page of 256 the number is in - bits that were already there, already
// written on every cell and every sprite, and reserved from the day the attribute was
// defined for exactly this.
//
// Four pages of 16K is 64K, which is the whole atlas, so THE FOURTH PAGE IS THE MEMORY THE
// SPRITE TABLE AND THE PALETTE ARE IN. That is not a hole in the design, it is the same
// answer shared video memory has always given: the atlas is 1024 tiles, and what a program
// spends on sprites and colours comes out of them. A program that wants no sprites may have
// page three for art, and one that wants sprites has 768 tiles and knows why.
//
// The page is per CELL and per SPRITE rather than a mode, so a screen can show tiles from
// all four at once and a program never has to decide which page it is "in".
#define VIDEO_TILE_PAGE_BYTES (VIDEO_TILE_COUNT * VIDEO_TILE_BYTES)
#define VIDEO_TILE_PAGES 4
// ---- The map, one page a row ----
//
// A row is padded to exactly 256 bytes whether the mode uses all of it or not, and that is
// not waste, it is arithmetic. THE MACHINE HAS NO MULTIPLY. On a 40 column screen every
// cursor move would otherwise need row times 40 in software, which is a tax on the most
// common operation in the whole system. At a page a row the address needs no arithmetic at
// all: the row number IS the high byte and the doubled column IS the low byte.
//
// It also frees the geometry from having to be a power of two, which is what lets the
// pixel resolution be whatever looks right.
//
// IN THE SCREEN BANK, and still at 0x4000 rather than at the bottom of a bank it now has to
// itself. Moving it would have been tidier and would have meant changing which bank a
// program registers AND which address it writes to in the same breath - so a screen that
// came out wrong would have had two possible causes. The address costs nothing where it is.
#define VIDEO_MAP_BASE 0x4000
#define VIDEO_MAP_STRIDE 256
#define VIDEO_MAP_ROWS 128
#define VIDEO_MAP_COLUMNS (VIDEO_MAP_STRIDE / 2)
// Two bytes to a cell: which tile, and how to colour it.
#define VIDEO_CELL_BYTES 2
// ---- What the attribute byte means, in both places it appears ----
//
// A map cell's second byte and a sprite's byte 1 are the same thing and are read the same
// way, which is what lets the same art be a background in one place and a moving thing in
// another with nothing rewritten.
#define VIDEO_ATTRIBUTE_SCHEME 0x0F
#define VIDEO_ATTRIBUTE_PAGE 0x30
#define VIDEO_ATTRIBUTE_SHIFT 4
// Bits 6 and 7 are still reserved and should be left at nought.
// ---- A bitmap, over the top of the tiles and the map ----
//
// THE SAME MEMORY MEANING DIFFERENT THINGS IN DIFFERENT MODES, which is what shared video
// memory has always been. There is no room for it to be anywhere else: 320 by 200 at a byte
// a pixel is 64,000 bytes and the whole bank is 65,536, so a bitmap that sat beside the
// tiles rather than on top of them would need a second bank for no reason except tidiness.
//
// What it costs is that the two do not coexist. Going to bitmap mode does not clear the text
// screen; it stops calling it a text screen. Coming back finds the map holding whatever the
// picture put there, which is what taking the screen means.
//
// IN THE SCREEN BANK, so what a picture costs is the map and nothing else. THE TILES AND THE
// PALETTE ARE IN THE OTHER BANK AND SURVIVE IT, which is what lets a program draw a picture
// and then put text back on the screen without reloading the character generator first.
#define VIDEO_BITMAP_BASE 0x0000
#define VIDEO_BITMAP_WIDTH 320
#define VIDEO_BITMAP_HEIGHT 200
// ---- Sprites ----
//
// Things that move without the map moving. A map cell is where it is, and a program that
// wanted something between two cells had to redraw both of them; a sprite is put at a PIXEL
// and the device draws it over whatever is behind.
//
// MADE OF TILES, which is the decision the rest follows from. A sprite is m by n tiles taken
// in reading order from one index, so it needs no pixel format of its own, no second kind of
// memory, and no way for its art to be anything the map could not also show. A 16 by 16
// character is four tiles and a program that wants it in the background too just names the
// same four.
//
// The table is 256 entries of 16 bytes. Sixteen so that entry n begins at n times sixteen,
// which is a shift - the same no-multiply argument that makes a palette entry four bytes and
// a map row a page.
//
// It was eight, and grew when scaling arrived: four bytes for a target size and one for a
// depth would not fit beside what was already there. Grown NOW rather than later, because
// the cost of moving it is a rebuild of the two programs that use it, and the cost of moving
// it once somebody has written a game on top of it is not.
#define VIDEO_SPRITE_BASE 0xC000
#define VIDEO_SPRITE_COUNT 256
#define VIDEO_SPRITE_BYTES 16
// Byte 0 is the top left tile, byte 1 the attribute, which means what a map cell's attribute
// means: its low nibble times sixteen is added to every index in the art.
#define VIDEO_SPRITE_TILE 0
#define VIDEO_SPRITE_ATTRIBUTE 1
// Bytes 2 to 5, low byte first, and SIGNED - a screen is 640 by 400 in the larger mode, so
// neither axis fits in a byte, and a sprite has to be able to sit half off the left or the
// top rather than appearing whole at the edge.
#define VIDEO_SPRITE_X 2
#define VIDEO_SPRITE_Y 4
// Byte 6: how many tiles across in the high nibble, how many down in the low. Fifteen each
// way, so 120 by 120 pixels.
//
// A SPRITE OF NO WIDTH OR NO HEIGHT DRAWS NOTHING, and that is the off switch. It saves a
// flag, it is per sprite rather than a global the whole table shares, and it means the table
// is already off when the machine starts, since the atlas wakes up cleared.
//
// Deliberately the OPPOSITE of what a length of zero means to the memory controller, where
// it means the whole 64K. The reason is the same both times: moving no bytes is a useless
// thing to ask for, so zero was free to mean something else there - and drawing no sprite is
// the commonest state in this table, so zero has to mean nothing here.
#define VIDEO_SPRITE_SIZE 6
// Byte 7.
#define VIDEO_SPRITE_FLAGS 7
#define VIDEO_SPRITE_HFLIP 0x01
#define VIDEO_SPRITE_VFLIP 0x02
// Drawn only where the background had nothing, so a thing can walk behind a pillar. See
// below for what "nothing" means.
#define VIDEO_SPRITE_BEHIND 0x04
// ---- How big to draw it, which is not the same as how big it is ----
//
// Bytes 8 to 11: a target width and a target height in PIXELS, low byte first. The device
// stretches the m by n tiles to fill that, so a program says how big the thing should look
// and never works out a ratio.
//
// A TARGET IN PIXELS RATHER THAN A MULTIPLIER, which is the whole of why this is usable on
// a machine with no divide. A billboard at distance d wants to be k/d pixels tall, and that
// is a number the program has anyway - out of a lookup table, most likely. A multiplier
// would have to be a fixed point fraction, computed by dividing, which is the one thing
// this CPU cannot do.
//
// NOUGHT MEANS NATURAL SIZE, eight times the tile count on that axis. So every sprite
// written before scaling existed still means what it meant, and the common case - a thing
// drawn at the size it was drawn at - costs nothing to say.
#define VIDEO_SPRITE_WIDTH 8
#define VIDEO_SPRITE_HEIGHT 10
// ---- And how far away it is ----
//
// Byte 12. Nought means no depth test at all, which is what every ordinary sprite wants and
// what a cleared table already says.
//
// Otherwise it is compared against the DEPTH BUFFER below, one column at a time, and the
// sprite draws only in the columns it is in front of. That is the thing table order cannot
// do: a billboard can be nearer than the wall in one column and further in the next, and no
// amount of sorting the table expresses that.
#define VIDEO_SPRITE_DEPTH 12
// Bytes 13 to 15 are reserved and should be left at nought.
// ---- The depth buffer ----
//
// One byte a screen column, written by the program and read by the device. A wall pass says
// how far away the thing it drew in each column was; a sprite with a depth says how far away
// it is; and a sprite pixel is drawn only where it is nearer.
//
// NOUGHT IN A COLUMN MEANS NOTHING IS THERE, so a program that never writes this has a
// buffer of noughts and every sprite draws - which is the behaviour there was before it
// existed. It is not cleared between frames: it belongs to the program, and a program that
// draws walls rewrites all of it every frame anyway.
//
// It is in the atlas because that is where the things a program sets up live, and because
// the alternative is a port and this is 640 bytes.
#define VIDEO_DEPTH_BASE 0xD000
// ---- What a sprite does not cover ----
//
// A PIXEL OF ZERO IS NOT DRAWN. Without that every sprite is a rectangle, and there is no
// other candidate: the font already uses index 0 for paper, so it is the value art in this
// machine has always left empty.
//
// Tested BEFORE the attribute is added, so it is a property of the art and not of the colour
// scheme it is being shown in. A sprite drawn in indices 1 to 15 is transparent in the same
// places in all sixteen schemes, which is the whole point of the additive nibble.
//
// The same rule read the other way is what "behind" means: a sprite marked behind draws only
// where the BACKGROUND pixel was zero. One rule, applied to whichever layer is in front.
//
// ---- How many at once ----
//
// All of them. Every entry in the table is drawn every frame, so sprites cannot flicker.
// Real machines dropped them per scanline because they had a fixed number of shift registers
// and a fixed time to fill them; this has a loop. The limit is how many entries there are,
// which is a constant a program can count on rather than a property of what it happens to be
// drawing this frame.
//
// Where they overlap, THE LOWER NUMBER IS IN FRONT.
// ---- The palette ----
//
// Four bytes an entry rather than three, for the same reason a map row is a page: entry n
// begins at n times four, which is a shift. Three would need a multiply the machine does
// not have. The fourth byte is unused and reads as whatever was put there.
//
// At the TOP of the ATLAS bank, clear of everything else, because it is the one thing that
// has to mean the same in every mode - a bitmap needs colours as much as a tile does, and it
// is written when a program loads rather than per frame, which is what the atlas is for.
//
// Being out of the screen bank is what leaves a bitmap the WHOLE of one: 64,000 bytes of
// picture in 65,536, with nothing it has to dodge.
#define VIDEO_PALETTE_BASE 0xFC00
#define VIDEO_PALETTE_BYTES 4
#define VIDEO_PALETTE_SIZE 256
// ---- The window: a layer that does not scroll ----
//
// A status bar over a playfield. The map moves and this does not, which the map alone cannot
// express: the scroll registers move ALL of it, so a score printed into the map is a score
// that slides away, and one printed into the rows the view happens to be showing is one that
// jumps a pixel at a time as the fine offset changes.
//
// ITS OWN MEMORY, and that is the whole argument. The first design drew the top rows of the
// MAP without the scroll applied, which needs no new memory and costs one register - and
// makes those rows part of the playfield's ring, so a game that scrolls vertically has to
// route its world around its own scoreboard for ever. The whole point of a status bar is that
// it is not somewhere in the level.
//
// 0xC000 to 0xFFFF in the screen bank, which the map does not reach: it ends at 0xBFFF. Sixty
// four rows of the same 256 byte cells, so a window row is a page like a map row is, and a
// cell in it means exactly what a cell in the map means - same tiles, same pages, same
// colour schemes.
//
// PER SCREEN, because it is in the screen bank: flipping to the other buffer flips the status
// bar with it, which is what a double buffered game wants and would be surprising the other
// way round.
#define VIDEO_WINDOW_BASE 0xC000
#define VIDEO_WINDOW_ROWS 64
// How many screen rows tall it is, and which screen row it starts at. NOUGHT IS OFF, so a
// machine that wakes up with a cleared screen has no window and every program written before
// this one means what it meant.
//
// A start row is worth a register because a status bar at the bottom is as common as one at
// the top, and working it out from the screen height is a thing every program would have to
// do again.
#define VIDEO_WINDOW_HEIGHT 0x3D
#define VIDEO_WINDOW_AT 0x3E
// ---- Modes ----
//
// Both are 8x8 cells over the same engine; only how many of them differ. The pixel count
// costs the CPU nothing, because it only ever writes the map - which is why the larger mode
// is affordable at all.
#define VIDEO_MODE_40x25 0
#define VIDEO_MODE_80x50 1
#define VIDEO_MODE_BITMAP 2
#define VIDEO_MODE_COUNT 3
#define VIDEO_CELL_PIXELS 8
#define VIDEO_MAX_WIDTH (80 * VIDEO_CELL_PIXELS)
#define VIDEO_MAX_HEIGHT (50 * VIDEO_CELL_PIXELS)
// How many characters across and down the screen is, and ZERO IN BITMAP MODE, where there is
// no such thing. The console asks, and a console told there are no columns has nowhere to
// put a glyph and does not try.
int videoTextRows(void);
// ---- Ports ----
//
// Sixteen, like the controller, and it interrupts on its base the way the disk established.
// Nothing interrupts yet; the frame interrupt is the next rung.
#define VIDEO_STATUS 0x30
#define VIDEO_MODE 0x31
#define VIDEO_COLUMNS 0x32
#define VIDEO_ROWS 0x33
#define VIDEO_SCROLL 0x34
#define VIDEO_CONTROL 0x35
// ---- The other three quarters of scrolling ----
//
// 0x34 moves the view a whole cell at a time and only downwards, which is a scrolling text
// screen and not a scrolling picture. These are the rest of it: a column origin so the map
// can be wider than the screen as well as taller, and a pixel remainder for each axis so the
// step can be one pixel rather than eight.
//
// COARSE AND FINE DO NOT CARRY INTO EACH OTHER. Fine is the low three bits of what is
// written and nothing else, so a program that scrolls past a cell edge advances the coarse
// register itself. That is what the machines this one is pretending to be did, it keeps each
// register meaning exactly one thing, and it means a program always knows where it is
// without reading anything back off the screen.
#define VIDEO_SCROLL_COLUMN 0x36
#define VIDEO_FINE_X 0x37
#define VIDEO_FINE_Y 0x38
// ---- The character generator, which is a chip and not a memory that remembers ----
//
// The font and the sixteen colour schemes used to be WRITTEN INTO VIDEO RAM at reset, and
// that was the one piece of magic left in this device: RAM does not wake up with anything
// in it. It looked harmless until something needed the font BACK - a program that redefines
// a tile has overwritten a glyph, and there was nowhere to get it from, because the only
// copy was the one that had just been drawn over.
//
// So the device has a ROM, the way the machines this one is pretending to be really did,
// and the copy into RAM is a thing it DOES rather than a state it mysteriously starts in.
// Reset performs it, and a program can ask for it again.
//
// THE RAM IS STILL RAM. A program may overwrite every glyph and every colour, and should be
// able to: that is what makes a tile engine a tile engine rather than a text display. What
// has changed is that doing so is no longer a one way door.
//
// A SYSTEM THAT WANTS ITS OWN FONT still loads one over the top. This is the floor, not the
// policy - it is what makes a machine with no disk able to say so, and what lets a program
// with no system behind it put readable text on a screen.
#define VIDEO_COMMAND 0x39
// Copy the 135 glyphs back into the tiles they live in, leaving every other tile alone.
#define VIDEO_COMMAND_FONT 0x01
// Copy the sixteen ink and paper pairs back, leaving the rest of the palette alone.
#define VIDEO_COMMAND_PALETTE 0x02
// ---- The ports that own banks ----
//
// A bank is registered by naming THE PORT THAT OWNS IT, which the controller settled long
// before the screen had more than one. So a device with several banks needs several ports
// that own memory, and needs no new mechanism at all.
//
// The base port keeps the atlas rather than a screen because tiles have been at 0x0000 since
// there was a screen at all, and whichever way round that went, one of the two meanings had
// to move. Nothing is READ OR WRITTEN at any of these - they are names for banks, and the
// registry is where a program finds out which of them bring one.
#define VIDEO_SCREEN0 0x3A
#define VIDEO_SCREEN1 0x3B
// ---- Two screens, and only one register to say which ----
//
// A back buffer is a whole screen's worth of map written where nobody can see it, and then
// shown all at once. It is what stops a picture being seen half finished - a game that moves
// forty sprites and rewrites the map underneath them is not finished being wrong until the
// last of them has been put right.
//
// THE DEVICE ONLY NEEDS TO KNOW WHICH IS DISPLAYED. Real machines needed a second register
// saying which one the CPU's window pointed at; there is no window here, because a program
// reaches a bank through the memory controller BY ITS NUMBER. Writing to the one that is not
// being shown is a matter of naming its bank, and the screen never has to be told.
//
// A FLIP CANNOT TEAR. A frame is drawn from one bank in one go, so a flip either happened
// before that frame or it happens before the next one; there is no state of having flipped
// halfway. That is worth saying because it is the thing the hardware this imitates had to
// work for, with an interrupt and a register written in the few lines between frames.
#define VIDEO_DISPLAY 0x3C
#define VIDEO_SCREEN_COUNT 2
// Eight pixels to a cell, so three bits say where inside one the view begins.
#define VIDEO_FINE_MASK 0x07
// ---- The frame ----
//
// A screen finishes drawing sixty times a second and then has a moment before it starts
// again, and that moment is the one safe time to change what it is drawing. It is also the
// only regular beat this machine has: there is no clock, and every program that wanted to
// happen at a certain speed has until now counted instructions and hoped.
//
// Sixty a second at a megahertz. On the MACHINE'S clock rather than the host's, so a program
// runs the same number of frames in the same number of cycles however fast anything really
// went - which is what makes a frame something a test can count.
#define VIDEO_FRAME_CYCLES 16667
// Set when a frame has gone by, and cleared by reading the status port. A program with no
// handler installed can wait on this instead, the way a program can poll the console rather
// than being interrupted by it.
#define VIDEO_STATUS_FRAME 0x01
// Whether the screen is set to interrupt, so that a program can ask what it asked for.
#define VIDEO_STATUS_INTERRUPT 0x02
// Asks to be interrupted at each frame, on hardware vector 0x30. OFF WHEN THE MACHINE
// STARTS, because an interrupt with nothing installed to catch it is a fault, and a machine
// that began interrupting the moment it was switched on would take any program that had not
// thought about frames down with it.
#define VIDEO_CONTROL_FRAME 0x01
void videoReset(void);
// ---- What the console needs to draw with ----
//
// The Voyager's console is a display controller: it takes a byte stream and puts glyphs on
// the screen, the way a video terminal's character generator does. That is a real kind of
// chip rather than an emulator convenience - but it does mean the console and a program
// drawing graphics are writing one screen, because a machine has one screen.
//
// The font is expanded into tile memory at reset rather than stored expanded: 1,088 bytes
// of one-bit rows against 16 kilobytes of tiles.
// Copies the character generator ROM into the tiles the console draws from, and the default
// schemes into the palette entries they live in. Reset does both; the Command port is how a
// program asks for either afterwards.
//
// NEITHER CLEARS WHAT IT DOES NOT OWN. The font writes glyphs 0 to 134 and stops, so a tile
// a program defined above them survives; the palette writes the two entries of each of the
// sixteen schemes and stops, so a program's own colours in between survive. Asking for the
// font back should not cost a program the tile it was drawing with.
void videoLoadFont(void);
void videoLoadPalette(void);
// ---- The cursor ----
//
// Drawn by the device rather than by whatever is presenting, because on a machine with a
// screen the cursor IS a hardware feature - a display controller blinks it from a counter,
// and one drawn by the window would not be in a picture the machine saved.
//
// It blinks on the machine's own clock, so the phase is a pure function of the cycle count
// and a screen saved at a given cycle is the same screen every time.
#define VIDEO_BLINK_CYCLES 500000
void videoSetCursor(int row, int column, int visible);
// The machine's clock, for anything that has to know time has passed.
void videoTick(unsigned long now);
int videoColumns(void);
int videoRows(void);
// Screen coordinates, not map coordinates. The ring is the device's business, and a caller
// that had to know where the origin was would have to be told every time it moved.
void videoPutCell(int screenRow, int column, uint8_t tile, uint8_t attribute);
// Moves the origin on by a row and clears the one that has just come into view at the
// bottom - which is holding whatever was there 128 rows ago, since the map is a ring.
void videoScrollUp(void);
// The memory behind one of the device's memory-owning ports: VIDEO_STATUS for the atlas,
// VIDEO_SCREEN0 and VIDEO_SCREEN1 for the two screens. NULL for any other port, because the
// rest of the block owns no memory and registering a bank onto one would put a number in the
// table that leads nowhere.
uint8_t *videoMemory(uint8_t port, uint32_t *capacity);
uint8_t videoWrite(uint8_t value, uint8_t port);
uint8_t videoRead(uint8_t port);
// Turns what is in video memory into pixels. A pure function of that memory, so the same
// contents give the same picture with nobody watching - which is what lets the suite check
// a screen on a machine that has no display.
void videoRender(void);
// The pixels the last render produced, three bytes each, red then green then blue.
const uint8_t *videoPixels(int *width, int *height);
// Renders and writes a binary PPM. Returns 0 if it worked.
int videoWriteImage(const char *path);
#endif // VIDEO_H