tmp backgrounds
ci/woodpecker/push/ebitengine Pipeline was successful

This commit is contained in:
2026-08-29 22:30:22 +02:00
parent 45dac473fd
commit bbc3faf3d7
87 changed files with 1672 additions and 158 deletions
+107
View File
@@ -0,0 +1,107 @@
package screen
import (
inkwell "git.teletypegames.org/engines/inkwell"
"git.teletypegames.org/games/realworld/internal/names"
"git.teletypegames.org/games/realworld/internal/world"
)
// alley is the alley behind Noodle's house — beat 3, and the vertical slice the
// whole HUD is measured against: hotspots, a gated pickup, the two-slot
// Armilla, tape dialogue and an authored failure.
//
// The wiki keeps it in the same catalogue row as the house ("Noodle háza & a
// sikátor") and its graph has one arrow in, NoodleUtcafront → Sikátor, so the
// only way out is back to the street. The alley's own locked back door is the
// New Game+ hook and leads to a room the deck has not painted.
func alley() screen {
return screen{
name: names.ScreenAlley,
title: "ALLEY — BEHIND NOODLE'S HOUSE",
background: names.BgAlley,
exits: []exit{
// On the left: the back door hotspot below owns the other end.
{to: names.ScreenNoodleHouse, label: "back out to the street", side: sideLeft},
},
actors: []inkwell.SceneActor{
{CharacterName: names.Paul, At: inkwell.Point{X: 120, Y: 232}},
},
onEnter: setVarIfEmpty(names.VarArmillaStrip, "SRP: 1 tape"),
hotspots: []inkwell.Hotspot{hidingPlace(), backDoor(), bin(), fireEscape()},
}
}
// hidingPlace holds the mystery tape. It only opens once Paul has the tip from
// the police station (beat 2) — without it he does not know what to look for.
func hidingPlace() inkwell.Hotspot {
return inkwell.Hotspot{
Name: "hiding_place",
Label: "gap at the foot of the wall",
Area: inkwell.Rect(416, 150, 68, 52),
OnLook: inkwell.If(inkwell.Flag(names.FlagHasTape),
inkwell.Say(names.Paul, "Empty. Whatever was in there is on me now."),
inkwell.Say(names.Paul, "Loose brick. There's a gap behind it."),
),
OnUse: inkwell.If(inkwell.Flag(names.FlagPoliceTip),
inkwell.If(inkwell.Flag(names.FlagHasTape),
inkwell.Say(names.Paul, "There's nothing else in there."),
inkwell.Seq(
inkwell.Say(names.Paul, "“Behind the brick.” All right then."),
inkwell.Give(names.ItemMysteryTape),
inkwell.SetFlag(names.FlagHasTape),
inkwell.Say(names.Paul, "A Personal Tape. No label, no seal."),
world.TapeSay(names.Dex, "Password-locked. And somebody very much did not want it found."),
),
),
inkwell.Say(names.Paul, "One loose brick. I'm not taking the alley apart over it."),
),
OnTake: inkwell.Say(names.Paul, "I'm not carrying a wall."),
}
}
// backDoor is the New Game+ hook: the code can be entered on the first visit
// too, if the player already knows it.
func backDoor() inkwell.Hotspot {
return inkwell.Hotspot{
Name: "back_door",
Label: "locked back door",
Area: inkwell.Rect(72, 106, 80, 124),
OnLook: inkwell.Say(names.Paul, "Keypad. Four digits, worn keys."),
OnUse: inkwell.Seq(
inkwell.Say(names.Paul, "Locked. And I'm not guessing four digits."),
world.TapeSay(names.Dex, "The wear is heaviest on the two and the seven. That isn't a code. It's fewer options."),
),
OnTalk: inkwell.Say(names.Paul, "To the door? I'm not there yet."),
OnUseWith: map[string]inkwell.Action{
// An AUTHORED failure — not the engine's hardcoded flash.
names.ItemBlackArmilla: inkwell.Seq(
inkwell.Say(names.Paul, "A black-market bracelet doesn't open a keypad."),
world.TapeSay(names.Dex, "But you do get an advert out of it."),
),
},
}
}
func bin() inkwell.Hotspot {
return inkwell.Hotspot{
Name: "bin",
Label: "toppled bin",
Area: inkwell.Rect(240, 170, 88, 60),
OnLook: inkwell.Say(names.Paul, "Somebody's been through it. Thoroughly."),
OnUse: inkwell.Seq(
inkwell.Say(names.Paul, "Already turned out. I'm not going to be the second one."),
world.TapeSay(names.Dex, "The police don't go through bins. So who did?"),
),
}
}
func fireEscape() inkwell.Hotspot {
return inkwell.Hotspot{
Name: "fire_escape",
Label: "fire escape",
Area: inkwell.Rect(528, 44, 88, 160),
OnLook: inkwell.Say(names.Paul, "Runs all the way to the roof. Bottom rung is two metres over my head."),
OnUse: inkwell.Say(names.Paul, "Can't reach it. I'd need something to stand on."),
}
}
+21
View File
@@ -0,0 +1,21 @@
package screen
import "git.teletypegames.org/games/realworld/internal/names"
// bbsTerminal is the screen you get when Paul sits down at a terminal: the
// catalogue calls it a "Tárgy/portál", a physical object in the world that
// opens the SPOKE — BBS surface (wiki: entities#helyszinek).
//
// It is the same screen wherever the terminal stands — the club's ECHO box,
// Norman's machine at the flat, the old one on the roof — which is why its way
// out is "back", and not a place.
func bbsTerminal() screen {
return screen{
name: names.ScreenBBSTerminal,
title: "TERMINAL — BBS ACCESS POINT",
background: names.BgBBSTerminal,
exits: []exit{
{label: "step back from the terminal", side: sideNear},
},
}
}
+16
View File
@@ -0,0 +1,16 @@
package screen
import "git.teletypegames.org/games/realworld/internal/names"
// bvkBranch is a 💡 suggestion: the local certification office, imprimatur
// stamps and licence checks and numbered waiting rooms — everything a man with
// a jailbroken two-slot Armilla should stay away from
// (wiki: entities#potenciális-további-helyszínek).
func bvkBranch() screen {
return screen{
name: names.ScreenBVKBranch,
title: "BVK — SAN FRANCISCO BRANCH",
background: names.BgBVKBranch,
onSelector: true,
}
}
+16
View File
@@ -0,0 +1,16 @@
package screen
import "git.teletypegames.org/games/realworld/internal/names"
// columbarium is a 💡 suggestion: where Dex is buried. A quiet place, away
// from the noise of the terminals — the loss-and-letting-go thread, which is
// the same thread as Norman and The AI
// (wiki: entities#potenciális-további-helyszínek).
func columbarium() screen {
return screen{
name: names.ScreenColumbarium,
title: "COLUMBARIUM — DEX'S MEMORIAL",
background: names.BgColumbarium,
onSelector: true,
}
}
+15
View File
@@ -0,0 +1,15 @@
package screen
import "git.teletypegames.org/games/realworld/internal/names"
// curatorShop is a 💡 suggestion: the last workshop of the declining curators'
// guild — quartz tuning, tape repair, craft handed down for generations. The
// man who built Paul's second slot (wiki: entities#potenciális-további-helyszínek).
func curatorShop() screen {
return screen{
name: names.ScreenCuratorShop,
title: "CURATOR SERVICE — THE MASTER'S WORKSHOP",
background: names.BgCuratorShop,
onSelector: true,
}
}
+109
View File
@@ -0,0 +1,109 @@
package screen
// Exits — the connections between screens.
//
// Where a screen leads is recorded in that screen's own file, and it comes from
// the wiki's location graph (projects/realworld/story#helyszín-gráf-vázlat).
// That graph has two kinds of edge, and so does this file:
//
// - Physical adjacency. The alley is behind Noodle's house; the club is
// through the back of the trinket shop; the noodle place is next door to
// it. These are the arrows the wiki draws between locations, and they are
// declared with `to`.
// - The selector. The wiki centres its map on a "Helyszínválasztó" — not a
// real location but the menu screen every main location connects to both
// ways. A screen marks itself with onSelector; selector.go builds the other
// half of each of those edges.
//
// Some of the wiki's arrows are one-way with a condition on them ("beengedés",
// "NG+ kód", "Sumphor liftkódja"). `needs` carries that: the exit is there, and
// says so, but will not open until the flag is set.
//
// Until the doors are measured on the paintings, an exit is a strip along one
// edge of the picture — the convention every 1990s point & click used, and a
// one-field change to re-aim at a real door later.
import (
inkwell "git.teletypegames.org/engines/inkwell"
"git.teletypegames.org/games/realworld/internal/names"
"git.teletypegames.org/games/realworld/internal/world"
)
type side int
const (
sideLeft side = iota // off the left edge
sideRight // off the right edge
sideBack // into the depth of the picture
sideNear // out towards the camera
)
// area is the strip of picture the exit occupies, inside the scene region
// (below the top bar at y=20, above the HUD divider at y=262).
func (s side) area() inkwell.Shape {
switch s {
case sideLeft:
return inkwell.Rect(0, 40, 56, 212)
case sideRight:
return inkwell.Rect(584, 40, 56, 212)
case sideBack:
return inkwell.Rect(232, 28, 176, 84)
default: // sideNear
return inkwell.Rect(232, 214, 176, 48)
}
}
// exit is one way out of a screen.
type exit struct {
to string // the screen it leads to; empty means "back the way you came"
label string // what the status line calls it
side side
// area overrides the edge strip. The selector's map pins use it; nothing
// else should need to until the art arrives.
area inkwell.Shape
// needs is a flag that has to be set before the exit opens, and blocked is
// what Paul says while it is not.
needs string
blocked string
}
// toSelector is the way back out to the rest of the city, on every screen the
// wiki's Helyszínválasztó lists.
func toSelector() exit {
return exit{to: names.ScreenSelector, label: "the rest of the city", side: sideNear}
}
// exitName is how a connection stays machine-readable: the graph can be read
// straight back out of the registered scenes, so a test can walk it.
func exitName(to string) string {
if to == "" {
return "exit:back"
}
return "exit:" + to
}
func (e exit) hotspot(w *world.World) inkwell.Hotspot {
var travel inkwell.Action = inkwell.GoTo(e.to)
if e.to == "" {
travel = world.Back(w)
}
if e.needs != "" {
travel = inkwell.If(inkwell.Flag(e.needs), travel, inkwell.Say(names.Paul, e.blocked))
}
area := e.area
if area == nil {
area = e.side.area()
}
return inkwell.Hotspot{
Name: exitName(e.to),
Label: e.label,
Area: area,
Cursor: inkwell.CursorExit,
OnLook: inkwell.Say(names.Paul, "That way: "+e.label+"."),
OnUse: travel,
OnTake: inkwell.Say(names.Paul, "It's a way out, not a thing."),
}
}
+18
View File
@@ -0,0 +1,18 @@
package screen
import "git.teletypegames.org/games/realworld/internal/names"
// hackerspace: the community workshop at the edge of the Mission, printers and
// stripped servers and paranoid amateur cryptographers. Where the server-farm
// access tape can be had (wiki: entities#helyszinek, timeline 24).
//
// The wiki's graph gives it no physical neighbour, so it hangs off the
// selector like every other main location.
func hackerspace() screen {
return screen{
name: names.ScreenHackerspace,
title: "HACKERSPACE",
background: names.BgHackerspace,
onSelector: true,
}
}
+14
View File
@@ -0,0 +1,14 @@
package screen
import "git.teletypegames.org/games/realworld/internal/names"
// hospital is the clinical dead end: Dr. Maren looked for a medical reason for
// Norman's state and did not find one (wiki: entities, timeline 21).
func hospital() screen {
return screen{
name: names.ScreenHospital,
title: "HOSPITAL — PSYCHIATRIC WING",
background: names.BgHospital,
onSelector: true,
}
}
+19
View File
@@ -0,0 +1,19 @@
package screen
import "git.teletypegames.org/games/realworld/internal/names"
// iceCreamShop stands where Paul's old bar used to be, and they insist there
// never was a bar. The BVK-registered terminal in the corner is the one the
// candy-crush guy will not give up (wiki: story, the Fagyizó beat).
//
// The search he eventually allows is what names the club — a lead, not a door:
// the way there is the trinket shop's address, so this screen has no exit of
// its own.
func iceCreamShop() screen {
return screen{
name: names.ScreenIceCreamShop,
title: "ICE CREAM SHOP — WHERE THE BAR WAS",
background: names.BgIceCreamShop,
onSelector: true,
}
}
+21
View File
@@ -0,0 +1,21 @@
package screen
import "git.teletypegames.org/games/realworld/internal/names"
// noodleHouse is the street front outside Noodle's place. By the time Paul
// gets there the police have sealed the house and taken Noodle in, and a
// uniform stands in front of the tape (wiki: story#noodle-szál).
//
// The wiki's graph runs NoodleUtcafront → Sikátor: the alley behind the house
// is reached from here, and from nowhere else.
func noodleHouse() screen {
return screen{
name: names.ScreenNoodleHouse,
title: "NOODLE'S HOUSE — SEALED",
background: names.BgNoodleHouse,
onSelector: true,
exits: []exit{
{to: names.ScreenAlley, label: "the alley behind the house", side: sideRight},
},
}
}
@@ -0,0 +1,23 @@
package screen
import "git.teletypegames.org/games/realworld/internal/names"
// normanApartment is the flat Norman was taken from: his Armilla left behind
// on the side, his Personal Tape missing from it, his terminal full of the
// correspondence and the diary (wiki: story#norman-lakása).
//
// Two ways on from here. His terminal is one of the BBS access points the
// catalogue lists ("a titkos klub ECHO-terminálja; később Norman lakása /
// tetőterasz"), and the roof hideout is up his own stairs.
func normanApartment() screen {
return screen{
name: names.ScreenNormanApartment,
title: "NORMAN'S APARTMENT",
background: names.BgNormanApartment,
onSelector: true,
exits: []exit{
{to: names.ScreenRooftopHideout, label: "the stairs up to the roof", side: sideBack},
{to: names.ScreenBBSTerminal, label: "Norman's terminal", side: sideRight},
},
}
}
+22
View File
@@ -0,0 +1,22 @@
package screen
import "git.teletypegames.org/games/realworld/internal/names"
// paulShop is where the game starts: the junk shop and garage Paul lives
// behind (wiki: entities#helyszinek). It is the only screen outside San
// Francisco — Noodle's letter arrives here and puts him on a bus
// (wiki: entities, Paul's timeline 12).
//
// The bus is a screen the deck does not have, so the exit is the journey
// itself: one way, with no coming back to the shop mid-story. That is also why
// the shop is not on the selector — it is not one of the city's locations.
func paulShop() screen {
return screen{
name: names.ScreenPaulShop,
title: "PAUL'S SHOP — JUNK AND GARAGE",
background: names.BgPaulShop,
exits: []exit{
{to: names.ScreenNoodleHouse, label: "the bus to San Francisco", side: sideNear},
},
}
}
+20
View File
@@ -0,0 +1,20 @@
package screen
import "git.teletypegames.org/games/realworld/internal/names"
// policeStation is where Noodle is held for dealing in cracked Armillas, and
// where a clerk quietly tips Paul off about the hiding place
// (wiki: entities, Paul's timeline 4 and 15).
//
// The wiki's graph splits it three ways — utcafront → recepció → kihallgató —
// but the deck paints it as one screen, so the reception desk, the waiting
// room and the interview window are hotspots inside this one, not screens of
// their own.
func policeStation() screen {
return screen{
name: names.ScreenPoliceStation,
title: "SFPD — STATION AND HOLDING",
background: names.BgPoliceStation,
onSelector: true,
}
}
+16
View File
@@ -0,0 +1,16 @@
package screen
import "git.teletypegames.org/games/realworld/internal/names"
// publicBBS is one of the wiki's 💡 suggestions: the canon has BBS terminals
// standing in cafés, post offices and bus stops — live infrastructure, not
// relics. An alternative way to read the boards, in a place where somebody can
// read over your shoulder (wiki: entities#potenciális-további-helyszínek).
func publicBBS() screen {
return screen{
name: names.ScreenPublicBBS,
title: "PUBLIC BBS TERMINAL",
background: names.BgPublicBBS,
onSelector: true,
}
}
@@ -0,0 +1,21 @@
package screen
import "git.teletypegames.org/games/realworld/internal/names"
// rooftopHideout is Norman's hiding place: an abandoned roof terrace with his
// personal things, his notes and an old BBS terminal — the emotional high point
// of the investigation (wiki: entities#helyszinek, timeline 23).
//
// It is up the stairs from his flat, and it is one of the places the catalogue
// puts a terminal.
func rooftopHideout() screen {
return screen{
name: names.ScreenRooftopHideout,
title: "NORMAN'S ROOFTOP HIDEOUT",
background: names.BgRooftopHideout,
exits: []exit{
{to: names.ScreenNormanApartment, label: "back down into the flat", side: sideNear},
{to: names.ScreenBBSTerminal, label: "the old terminal", side: sideRight},
},
}
}
+16
View File
@@ -0,0 +1,16 @@
package screen
import "git.teletypegames.org/games/realworld/internal/names"
// samizdatPress is a 💡 suggestion: the Resistance's physical node — parody
// BVK databases, underground publications, a P2P node humming in the cellar.
// The samizdat intellectuals are the first to write down that the world is
// slipping (wiki: entities#potenciális-további-helyszínek).
func samizdatPress() screen {
return screen{
name: names.ScreenSamizdatPress,
title: "SAMIZDAT PRINTING HOUSE",
background: names.BgSamizdatPress,
onSelector: true,
}
}
+15
View File
@@ -0,0 +1,15 @@
package screen
import "git.teletypegames.org/games/realworld/internal/names"
// scrapMarket is a 💡 suggestion: the black market in condemned ACPs and bare,
// unstamped tape. Canon says the leak of scrap is not an accident — "something
// wants to live on" (wiki: entities#potenciális-további-helyszínek).
func scrapMarket() screen {
return screen{
name: names.ScreenScrapMarket,
title: "SCRAP MARKET",
background: names.BgScrapMarket,
onSelector: true,
}
}
+149
View File
@@ -0,0 +1,149 @@
// Package screen registers the game's screens — one file per screen.
//
// "Screen" is this project's word: the wiki's catalogue, the concept-art deck
// and the beat tables all count screens. inkwell's word for the same thing is
// Scene, which is the type every file here returns — a screen IS the engine's
// scene, and the rest of the code says "scene" only where it is talking to the
// engine.
//
// A file here holds everything that is true of one screen: its picture, the
// name on the top bar, where you can get to from it (exit.go), and — once its
// beat is written — its hotspots, NPCs and dialogue. Only the alley has that
// last part so far; the rest are greybox.
package screen
import (
inkwell "git.teletypegames.org/engines/inkwell"
"git.teletypegames.org/games/realworld/internal/names"
"git.teletypegames.org/games/realworld/internal/world"
)
// screen is what each file in this package fills in.
type screen struct {
name string
title string
background string
// exits: where you can get to from here. See exit.go.
exits []exit
// onSelector marks a main location — one of those the wiki's
// Helyszínválasztó lists (story#helyszín-gráf-vázlat). It gets a way back
// to the selector for free, and the selector gets a way here.
onSelector bool
// What an authored beat adds on top. Zero values are fine: Paul lands in
// the middle of the default floor strip and nothing else happens.
hotspots []inkwell.Hotspot
actors []inkwell.SceneActor
walkboxes []inkwell.Polygon
onEnter inkwell.Action
}
// deck is every screen in the game, in the order of the wiki's location
// catalogue (entities#helyszinek) — which the concept-art deck follows number
// for number. Registration order is this order, and it is also the order the
// arrow keys walk (ui/screen_nav.go).
//
// Deck 05, Norman's workplace, is in the catalogue but has not been drawn, so
// it has no file yet. The alley has no deck number: the catalogue keeps it in
// the same row as Noodle's house, and that is where it sits here too.
var deck = []func() screen{
paulShop, // 01
noodleHouse, // 02
alley, // 02, same catalogue row
normanApartment, // 03
policeStation, // 04
hackerspace, // 06
iceCreamShop, // 07
trinketShop, // 08
smallRestaurant, // 09
secretClub, // 10
bbsTerminal, // 11
street, // 12
hospital, // 13
serverFarm, // 14
secretLab, // 15
rooftopHideout, // 16
publicBBS, // 17
bvkBranch, // 18
curatorShop, // 19
scrapMarket, // 20
samizdatPress, // 21
showroom, // 22
columbarium, // 23
}
// Register adds every screen to the game, the selector first: it is the centre
// of the wiki's location graph, and it is built from the deck rather than
// listed in it.
func Register(w *world.World) {
screens := make([]screen, 0, len(deck)+1)
for _, f := range deck {
screens = append(screens, f())
}
for _, s := range append([]screen{selector(screens)}, screens...) {
w.G.SceneManager.Register(build(w, s))
}
}
// The default floor: the bottom of the scene region, above the HUD divider.
// A screen with a painted floor of its own overrides it.
var floor = []inkwell.Polygon{
inkwell.Poly(
inkwell.Point{X: 16, Y: 196}, inkwell.Point{X: 624, Y: 196},
inkwell.Point{X: 624, Y: 258}, inkwell.Point{X: 16, Y: 258},
),
}
// build turns a screen into the engine's scene.
//
// The authored hotspots go in front of the exits, because the engine takes the
// first one whose area contains the click (HotspotAt): a painted thing beats
// the edge strip an exit sits on, wherever the two overlap.
func build(w *world.World, s screen) inkwell.Scene {
exits := s.exits
if s.onSelector {
exits = append(exits, toSelector())
}
hotspots := make([]inkwell.Hotspot, 0, len(exits)+len(s.hotspots))
hotspots = append(hotspots, s.hotspots...)
for _, e := range exits {
hotspots = append(hotspots, e.hotspot(w))
}
actors := s.actors
if actors == nil {
actors = []inkwell.SceneActor{
{CharacterName: names.Paul, At: inkwell.Point{X: 320, Y: 232}},
}
}
walkboxes := s.walkboxes
if walkboxes == nil {
walkboxes = floor
}
onEnter := inkwell.Action(world.EnterScene(w, s.name))
if s.onEnter != nil {
onEnter = inkwell.Seq(onEnter, s.onEnter)
}
return inkwell.Scene{
Name: s.name,
Title: s.title,
Background: s.background,
Actors: actors,
Walkboxes: walkboxes,
Hotspots: hotspots,
OnEnter: onEnter,
}
}
// setVarIfEmpty only writes when the variable is still unset, so re-entering a
// screen does not clobber what the game has set in the meantime.
func setVarIfEmpty(name string, v any) inkwell.Action {
return world.Fn(func(ctx *inkwell.Ctx) {
if ctx.Game.State.Var(name) == nil {
ctx.Game.State.SetVar(name, v)
}
})
}
+23
View File
@@ -0,0 +1,23 @@
package screen
import "git.teletypegames.org/games/realworld/internal/names"
// secretClub is the empty room behind the shop where the "underwater sun"
// company gave their talk, with a secretary who knows about the BVK's plans for
// people's minds and nothing about the key or the tape
// (wiki: story, the club beat).
//
// The terminal in here is the one that matters: not under BVK control, ECHO
// running under the standard LECTOR surface. Reading it is where the mystery
// tape finally speaks.
func secretClub() screen {
return screen{
name: names.ScreenSecretClub,
title: "SECRET CLUB — THE UNDERWATER SUN",
background: names.BgSecretClub,
exits: []exit{
{to: names.ScreenBBSTerminal, label: "the terminal in the corner", side: sideBack},
{to: names.ScreenTrinketShop, label: "back out through the shop", side: sideNear},
},
}
}
+20
View File
@@ -0,0 +1,20 @@
package screen
import "git.teletypegames.org/games/realworld/internal/names"
// secretLab is the lab the BVK's secret section runs, where Norman lies wired
// into the simulator: the two-thirds point where Paul finds him, and the
// screen the finale plays on (wiki: entities#helyszinek, timeline 22 and 25).
//
// The wiki's graph runs Híd → security szoba → Norman szimulációs szobája, and
// the story puts the whole thing inside the Golden Gate Bridge. The deck paints
// it as one screen, so the door at the foot of the bridge, the observation room
// and the glass wall are hotspots in here.
func secretLab() screen {
return screen{
name: names.ScreenSecretLab,
title: "SECRET RESEARCH LABORATORY",
background: names.BgSecretLab,
onSelector: true,
}
}
+66
View File
@@ -0,0 +1,66 @@
package screen
import (
"strings"
inkwell "git.teletypegames.org/engines/inkwell"
"git.teletypegames.org/games/realworld/internal/names"
)
// selector is the wiki's Helyszínválasztó: "nem valódi helyszín, hanem a
// menü-képernyő, ahonnan az összes fő helyszín elérhető"
// (story#helyszínválasztó). The location graph is centred on it — every main
// location connects to it both ways, and physical adjacency takes over from
// there inwards — so without it the city is a handful of disconnected rooms.
//
// It is not in the concept-art deck and has no painting, so it renders as an
// inkwell placeholder with one labelled pin per destination, laid out on a
// grid. Whatever the map ends up looking like, the connections it carries are
// the ones the screens declare, not a list kept here: selector() is built from
// whichever screens marked themselves onSelector.
func selector(rest []screen) screen {
var exits []exit
for _, s := range rest {
if !s.onSelector {
continue
}
exits = append(exits, exit{
to: s.name,
label: pinLabel(s.title),
area: pin(len(exits)),
})
}
return screen{
name: names.ScreenSelector,
title: "SAN FRANCISCO",
background: names.BgSelector,
exits: exits,
// Nobody stands on a map.
actors: []inkwell.SceneActor{},
walkboxes: []inkwell.Polygon{},
}
}
// The pin grid: three columns down the scene region, filled top to bottom.
const (
pinCols = 3
pinW = 192.0
pinH = 32.0
pinX = 16.0
pinY = 28.0
pinGapX = 204.0
pinGapY = 38.0
)
func pin(i int) inkwell.Shape {
col, row := i%pinCols, i/pinCols
return inkwell.Rect(pinX+float64(col)*pinGapX, pinY+float64(row)*pinGapY, pinW, pinH)
}
// pinLabel is the screen's title without the em-dash subtitle: on a map a
// location wants its name, not its description.
func pinLabel(title string) string {
name, _, _ := strings.Cut(title, " — ")
return name
}
+15
View File
@@ -0,0 +1,15 @@
package screen
import "git.teletypegames.org/games/realworld/internal/names"
// serverFarm is the cooled, humming corridors where The AI's physical
// infrastructure can be seen for what it is. Getting in needs the access tape
// from the hackerspace or from Noodle (wiki: entities, timeline 24).
func serverFarm() screen {
return screen{
name: names.ScreenServerFarm,
title: "SERVER FARM",
background: names.BgServerFarm,
onSelector: true,
}
}
+16
View File
@@ -0,0 +1,16 @@
package screen
import "git.teletypegames.org/games/realworld/internal/names"
// showroom is a 💡 suggestion: the legitimate, glittering side — Armilla
// cabinets, licensed operators, matte-black Nokia-punk under showroom lights.
// The contrast with Paul's edge of the world
// (wiki: entities#potenciális-további-helyszínek).
func showroom() screen {
return screen{
name: names.ScreenShowroom,
title: "NEUMATRONIC SHOWROOM",
background: names.BgShowroom,
onSelector: true,
}
}
@@ -0,0 +1,21 @@
package screen
import "git.teletypegames.org/games/realworld/internal/names"
// smallRestaurant is the little Chinese place beside the trinket shop, and the
// lit mirror in its toilet is the puzzle: it flickers, and something shows
// behind it (wiki: story, the Kajálda beat).
//
// The wiki's graph runs Kajálda → étterem rész → WC; the deck paints all three
// as one screen, so the mirror is a hotspot here. The only way out is back to
// the shop — this is an inner room, not a location on the selector.
func smallRestaurant() screen {
return screen{
name: names.ScreenSmallRestaurant,
title: "SMALL RESTAURANT — NEXT DOOR",
background: names.BgSmallRestaurant,
exits: []exit{
{to: names.ScreenTrinketShop, label: "back to the trinket shop", side: sideLeft},
},
}
}
+19
View File
@@ -0,0 +1,19 @@
package screen
import "git.teletypegames.org/games/realworld/internal/names"
// street is the city itself: pavements, a square, a metro entrance, and the
// environmental decay that tracks The AI's spread — glitching systems, odd
// news, infrastructure that slips (wiki: entities#helyszinek).
//
// The wiki's own street front is the one with the hotdog stand and Sumphor on
// it (story#norman-lakása); that is a beat for this screen, not a screen of its
// own.
func street() screen {
return screen{
name: names.ScreenStreet,
title: "STREET",
background: names.BgStreet,
onSelector: true,
}
}
+27
View File
@@ -0,0 +1,27 @@
package screen
import "git.teletypegames.org/games/realworld/internal/names"
// trinketShop is the Chinatown shop at the club's address, whose owner sells
// Paul junk he does not need and is, in fact, the club's doorman
// (wiki: story#csecsebecse-bolt--kajálda-szál).
//
// Both of the wiki's arrows out of it are here: Bolt → Kajálda, the eating
// place next door, and Bolt →|beengedés| Klub, which stays shut until the
// shopkeeper has been shown the underwater sun.
func trinketShop() screen {
return screen{
name: names.ScreenTrinketShop,
title: "CHINATOWN — TRINKET SHOP",
background: names.BgTrinketShop,
onSelector: true,
exits: []exit{
{to: names.ScreenSmallRestaurant, label: "the eating place next door", side: sideRight},
{
to: names.ScreenSecretClub, label: "the way in at the back", side: sideBack,
needs: names.FlagClubEntry,
blocked: "Just a shop, as far as the man behind the counter is concerned.",
},
},
}
}