kinescope

package module
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 4, 2026 License: MIT Imports: 8 Imported by: 0

README

kinescope

The picture of an old tube TV for Go games: scanlines, a phosphor mask, glow, afterglow, a bulging glass — and the moods of a worn set: drifting reception, torn lines, a shiver when the channel changes. Drawn with Ebitengine.

Try the lab → — build a setup section by section (effects, sources, drives, schedules) or start from a preset, drop your own screenshot on it, tell the TV the game's facts, then share it as a link or copy it as Go: a MyTV() function or a whole game.

Scope: the screen only. kinescope draws what the tube's glass shows — the picture and its flaws. Whatever is around it (a monitor case, a bezel, a room) is the game's business; outside the picture kinescope leaves black.

Used in

  • tnk9x — tanks from the 90s the way we remember them, with real-time 2D lighting; the Gorizont set.
  • modern-robinson — a remake of the 1999 quest «Новый Робинзон»; the Rubin set.

Quick start

import (
	"github.com/shpaker/kinescope"
	"github.com/shpaker/kinescope/ebitengine"
)

tv, err := kinescope.NewTV(kinescope.Gorizont())
renderer, err := ebitengine.NewRenderer()

// Every tick
tv.Update(1.0 / 60)

// In DrawFinalScreen: the frame where geoM puts it, black around it
func (g *Game) DrawFinalScreen(screen ebiten.FinalScreen, offscreen *ebiten.Image, geoM ebiten.GeoM) {
	_ = g.renderer.Draw(screen, offscreen, g.tv, geoM)
}

Any scale works: a whole number keeps pixel art crisp, a fraction is smoothed by Softness.

How it works

A TV is a set at work. It is built from a Setup — plain data:

  • Effects — the look of the set. Each effect is a struct of numbers: &kinescope.Scanlines{Depth: 0.3, MinScale: 3}. Use one or any mix of them.
  • Sources — values that change in time, from 0 to 1: a Signal the game sets (a shake), a Drift that wanders at random (the reception), a Wave.
  • Drives — a source moving a param: {From: "shake", To: kinescope.TearStrength, Weight: 1}.
  • Schedules — episodes played now and then: Every{Mean: 90, Spread: 30, Episodes: ...}. An episode is a short fault with an envelope: Jitter(), Ripple(), RollOver(), SnowBurst(), Degaussing().

The game talks to the TV in facts — a signal's level, an episode to play — and never holds the effects themselves:

setup := kinescope.Gorizont()
setup.Sources = map[string]kinescope.Source{
	"shake":     kinescope.Signal{},
	"reception": kinescope.Drift{Period: 20},
}
setup.Drives = []kinescope.Drive{
	{From: "shake", To: kinescope.TearStrength, Weight: 1},
	{From: "reception", To: kinescope.GrainStrength, Weight: 0.08},
	{From: "reception", To: kinescope.Rate("glitches"), Weight: 2}, // more glitches on poor reception
}
setup.Schedules = map[string]kinescope.Every{
	"glitches": {Mean: 90, Spread: 30, Episodes: []kinescope.Episode{kinescope.Jitter()}},
}
tv, err := kinescope.NewTV(setup)

shake, err := tv.Signal("shake") // once; a typo is an error here, not a dead knob in play
shake.Set(level)                 // whenever the game likes
reception := tv.SourceValue("reception") // a source's value now, for a meter
tv.Play(kinescope.Ripple())      // on a change of scene
tv.Hold(dragging)                // scheduled glitches wait

tv.PowerOn()                     // the picture grows from a dot
tv.PowerOff()                    // and folds away on quit…
if tv.Dark() { /* …then quit */ }

Modulation adds to a param's base and never changes it, so tv.Values() is always what the player set, even in the middle of a glitch.

Player settings

Changing a value is free: values are shader uniforms, sent every frame anyway. Changing the set of effects (tv.SetEffects) composes and compiles a new shader, which can stall a frame — do it in a menu, not in play. To let a player switch an effect off, keep it in the set at strength 0.

tv.Values() and tv.Apply() save and restore the player's values; kinescope.Blend(t, a, b) turns one knob from one preset to another.

Pointer and touch

The glass bends the picture. tv.Map(x, y, w, h) is the point of the frame the TV shows at a point — hit what the player sees.

Effects

Effect Stage What it does
Afterglow prepass bright moving things leave a short, cold trail
Power geometry the picture grows from a dot and folds away (PowerOn, PowerOff)
Curvature geometry the glass bulges the picture
Degauss geometry the picture wobbles as the coil shakes the mask
Roll geometry the picture slips a whole height, the blanking bar with it
Tear geometry lines shift sideways: a running wave and torn bands
Softness read the beam spreads between neighboring pixels
Convergence sample red and blue guns out of register
Glow light light bleeds around bright areas
Glass light black is never quite black; a reflection in the corner
Scanlines beam dark gaps between the beam's lines
Interlace beam the two fields of lines take turns, a shimmer
ApertureMask mask the phosphor's red, green and blue stripes
SlotMask mask the stripes of a shadow-mask set, cut by slots
Grain post fine noise over the picture
Snow post the picture drowns in snow
Flicker post the picture's brightness trembles
Hum post a bar, light or dark, rolls down the picture
Vignette post the corners dim
Corners frame the picture's corners round off

Presets are named after Soviet sets:

  • Gorizont() — a well-kept Minsk color set of the eighties: crisp scanlines, a faint aperture mask, glowing highlights, a short afterglow.
  • Rubin() — a well-worn Moscow set: a soft beam through a slot mask, a dark hum, plenty of grain and a glitch every minute or two.

Under the hood

kinescope            the core: effects as data, TV, modulation — no engine
kinescope/ebitengine the Ebitengine backend: Renderer, Kage shaders
cmd/kinescope-lab    the lab, for the browser

Dependencies point inwards only, checked by depguard: the core knows no engine, the backend knows no lab. The backend composes one picture shader per set of effects from a Kage fragment per effect, in the order light goes through a real set (geometry → read → sample → light → beam → mask → post → frame), plus a prepass for glow and afterglow. Effects a TV does not have cost nothing.

Tests check that every effect compiles alone and with all the others, and that the shader's geometry agrees with TV.Map pixel for pixel.

Lab

The lab is a web page: shpaker.github.io/kinescope. Its sections are a Setup's; the Go panel beside them is the same setup as code to paste into a game. The address keeps the setup and the picture, so a link shares both. A link can be written by hand or by an agent, as JSON: https://shpaker.github.io/kinescope/#j={"preset":"Rubin"}; the format and everything a setup can be made of are in llms.txt. To run it locally:

just serve-web   # http://localhost:8080

License

MIT

Documentation

Overview

Package kinescope draws the picture of an old tube TV for Go games: scanlines, a phosphor mask, glow, a bulging glass — and the moods of a worn set: drifting reception, torn lines, a shiver when the channel changes.

A Setup is a whole TV as data; NewTV builds a TV from it, and the game tells the TV its facts: time with TV.Update, signal levels, episodes. The ebitengine package draws a frame through the TV.

tv, err := kinescope.NewTV(kinescope.Gorizont())
renderer, err := ebitengine.NewRenderer()

// Every tick
tv.Update(1.0 / 60)

// In DrawFinalScreen
_ = renderer.Draw(screen, offscreen, tv, geoM)

The lab

The lab, https://shpaker.github.io/kinescope/, shows a setup on a picture and writes it out as Go. A link opens it on a setup written as JSON, by hand or by an agent:

https://shpaker.github.io/kinescope/#j={"preset":"Rubin"}

The format and everything a setup can be made of, effects and their params with ranges, are in https://shpaker.github.io/kinescope/llms.txt.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Blend

func Blend(t float32, a, b map[ParamKey]float32) map[ParamKey]float32

Blend mixes two sets of values: t = 0 gives a, t = 1 gives b. A key found in only one of them keeps its value. Use it for a single knob a player turns from one preset to another:

tv.Apply(kinescope.Blend(knob, soft.Values(), worn.Values()))

Types

type Afterglow

type Afterglow struct {
	Decay float32 // the share of the glow kept from one frame to the next
}

Afterglow keeps the phosphor glowing after the beam has passed: bright moving things leave a short trail, the blue one longest.

func NewAfterglow

func NewAfterglow() *Afterglow

NewAfterglow is a short trail.

func (*Afterglow) Name

func (*Afterglow) Name() string

func (*Afterglow) Params

func (e *Afterglow) Params() []Param

func (*Afterglow) Stage

func (*Afterglow) Stage() Stage

type ApertureMask

type ApertureMask struct {
	Strength float32 // how much the stripes show
	// MinScale is the screen pixels per frame pixel the mask needs: below
	// it the mask fades out.
	MinScale float32
}

ApertureMask is the phosphor's red, green and blue stripes, one triad per three screen pixels.

func NewApertureMask

func NewApertureMask() *ApertureMask

NewApertureMask is a faint mask on a big enough screen.

func (*ApertureMask) Name

func (*ApertureMask) Name() string

func (*ApertureMask) Params

func (e *ApertureMask) Params() []Param

func (*ApertureMask) Stage

func (*ApertureMask) Stage() Stage

type Convergence

type Convergence struct {
	Amount float32 // the parting at the edges, outwards, in frame pixels
	// Offset is the sideways drift, red to the right and blue to the left:
	// half of it in the middle, all of it at the edges, in frame pixels.
	Offset float32
}

Convergence puts the red and blue guns out of register: their beams part towards the edges of the screen, and on a worn set drift apart sideways all over it.

func NewConvergence

func NewConvergence() *Convergence

NewConvergence is a set a little out of register.

func (*Convergence) Name

func (*Convergence) Name() string

func (*Convergence) Params

func (e *Convergence) Params() []Param

func (*Convergence) Stage

func (*Convergence) Stage() Stage

type Corners

type Corners struct {
	Radius float32 // in frame pixels
}

Corners round the picture's corners to the tube's outline.

func NewCorners

func NewCorners() *Corners

NewCorners are softly rounded corners.

func (*Corners) Name

func (*Corners) Name() string

func (*Corners) Params

func (e *Corners) Params() []Param

func (*Corners) Stage

func (*Corners) Stage() Stage

type Curvature

type Curvature struct {
	X float32 // bulge across, the stronger the nearer the top and bottom
	Y float32 // bulge down, the stronger the nearer the sides
}

Curvature bulges the glass: the picture bows out towards the tube's corners.

func NewCurvature

func NewCurvature() *Curvature

NewCurvature is a slightly bulging tube.

func (*Curvature) Name

func (*Curvature) Name() string

func (*Curvature) Params

func (e *Curvature) Params() []Param

func (*Curvature) Stage

func (*Curvature) Stage() Stage

type Degauss added in v0.2.0

type Degauss struct {
	Strength float32
}

Degauss is the degaussing coil shaking the mask: the picture wobbles and settles. Its strength rests at zero and is driven by the Degauss episode.

func NewDegauss added in v0.2.0

func NewDegauss() *Degauss

NewDegauss is a coil at rest.

func (*Degauss) Name added in v0.2.0

func (*Degauss) Name() string

func (*Degauss) Params added in v0.2.0

func (e *Degauss) Params() []Param

func (*Degauss) Stage added in v0.2.0

func (*Degauss) Stage() Stage

type Drift

type Drift struct {
	Period float32 // seconds between one random level and the next
}

Drift wanders smoothly and at random between 0 and 1, like the reception of a far station: now clearer, now worse.

type Drive

type Drive struct {
	From   string   // a source's name in Setup.Sources
	To     ParamKey // the param it moves
	Weight float32  // how far the param moves at the source's full value
}

Drive lets a source move a param: Weight times the source's value is added to the param's base. A drive to a param of an effect the TV does not have does nothing.

type Effect

type Effect interface {
	// Name is the effect's name, the first part of its params' keys.
	Name() string
	// Stage is where in the picture the effect works.
	Stage() Stage
	// Params lists the effect's tunable numbers.
	Params() []Param
	// contains filtered or unexported methods
}

Effect is one part of the look of a set: plain data, its parameters' base values. A TV owns its effects; what changes them over time — signals, drifts, glitches — is set up beside them, in Setup.

The set of effects is closed: every effect is defined in this package, and every backend knows how to draw each of them.

func Effects

func Effects() []Effect

Effects returns a fresh copy of every effect there is, set to its defaults, in stage order. A lab lists them; a backend checks it can draw each one.

type Envelope

type Envelope struct {
	Attack  float32
	Hold    float32
	Release float32
}

Envelope is the shape of an episode in time, in seconds: the rise to its peak, the stay there and the fall back.

type Episode

type Episode struct {
	Name     string
	Envelope Envelope
	Targets  []Target
}

Episode is a fault of the set played over a moment: its targets move by their weights, shaped by the envelope. Play one with TV.Play or let a schedule (Every) play it.

func Degaussing added in v0.2.0

func Degaussing() Episode

Degaussing is the coil shaking the mask: the picture wobbles and settles.

func Jitter

func Jitter() Episode

Jitter tears the lines for a moment, as when the horizontal sync slips.

func Ripple

func Ripple() Episode

Ripple is the signal shivering as a channel is switched: the lines tear a little, the grain thickens and snow flickers, all dying out.

func RollOver added in v0.2.0

func RollOver() Episode

RollOver slips the picture a whole height down, as when the vertical sync is lost, the blanking bar crossing with it.

func SnowBurst added in v0.2.0

func SnowBurst() Episode

SnowBurst drowns the picture in snow for a moment.

type Every

type Every struct {
	Mean     float32
	Spread   float32
	Episodes []Episode
}

Every plays one of its episodes now and then. At rate 1 the waits between them are random, from Mean-Spread to Mean+Spread seconds; the schedule's rate (Rate(name)) speeds them up or slows them down, and a drive can move it like any other param.

type Flicker

type Flicker struct {
	Strength  float32 // the share of brightness the picture trembles by
	Frequency float32 // trembles per second
}

Flicker trembles the whole picture's brightness.

func NewFlicker

func NewFlicker() *Flicker

NewFlicker is a barely seen tremble.

func (*Flicker) Name

func (*Flicker) Name() string

func (*Flicker) Params

func (e *Flicker) Params() []Param

func (*Flicker) Stage

func (*Flicker) Stage() Stage

type Glass

type Glass struct {
	Level float32 // the glass's own light on black
}

Glass is the tube's glass catching the room's light: black is never quite black, and a soft reflection sits in the upper left corner.

func NewGlass

func NewGlass() *Glass

NewGlass is a faint glass, enough to tell the tube from the black around it in a dark scene.

func (*Glass) Name

func (*Glass) Name() string

func (*Glass) Params

func (e *Glass) Params() []Param

func (*Glass) Stage

func (*Glass) Stage() Stage

type Glow

type Glow struct {
	Threshold float32 // the brightness the glow starts at
	Strength  float32 // how much of the glow is added
	Radius    float32 // how far it spreads, in frame pixels
}

Glow bleeds the light of bright areas around them.

func NewGlow

func NewGlow() *Glow

NewGlow is a soft glow around the brightest areas.

func (*Glow) Name

func (*Glow) Name() string

func (*Glow) Params

func (e *Glow) Params() []Param

func (*Glow) Stage

func (*Glow) Stage() Stage

type Grain

type Grain struct {
	Strength float32
}

Grain is the signal's snow: fine noise, new every frame, seen on black too.

func NewGrain

func NewGrain() *Grain

NewGrain is a faint grain.

func (*Grain) Name

func (*Grain) Name() string

func (*Grain) Params

func (e *Grain) Params() []Param

func (*Grain) Stage

func (*Grain) Stage() Stage

type Hum

type Hum struct {
	Strength float32 // how much brighter the bar is; below zero, darker
	Period   float32 // seconds the bar takes to cross the picture
	Width    float32 // the bar's width, in the picture's heights
}

Hum is the mains hum: a bar rolling slowly down the picture, light or dark.

func NewHum

func NewHum() *Hum

NewHum is a faint thin light bar crossing every eight seconds.

func (*Hum) Name

func (*Hum) Name() string

func (*Hum) Params

func (e *Hum) Params() []Param

func (*Hum) Stage

func (*Hum) Stage() Stage

type Interlace added in v0.2.0

type Interlace struct {
	Strength float32
	// MinScale is the screen pixels per frame pixel the fields need: below
	// it they fade out.
	MinScale float32
}

Interlace draws the lines in two fields taking turns, frame by frame: the lines of the field not drawn fade, and the picture shimmers.

func NewInterlace added in v0.2.0

func NewInterlace() *Interlace

NewInterlace is a faint shimmer.

func (*Interlace) Name added in v0.2.0

func (*Interlace) Name() string

func (*Interlace) Params added in v0.2.0

func (e *Interlace) Params() []Param

func (*Interlace) Stage added in v0.2.0

func (*Interlace) Stage() Stage

type Level

type Level struct {
	// contains filtered or unexported fields
}

Level is the live level of a signal. The game sets it whenever it likes — in Update or in Draw — and the TV's values follow at once.

func (*Level) Set

func (l *Level) Set(level float32)

Set sets the signal's level, kept within 0..1.

type Param

type Param struct {
	Key   ParamKey
	Min   float32
	Max   float32
	Value *float32
}

Param is one tunable number: its key, its range and where its base value lives. Value points into the effect (or the TV, for a schedule's rate), so writing through it changes the base the modulation is added to.

type ParamKey

type ParamKey string

ParamKey names a tunable number of a TV: a parameter of one of its effects ("tear.strength") or the rate of one of its schedules ("rate.glitches"). Values, Apply, drives and episode targets all speak these keys.

const (
	PowerWidth      ParamKey = "power.width"
	PowerHeight     ParamKey = "power.height"
	PowerFlash      ParamKey = "power.flash"
	CurvatureX      ParamKey = "curvature.x"
	CurvatureY      ParamKey = "curvature.y"
	DegaussStrength ParamKey = "degauss.strength"
	RollStrength    ParamKey = "roll.strength"
	TearStrength    ParamKey = "tear.strength"
	TearAmplitude   ParamKey = "tear.amplitude"
	TearBands       ParamKey = "tear.bands"
)

Keys of the geometry effects' params.

const (
	AfterglowDecay       ParamKey = "afterglow.decay"
	ConvergenceAmount    ParamKey = "convergence.amount"
	ConvergenceOffset    ParamKey = "convergence.offset"
	GlowThreshold        ParamKey = "glow.threshold"
	GlowStrength         ParamKey = "glow.strength"
	GlowRadius           ParamKey = "glow.radius"
	GlassLevel           ParamKey = "glass.level"
	ScanlinesDepth       ParamKey = "scanlines.depth"
	ScanlinesMinScale    ParamKey = "scanlines.min_scale"
	ApertureMaskStrength ParamKey = "aperture_mask.strength"
	ApertureMaskMinScale ParamKey = "aperture_mask.min_scale"
	SoftnessAmount       ParamKey = "softness.amount"
	InterlaceStrength    ParamKey = "interlace.strength"
	InterlaceMinScale    ParamKey = "interlace.min_scale"
	SlotMaskStrength     ParamKey = "slot_mask.strength"
	SlotMaskMinScale     ParamKey = "slot_mask.min_scale"
)

Keys of the picture effects' params: the phosphor, the guns, the light and the beam.

const (
	GrainStrength    ParamKey = "grain.strength"
	FlickerStrength  ParamKey = "flicker.strength"
	FlickerFrequency ParamKey = "flicker.frequency"
	HumStrength      ParamKey = "hum.strength"
	HumPeriod        ParamKey = "hum.period"
	HumWidth         ParamKey = "hum.width"
	SnowStrength     ParamKey = "snow.strength"
	VignetteStrength ParamKey = "vignette.strength"
	CornersRadius    ParamKey = "corners.radius"
)

Keys of the signal and tube effects' params.

func Rate

func Rate(schedule string) ParamKey

Rate is the key of a schedule's rate: 1 keeps its pace, 2 doubles it, 0 stops it. Drive it to make glitches come thicker or thinner.

type Power added in v0.2.0

type Power struct {
	Width  float32 // the raster's width, 1 whole
	Height float32 // the raster's height, 1 whole
	Flash  float32 // the beam's flash added to the picture
}

Power squeezes the raster as the tube warms up or dies — a dot, a line, the picture — and flashes the beam. The TV drives it on PowerOn and PowerOff; at rest it is the whole picture.

func NewPower added in v0.2.0

func NewPower() *Power

NewPower is a tube at rest: the whole picture, no flash.

func (*Power) Name added in v0.2.0

func (*Power) Name() string

func (*Power) Params added in v0.2.0

func (e *Power) Params() []Param

func (*Power) Stage added in v0.2.0

func (*Power) Stage() Stage

type Roll added in v0.2.0

type Roll struct {
	Strength float32
}

Roll slips the picture down as the vertical sync is lost: Strength is the share of the height it has slipped, the blanking bar crossing with it. It rests at zero and is driven by the Roll episode.

func NewRoll added in v0.2.0

func NewRoll() *Roll

NewRoll is a picture held still.

func (*Roll) Name added in v0.2.0

func (*Roll) Name() string

func (*Roll) Params added in v0.2.0

func (e *Roll) Params() []Param

func (*Roll) Stage added in v0.2.0

func (*Roll) Stage() Stage

type Scanlines

type Scanlines struct {
	Depth float32 // how dark the gaps get
	// MinScale is the screen pixels per frame pixel the lines need: below
	// it they fade out, as too few pixels turn them into moiré.
	MinScale float32
}

Scanlines are the beam's lines, one per frame row: dark gaps between them, narrower where the beam is bright and wide.

func NewScanlines

func NewScanlines() *Scanlines

NewScanlines are clear lines on a big enough screen.

func (*Scanlines) Name

func (*Scanlines) Name() string

func (*Scanlines) Params

func (e *Scanlines) Params() []Param

func (*Scanlines) Stage

func (*Scanlines) Stage() Stage

type Setup

type Setup struct {
	// Seed makes the TV's randomness its own: two TVs with one seed, fed
	// the same Update steps, show the same.
	Seed uint64
	// Effects are the look of the set, at most one of each kind.
	Effects []Effect
	// Sources are named values in time: signals the game sets, drifts and
	// waves.
	Sources map[string]Source
	// Drives connect sources to params.
	Drives []Drive
	// Schedules play episodes now and then, by name; Rate(name) is the key
	// of each one's rate.
	Schedules map[string]Every
}

Setup is a whole TV as data: what it looks like and how it misbehaves. A preset is a function returning a Setup; NewTV builds a TV from one.

func Gorizont

func Gorizont() Setup

Gorizont is a Minsk color set of the eighties, well kept: a slightly bulging tube with clear scanlines, a faint mask, glowing highlights and a short afterglow. It has no moods of its own; add sources, drives and schedules to the returned setup to give it some.

func Rubin added in v0.2.0

func Rubin() Setup

Rubin is a Moscow color set of the eighties, well worn: a bulging tube, a soft beam through a slot mask, a dark hum rolling through, plenty of snow — and a glitch every minute or two.

func (Setup) Values

func (s Setup) Values() map[ParamKey]float32

Values are the base values of the setup's params: its effects' and its schedules' rates. They are what Blend mixes and TV.Apply takes.

type Signal

type Signal struct{}

Signal is a level the game sets, frame by frame: a fact of the game, such as the set being shaken. Get its Level with TV.Signal.

type SlotMask added in v0.2.0

type SlotMask struct {
	Strength float32
	// MinScale is the screen pixels per frame pixel the mask needs: below
	// it the mask fades out.
	MinScale float32
}

SlotMask is the phosphor of a shadow-mask set: red, green and blue stripes cut by slots, each column of triads half a slot below the last.

func NewSlotMask added in v0.2.0

func NewSlotMask() *SlotMask

NewSlotMask is a mask that shows on a big enough screen.

func (*SlotMask) Name added in v0.2.0

func (*SlotMask) Name() string

func (*SlotMask) Params added in v0.2.0

func (e *SlotMask) Params() []Param

func (*SlotMask) Stage added in v0.2.0

func (*SlotMask) Stage() Stage

type Snow added in v0.2.0

type Snow struct {
	Strength float32
}

Snow is the screen drowning in snow, as when the signal is lost. Its strength rests at zero and is driven by the Snow episode.

func NewSnow added in v0.2.0

func NewSnow() *Snow

NewSnow is a clear signal.

func (*Snow) Name added in v0.2.0

func (*Snow) Name() string

func (*Snow) Params added in v0.2.0

func (e *Snow) Params() []Param

func (*Snow) Stage added in v0.2.0

func (*Snow) Stage() Stage

type Softness added in v0.2.0

type Softness struct {
	Amount float32
}

Softness spreads the beam between neighboring pixels of a line: 0 keeps them square, 1 blends them smoothly. It shows on a screen scaled by a fraction, where square pixels come out uneven.

func NewSoftness added in v0.2.0

func NewSoftness() *Softness

NewSoftness is a beam a little soft.

func (*Softness) Name added in v0.2.0

func (*Softness) Name() string

func (*Softness) Params added in v0.2.0

func (e *Softness) Params() []Param

func (*Softness) Stage added in v0.2.0

func (*Softness) Stage() Stage

type Source

type Source interface {
	// contains filtered or unexported methods
}

Source is a value that changes in time, from 0 to 1, and drives params through Drives. Sources are named in Setup.Sources.

type Stage

type Stage int

Stage is where in the picture an effect works. Stages run in this order, which is the order the light of a real set goes through; within a stage, effects run in the order they are listed.

const (
	// StagePrepass effects work on whole frames before the picture is
	// drawn: the afterglow of the phosphor.
	StagePrepass Stage = iota
	// StageGeometry effects move the point of the frame a screen point shows.
	StageGeometry
	// StageRead effects change how one point of the frame is read: the
	// beam's softness between neighboring pixels. A TV has at most one.
	StageRead
	// StageSample effects change how the picture is gathered at that point
	// from the frame: the guns out of register. A TV has at most one.
	StageSample
	// StageLight effects add light before the beam draws it: glow, glass.
	StageLight
	// StageBeam effects shape the beam: scanlines.
	StageBeam
	// StageMask effects are the phosphor mask on the screen's own pixels.
	StageMask
	// StagePost effects are the signal's noise and the tube's falloff.
	StagePost
	// StageFrame effects cut the picture to the tube's outline.
	StageFrame
)

type TV

type TV struct {
	// contains filtered or unexported fields
}

TV is a set at work: its effects, its clock and its moods. The game talks to it in facts — a signal's level, an episode to play — and the TV works out what each effect shows. A backend draws it.

A TV is not safe for concurrent use.

func NewTV

func NewTV(setup Setup) (*TV, error)

NewTV builds a TV from a setup. The TV takes copies of the setup's effects, so the setup can be reused. It fails on a setup that does not hold together: a drive from a source that is not there, a schedule with no episodes and the like.

func (*TV) Apply

func (tv *TV) Apply(values map[ParamKey]float32)

Apply sets the base values of the params it names, kept in range. Keys the TV does not have are left alone.

func (*TV) Dark added in v0.2.0

func (tv *TV) Dark() bool

Dark reports the tube gone dark after PowerOff — the moment a game that lets its picture fold away can quit.

func (*TV) Effects

func (tv *TV) Effects() []Effect

Effects are the TV's effects in stage order. They belong to the TV: change them through Params or Apply.

func (*TV) Epoch

func (tv *TV) Epoch() int

Epoch changes on every Reset: a backend clears what it keeps between frames when it sees a new one.

func (*TV) Frame

func (tv *TV) Frame() int

Frame is the number of Updates so far.

func (*TV) Hold

func (tv *TV) Hold(held bool)

Hold keeps the schedules' episodes waiting while held is true — say, while the player drags something.

func (*TV) Map

func (tv *TV) Map(x, y float64, w, h int) (float64, float64)

Map is the point of a w×h frame that the TV shows at the point x, y — where it would be without the bend of the glass and the tear of the lines. Use it for a pointer or a touch: map where the player pressed, hit what they see there. The result can fall outside the frame.

func (*TV) Params

func (tv *TV) Params() []Param

Params are all the TV's tunable numbers, the effects' first, then the schedules' rates. Writing through a param's Value sets its base.

func (*TV) Play

func (tv *TV) Play(e Episode)

Play plays an episode from its start, over whatever is playing.

func (*TV) PowerOff added in v0.2.0

func (tv *TV) PowerOff()

PowerOff switches the set off: the picture folds to a line and a dot and fades; Dark reports it gone. Calling it again while dying changes nothing.

func (*TV) PowerOn added in v0.2.0

func (tv *TV) PowerOn()

PowerOn switches the set on: the picture grows from a dot over half a second. The Power effect shows it.

func (*TV) Reset

func (tv *TV) Reset()

Reset starts the set afresh: no episode playing and, for the backend, no afterglow left on the phosphor. Call it when the effects come back on.

func (*TV) Revision

func (tv *TV) Revision() int

Revision changes whenever the set of effects does: a backend rebuilds its shaders when it sees a new one.

func (*TV) SetEffects

func (tv *TV) SetEffects(effects ...Effect) error

SetEffects replaces the TV's effects, keeping its sources, drives and schedules. A backend redraws the TV's shaders on the next frame, which may take a moment: change the set in a menu, not in play.

func (*TV) Signal

func (tv *TV) Signal(name string) (*Level, error)

Signal is the level of the named signal, for the game to set. It fails if the setup has no signal by that name: better at start than a dead knob in play.

func (*TV) SourceValue added in v0.4.0

func (tv *TV) SourceValue(name string) float32

SourceValue is the named source's value now, from 0 to 1: the reception a drift has wandered to, say, for the game to show. It is 0 for a source the setup does not have.

func (*TV) Time

func (tv *TV) Time() float64

Time is the TV's clock in seconds, wrapped every hour to keep shaders precise.

func (*TV) Update

func (tv *TV) Update(dt float64)

Update moves the TV on by dt seconds: sources, episodes and schedules. Call it once per game tick; the TV has no clock of its own.

func (*TV) Value

func (tv *TV) Value(key ParamKey) float32

Value is a param's value now: its base and the modulation on it, kept in range. A param the TV does not have is 0.

func (*TV) Values

func (tv *TV) Values() map[ParamKey]float32

Values are the base values of the TV's params, without the modulation: what a game saves as the player's settings.

type Target

type Target struct {
	Param  ParamKey
	Weight float32
}

Target is a param an episode moves, and how far at its peak.

type Tear

type Tear struct {
	Strength  float32 // 0 calm, 1 fully torn
	Amplitude float32 // the wave's shift of a line at full strength, in frame pixels
	// Bands is the shift of the bands torn at random, in frame pixels. They
	// last a moment, so Map leaves them out.
	Bands float32
}

Tear shifts the lines sideways, as when the set is knocked or the horizontal sync slips: in a running wave, and in bands of lines torn at random. Its strength usually rests at zero and is driven: by a signal such as a shake, or by a glitch.

func NewTear

func NewTear() *Tear

NewTear is a calm tear that shifts lines by a pixel and a half at full strength, without bands.

func (*Tear) Name

func (*Tear) Name() string

func (*Tear) Params

func (e *Tear) Params() []Param

func (*Tear) Stage

func (*Tear) Stage() Stage

type Vignette

type Vignette struct {
	Strength float32
}

Vignette dims the picture towards its corners.

func NewVignette

func NewVignette() *Vignette

NewVignette is a gentle falloff.

func (*Vignette) Name

func (*Vignette) Name() string

func (*Vignette) Params

func (e *Vignette) Params() []Param

func (*Vignette) Stage

func (*Vignette) Stage() Stage

type Wave

type Wave struct {
	Period float32 // seconds of a whole rise and fall
}

Wave rises from 0 to 1 and falls back, again and again.

Directories

Path Synopsis
cmd
kinescope-lab command
Command kinescope-lab is a lab for kinescope, built for the browser: make a setup — effects, sources, drives, schedules — watch a test card, a moving scene or a dropped picture through it, tell the TV the game's facts, and take the setup away as Go.
Command kinescope-lab is a lab for kinescope, built for the browser: make a setup — effects, sources, drives, schedules — watch a test card, a moving scene or a dropped picture through it, tell the TV the game's facts, and take the setup away as Go.
kinescope-lab/internal/lab
Package lab is the kinescope lab's model: a setup being made, kept as the lists the page shows, the TV built from it, its Go code and its state for the page's address.
Package lab is the kinescope lab's model: a setup being made, kept as the lists the page shows, the TV built from it, its Go code and its state for the page's address.
Package ebitengine draws kinescope TVs with Ebitengine: each TV's effects are composed into one picture shader, with a prepass or two at the frame's size for the effects that need whole frames (glow, afterglow).
Package ebitengine draws kinescope TVs with Ebitengine: each TV's effects are composed into one picture shader, with a prepass or two at the frame's size for the effects that need whole frames (glow, afterglow).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL