sd:rogueima_plan
Differences
This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
| sd:rogueima_plan [2026/09/07 22:04] – external edit 127.0.0.1 | sd:rogueima_plan [2026/09/09 03:27] (current) – external edit 127.0.0.1 | ||
|---|---|---|---|
| Line 190: | Line 190: | ||
| absurdly is worth a look at the emitted bytes, and then at the trace. | absurdly is worth a look at the emitted bytes, and then at the trace. | ||
| - | ==== Step 6. A level table ==== | + | ==== Step 6. A level table — DONE ==== |
| '' | '' | ||
| Line 198: | Line 198: | ||
| entry to begin with. | entry to begin with. | ||
| - | ==== Step 7. Stairs ==== | + | ==== Step 7. Stairs |
| ''<'' | ''<'' | ||
| Line 204: | Line 204: | ||
| already in the data. | already in the data. | ||
| - | ==== Step 8. A second level ==== | + | === What landed === |
| + | |||
| + | The descriptor, which is the old '' | ||
| + | were missing: | ||
| + | |||
| + | ^ offset ^ field ^ ^ | ||
| + | | 0 | '' | ||
| + | | 1 | '' | ||
| + | | 10 | '' | ||
| + | | 12 | '' | ||
| + | | 14 | '' | ||
| + | | 16 | '' | ||
| + | | 19 | '' | ||
| + | |||
| + | Every level is built at startup and keeps its own tile array, so **levels | ||
| + | persist** -- a door you opened is still open when you come back. There is | ||
| + | nothing to gain by discarding one: a 26-level dungeon is under 170 KB. | ||
| + | |||
| + | '' | ||
| + | '' | ||
| + | and per cell. | ||
| + | |||
| + | Both commands check you are standing on the staircase, switch the descriptor, | ||
| + | and put you on the matching stair of the destination -- down arrives at the | ||
| + | level' | ||
| + | of stairs is a turn, refusing to move is not. | ||
| + | |||
| + | '' | ||
| + | coordinates had been in the data since the game was written and nothing had | ||
| + | ever drawn them. | ||
| + | |||
| + | The table has two rows. Level 2 borrows level 1's text as a placeholder -- | ||
| + | Step 8 replaces that single pointer. Its //tiles// are already separate, which | ||
| + | is what made the persistence testable: open a door on level 1, and the same | ||
| + | square on level 2 is still shut. | ||
| + | |||
| + | ==== Step 8. A second level — DONE ==== | ||
| Hand-authored, | Hand-authored, | ||
| before any of it depends on a generator. | before any of it depends on a generator. | ||
| - | ==== Step 9. DungeonMaker ==== | + | The machinery is already in place and exercised -- what is left is **content**. |
| + | One pointer in '' | ||
| + | '' | ||
| + | |||
| + | === What landed === | ||
| + | |||
| + | **BrynnWell**, | ||
| + | '' | ||
| + | the table changed, which is what Steps 6 and 7 were for. | ||
| + | |||
| + | Glyph for glyph: NetWhack' | ||
| + | its six secret doors ('' | ||
| + | command yet, so a secret door would be a wall you could never get through; they | ||
| + | can go back to being secret once something can find them. | ||
| + | |||
| + | It is a **20 x 30 level**, not a 20 x 30 maze padded out to match level 1. | ||
| + | That needed two fixes in '' | ||
| + | assuming one map size: | ||
| + | |||
| + | * the camera clamp computed '' | ||
| + | when the map is the narrower of the two. A map that fits has nowhere to | ||
| + | scroll to, so the answer is zero. | ||
| + | * the render loop drew 58 x 23 squares unconditionally, | ||
| + | edge of a narrow map it carried on into the start of the next row. | ||
| + | Squares outside the map draw as nothing now. | ||
| + | |||
| + | Maps //larger// than the view always worked -- that is the scrolling case. | ||
| + | Everything else had carried per-level dimensions since Step 6. Level 1 is | ||
| + | unaffected and provably so: its rendered screen is byte-identical before and | ||
| + | after, walked to the same square with the same door open. | ||
| + | |||
| + | Verified by rebuilding the level out of its tile array and diffing against the | ||
| + | Java source: all 30 rows match, with ''<'' | ||
| + | descriptor says. | ||
| + | |||
| + | ==== Step 9. DungeonMaker | ||
| Rooms and corridors, seeded from the RNG so a level is reproducible from its | Rooms and corridors, seeded from the RNG so a level is reproducible from its | ||
| Line 219: | Line 290: | ||
| 3,200 bytes, so a 26-level dungeon fits inside a single bank. Levels can simply | 3,200 bytes, so a 26-level dungeon fits inside a single bank. Levels can simply | ||
| persist. | persist. | ||
| + | |||
| + | === What landed === | ||
| + | |||
| + | '' | ||
| + | the middle, then repeatedly find a wall with **exactly one** floor square | ||
| + | beside it and build on the far side, leaving a door in the wall you dug | ||
| + | through. | ||
| + | |||
| + | That one-floor-neighbour rule is doing two jobs. It stops a level collapsing | ||
| + | into a single cave, and it is also why every level comes out connected: each | ||
| + | new piece is reachable through the door that made it. Connectivity is a | ||
| + | property of the construction, | ||
| + | |||
| + | Levels 3, 4 and 5 are generated, 78 x 22. '' | ||
| + | means "there is no text to expand, build it", and the generator writes | ||
| + | '' | ||
| + | exist yet cannot say where its stairs are. Generated levels persist exactly | ||
| + | like the hand-made ones. | ||
| + | |||
| + | === The corridors NetWhack never built === | ||
| + | |||
| + | '' | ||
| + | |||
| + | switch (feature_type) { | ||
| + | case 1: success = makeshop(...); | ||
| + | case 2: | ||
| + | default: success = makeroom(...); | ||
| + | } | ||
| + | |||
| + | '' | ||
| + | a room like everything else. '' | ||
| + | can produce one, but nothing ever generated one. The branch was never written | ||
| + | rather than broken -- which is why BDungeon levels are rooms hanging off rooms. | ||
| + | |||
| + | Writing it costs almost nothing, because **a corridor is a room with one | ||
| + | dimension collapsed to zero**, and '' | ||
| + | that: building EAST with height 0 gives '' | ||
| + | rooms and corridors are one routine called with different sizes, and the | ||
| + | feature roll now actually branches. | ||
| + | |||
| + | === Verification === | ||
| + | |||
| + | Dump the tile array and analyse it rather than looking at it. Level 3 came out | ||
| + | 310 floor squares with all 310 reachable by flood fill; level 4, 388 of 388. | ||
| + | Solid border, 18 and 20 doors, both staircases placed, and the two levels | ||
| + | different from each other. Corridor squares -- floor with exactly two opposite | ||
| + | floor neighbours -- numbered 70 on level 3. | ||
| ===== Part III — sight ===== | ===== Part III — sight ===== | ||
| - | ==== Step 10. Per-tile seen and visible bits ==== | + | ==== Step 10. Per-tile seen and visible bits — DONE ==== |
| Add the two flags to the tile structure from Step 5, and teach the renderer | Add the two flags to the tile structure from Step 5, and teach the renderer | ||
| Line 231: | Line 349: | ||
| small and makes a regression obvious. | small and makes a regression obvious. | ||
| - | ==== Step 11. Line of sight ==== | + | === What landed === |
| + | |||
| + | The flags have existed since Step 5, set on every square. The renderer reads | ||
| + | them now: | ||
| + | |||
| + | ^ state ^ drawn ^ colour ^ | ||
| + | | '' | ||
| + | | '' | ||
| + | | neither | not at all | -- | | ||
| + | |||
| + | They are still set on every square, so **nothing changes on screen** -- which | ||
| + | is the point. Step 11 only has to start clearing '' | ||
| + | |||
| + | Colour arrives with this step, because " | ||
| + | IBM 5153 showed it, and the indices are NetWhack' | ||
| + | light grey at 7 and dark grey at 8 in the same order, so a colour there is a | ||
| + | colour here. '' | ||
| + | staircases '' | ||
| + | |||
| + | Two traps worth writing down: | ||
| + | |||
| + | * '' | ||
| + | the colour plane with the mode's default. The colours have to go back on | ||
| + | after it, not once at startup. | ||
| + | * '' | ||
| + | plane. That is how the status panel is painted, and without setting it the | ||
| + | panel comes out dark grey on brown under a CGA palette. | ||
| + | |||
| + | Verified by dumping both planes: glyphs unchanged, colour uniformly '' | ||
| + | across all 2000 cells. Clearing '' | ||
| + | dim with its glyph intact; clearing '' | ||
| + | a gap in the wall. | ||
| + | |||
| + | ==== Step 11. Line of sight — DONE ==== | ||
| Each turn, clear '' | Each turn, clear '' | ||
| Line 243: | Line 394: | ||
| list.** A lit corridor ahead and a remembered room behind is the difference | list.** A lit corridor ahead and a remembered room behind is the difference | ||
| between a map and a dungeon. | between a map and a dungeon. | ||
| + | |||
| + | === What landed === | ||
| + | |||
| + | '' | ||
| + | a **radius of 8** rather than to the edge of the map. | ||
| + | |||
| + | '' | ||
| + | of up to 78 steps, which its own comment calls //"a terrible waste of | ||
| + | resources"// | ||
| + | at most 11 steps, some thirty times less work, and it gives a torch instead of | ||
| + | sight to the far wall. '' | ||
| + | |||
| + | The radius is **circular**: | ||
| + | '' | ||
| + | sides. Two MULs per square. | ||
| + | |||
| + | Bresenham is the error-accumulator form, which keeps every quantity | ||
| + | non-negative -- the signs live in '' | ||
| + | 8-bit wraparound. No signed comparisons, | ||
| + | testing N against V by hand. | ||
| + | |||
| + | Switching it on was **one line**: '' | ||
| + | '' | ||
| + | for. | ||
| + | |||
| + | Three things came with it: | ||
| + | |||
| + | * '' | ||
| + | what a square //is//, not whether you have been there. | ||
| + | * a known floor draws as '' | ||
| + | blank square is what "never seen" looks like -- lit floor was invisible. | ||
| + | * monsters and items are only drawn where '' | ||
| + | them through walls. | ||
| + | |||
| + | === Verification === | ||
| + | |||
| + | Dump the glyph and colour planes together and separate lit from remembered by | ||
| + | colour. In the open: a clean circle of lit floor spanning exactly '' | ||
| + | '' | ||
| + | BrynnWell' | ||
| + | |||
| + | # ##### | ||
| + | | ||
| + | # ##### | ||
| + | |||
| + | the corridor, the walls above and below it, the closed doors at each end, and | ||
| + | nothing through them. | ||
| ===== Part IV — things to carry ===== | ===== Part IV — things to carry ===== | ||
| - | ==== Step 12. Inventory ==== | + | ==== Steps 12–14. Inventory, item classes, wield and wear — DONE ==== |
| - | '' | + | Planned as three steps and built as one, because none of them is any use |
| - | items are a queue, and the machine has '' | + | alone: an item you cannot carry, a pack you cannot look in, or a suit you |
| - | exactly this — '' | + | cannot put on. All of it lives in the new '' |
| - | ==== Step 13. Item classes ==== | + | === The item table === |
| - | A shared header — kind, glyph, name pointer, weight — with a per-class payload | + | NetWhack knows what an item is from its **class**: '' |
| - | after it. **Weapons and armour first**, because | + | carries an '' |
| - | and will show the difference immediately. | + | Here the class is the '' |
| + | '' | ||
| + | inheritance. What the tag does //not// give you is the data, and that is the | ||
| + | table: | ||
| - | ==== Step 14. Wield, wear, take off ==== | + | |
| + | .equ IT_GLYPH 1 | ||
| + | .equ IT_STAT | ||
| + | .equ IT_NAME | ||
| + | .equ IT_SIZE | ||
| - | '' | + | item_table: |
| - | weapon damage, armour class. | + | .bytes 3, ')', 3, 0, @str_dagger |
| + | .bytes 4, '[', | ||
| - | ==== Step 15. Consumables and an effects hook ==== | + | '' |
| + | suit's ac modifier — exactly as '' | ||
| + | '' | ||
| + | '' | ||
| + | |||
| + | === The pack === | ||
| + | |||
| + | A second queue over the //same// fixed node array the world uses. Picking | ||
| + | something up is '' | ||
| + | the node itself never moves and nothing is copied. That is what | ||
| + | '' | ||
| + | something the moment you take it — it walks the world queue, and the node is | ||
| + | no longer on it. | ||
| + | |||
| + | === The screen === | ||
| + | |||
| + | ADOM by way of NetWhack: a full page, grouped by category, one letter per | ||
| + | item. Letters are the item's position in the pack, so an item keeps its letter | ||
| + | whichever heading it appears under. | ||
| + | |||
| + | INVENTORY | ||
| + | |||
| + | Weapons | ||
| + | a - dagger (wielded) | ||
| + | Armour | ||
| + | b - leather armor (worn) | ||
| + | |||
| + | -- press any key -- | ||
| + | |||
| + | '' | ||
| + | '' | ||
| + | debug command that drops one of each at your feet. | ||
| + | |||
| + | === What it does to combat === | ||
| + | |||
| + | '' | ||
| + | **2d(attack) against 2d(defense)**, | ||
| + | defender; wielding anything at all is worth +10 attack, armour adds its | ||
| + | '' | ||
| + | |||
| + | === Verification === | ||
| + | |||
| + | A miss proves nothing, so the rolls are called directly rather than played. | ||
| + | '' | ||
| + | memory: | ||
| + | |||
| + | hit by a monster, unarmoured | ||
| + | hit by a monster, in leather | ||
| + | damage over 1000 swings, fist 1000 (a fist is always 1) | ||
| + | damage over 1000 swings, dagger | ||
| + | |||
| + | Armour makes you harder to hit and the weapon changes the damage, which were | ||
| + | the two things this step set out to show. | ||
| + | |||
| + | === The memory map, which moved === | ||
| + | |||
| + | The step also flushed out a real bug. The game sat at '' | ||
| + | object array 12 KB above it at '' | ||
| + | '' | ||
| + | which presents as the player being unable to move, with the assembler | ||
| + | reporting nothing. | ||
| + | |||
| + | The game now loads at '' | ||
| + | '' | ||
| + | shell can run by name like any other. Everything it allocates is a whole bank | ||
| + | away, one bank per kind: | ||
| + | |||
| + | $020100 | ||
| + | $030000 | ||
| + | $040000 | ||
| + | |||
| + | The bases are labels and everything derives from them, so the second map bank | ||
| + | a real dungeon will need is one line. '' | ||
| + | that possible. | ||
| + | |||
| + | Two smaller fixes fell out of testing: '' | ||
| + | a dropped dagger vanished (it now draws every object that is not a monster), | ||
| + | and '' | ||
| + | '' | ||
| + | |||
| + | === Since then: the whole item table === | ||
| + | |||
| + | Steps 12-14 shipped with two items written by hand. '' | ||
| + | NetWhack\' | ||
| + | '' | ||
| + | '' | ||
| + | different constructor signatures come out as one table with a class tag and a | ||
| + | per-class payload. | ||
| + | |||
| + | **'' | ||
| + | of equipment slots an item may occupy: gloves name '' | ||
| + | a dagger names '' | ||
| + | '' | ||
| + | gloves on took your plate off, and the only symptom was a defence number that | ||
| + | went **down** when you added armour. There is an equipment rack now, one | ||
| + | pointer per slot, and '' | ||
| + | |||
| + | defence 10 bare -> 60 in field plate -> 64 in plate and gloves | ||
| + | |||
| + | === The equipment page === | ||
| + | |||
| + | '' | ||
| + | once, lettered A to M. Press a slot: something in it comes off, an empty one | ||
| + | offers the things that fit **that slot and nothing else**, which is | ||
| + | '' | ||
| + | goes and cannot show you what is empty, which is the question a player has. | ||
| + | |||
| + | === Identification === | ||
| + | |||
| + | Only potions and scrolls have a fake name -- '' | ||
| + | '' | ||
| + | |||
| + | Two things about '' | ||
| + | swaps the fakename **and the colour** together, so an appearance is a | ||
| + | (name, colour) //pair// -- get it wrong and you have a milky potion drawn in | ||
| + | green. And '' | ||
| + | one potion of healing and every potion of healing is named for the rest of the | ||
| + | game. Neither can live in the generated table, so both are a byte per kind in | ||
| + | the data bank. | ||
| + | |||
| + | Names are therefore **built**, not stored: " | ||
| + | potion" | ||
| + | |||
| + | === Paging === | ||
| + | |||
| + | '' | ||
| + | goes through, and, since the shops use it too, worth having once. Letters | ||
| + | restart at '' | ||
| + | thing I can see" and must not depend on how long the list is. | ||
| + | |||
| + | === A bug worth writing down === | ||
| + | |||
| + | Two commits of new variables were placed on top of existing ones in the | ||
| + | hand-managed variable page. '' | ||
| + | filter was overwritten halfway through drawing the inventory and **the weapons | ||
| + | and armour vanished from it**. Fourteen overlaps in all, no assembler warning, | ||
| + | and the symptom in a routine that looked innocent. | ||
| + | |||
| + | '' | ||
| + | comments and reports overlaps. Run it after adding one. | ||
| + | |||
| + | ==== Step 15. Consumables and an effects hook — HALF DONE ==== | ||
| Food, potions, scrolls, and a small effect dispatch table they trigger. | Food, potions, scrolls, and a small effect dispatch table they trigger. | ||
| - | NetWhack | + | |
| - | start. | + | The **things** all exist: importing |
| + | potions | ||
| + | identification system (see below). '' | ||
| + | gives you its nutrition -- which for poison is // | ||
| + | |||
| + | What is left is the **effects**: | ||
| + | town portal. NetWhack has an '' | ||
| + | '' | ||
| + | Effect.T_FOOD)'' | ||
| + | with a different name on it. | ||
| ===== Part V — pressure ===== | ===== Part V — pressure ===== | ||
| - | ==== Step 16. Hunger ==== | + | ==== Step 16. Hunger, as an energy meter — DONE ==== |
| + | |||
| + | A food clock, and '' | ||
| + | that changes how the game is played: it is the reason to go down rather than | ||
| + | explore forever. | ||
| + | |||
| + | === Inverted === | ||
| + | |||
| + | This is NetWhack' | ||
| + | '' | ||
| + | |||
| + | pc.hunger++; | ||
| + | if (pc.hunger > 5000) { ... } | ||
| + | if (pc.hunger > 4000) { ... } else if (pc.hunger > 3200) { ... } | ||
| + | |||
| + | Here the same number counts **down** from '' | ||
| + | They are the same meter — every threshold is 5000 minus NetWhack' | ||
| + | down-counting one can go on the status panel and be read without explaining. | ||
| + | //Energy: 800// says you are nearly out of something. //Hunger: 4200// does | ||
| + | not. | ||
| + | |||
| + | ^ NetWhack ^ Rogueima ^ says ^ odds ^ | ||
| + | | hunger > 800 | energy < 4200 | You're feeling a might peckish. | 1 in 500 | | ||
| + | | hunger > 1600 | energy < 3400 | Your stomach is grumbling. | ||
| + | | hunger > 2400 | energy < 2600 | You are feeling hungry. | ||
| + | | hunger > 3200 | energy < 1800 | You are feeling very hungry. | ||
| + | | hunger > 4000 | energy < 1000 | You are starving! | ||
| + | | hunger > 5000 | energy == 0 | starving.. TO DEATH! (-1 hp) | 1 in 50 | | ||
| + | |||
| + | Two consequences of the inversion, both small: | ||
| + | |||
| + | * eating **adds and clamps at the top** rather than subtracting toward zero, | ||
| + | so the bug to avoid is an unsigned overflow rather than an underflow. | ||
| + | NetWhack lets hunger go negative on a big meal; a meter has a maximum. | ||
| + | * starving is energy **at** zero rather than hunger past 5000. NetWhack' | ||
| + | hunger keeps climbing past its own limit and nothing reads it up there, so | ||
| + | nothing is lost by parking at the bottom instead. | ||
| + | |||
| + | === The clock === | ||
| + | |||
| + | NetWhack calls '' | ||
| + | its '' | ||
| + | Rogueima' | ||
| + | is spent, so the modulo is the identity here — '' | ||
| + | a turn. | ||
| + | |||
| + | At one point of drain per turn, a full meter is **five thousand turns**. That | ||
| + | is deliberately a minor concern for now: it is there, it works, and the numbers | ||
| + | are the thing to argue about when balance is the concern. | ||
| + | |||
| + | === Eating === | ||
| + | |||
| + | '' | ||
| + | is a **row in '' | ||
| + | bought. NetWhack' | ||
| + | '' | ||
| + | hp back. | ||
| + | |||
| + | Disposal is the fiddly part, and it is not a '' | ||
| + | finds a free node by scanning the **world** queue for one with a zero id, so an | ||
| + | item taken off the pack queue and put on no queue at all is leaked rather than | ||
| + | freed. Eating therefore puts the node back on the world queue and only then | ||
| + | zeroes the id — the same disposal '' | ||
| + | |||
| + | === Verification === | ||
| + | |||
| + | Five thousand turns of drain is not something you can sit through, and a | ||
| + | complaint that fires one turn in five hundred is not something you can see by | ||
| + | playing. '' | ||
| + | **pinned** at a chosen level, which holds it inside one band of the chain, and | ||
| + | asks whether that band says anything — by clearing the message buffer first and | ||
| + | counting the non-zero bytes after. | ||
| + | |||
| + | drain past the bottom | ||
| + | hp lost at empty over 5000 turns | ||
| + | messages at energy 4300 0 bytes (above every band: silent) | ||
| + | messages at 4100/ | ||
| + | |||
| + | The silent row is the one that matters as much as the loud ones: it pins the | ||
| + | '' | ||
| + | |||
| + | And in the real loop, poked down to 30 and left to walk: energy floored at 0 | ||
| + | and hp went 10 → 6 while starving. | ||
| + | |||
| + | === A bug it turned up === | ||
| + | |||
| + | '' | ||
| + | un-pickable the day it was added. That is the same shape as '' | ||
| + | ever drawing gold, found in Step 12. Both now name the one type they **exclude** | ||
| + | — a new kind of item should not need a line in either place. | ||
| + | |||
| + | ==== Step 17. Regeneration and death — HALF DONE ==== | ||
| + | |||
| + | **Regeneration is in.** '' | ||
| + | hundred you mend a point, and NetWhack\' | ||
| + | second point off the energy meter. That is the link that makes food matter for | ||
| + | something other than eventually dying of it — **a wound is paid for in | ||
| + | rations**. | ||
| + | |||
| + | One addition to NetWhack: it needs '' | ||
| + | and mending at once is the single combination that lets a player wait out any | ||
| + | wound for free, because the waiting is what heals them. Verified: **0** hit | ||
| + | points healed over ten thousand turns below the line. | ||
| - | A food clock, and '' | + | The cost is only taken when a point actually goes back. NetWhack rolls, calls |
| - | the reason | + | '' |
| + | unwounded player pays a point of food for nothing. That is an artifact of two | ||
| + | statements sitting next to each other, not a design. | ||
| - | ==== Step 17. Regeneration | + | Healing needed something to stop at, so the player has an '' |
| + | path that gives points back goes through one clamping routine. | ||
| - | HP returning slowly with time, resting to pass turns safely, and a proper death | + | What is left: **resting** to pass turns safely, and a proper |
| - | — tombstone, final score, and a return to the prompt | + | tombstone, final score, and a return to the prompt. Starvation and monsters |
| + | both drop into '' | ||
| ===== Part VI — a world worth returning to ===== | ===== Part VI — a world worth returning to ===== | ||
| - | ==== Step 18. A monster table ==== | + | ==== Step 18. A monster table — DONE ==== |
| - | Replace hardcoded monsters | + | There used to be '' |
| - | depth range, behaviour flags. NetWhack' | + | routines, each with its own hit-point roll and its own hard-coded glyph, and a |
| - | would already | + | '' |
| + | fourth monster meant writing a fourth routine.** | ||
| + | |||
| + | Now there is one routine and a table. '' | ||
| + | NetWhack' | ||
| + | |||
| + | === Generated, not transcribed === | ||
| + | |||
| + | The table is produced by '' | ||
| + | Java directly — resolving '' | ||
| + | CGA indices, '' | ||
| + | ''" | ||
| + | exactly what gets copied wrong by hand, and NetWhack' | ||
| + | again. Re-run it; do not edit the output. | ||
| + | |||
| + | '' | ||
| + | came across as-is. | ||
| + | |||
| + | === The record === | ||
| + | |||
| + | MO_GLYPH | ||
| + | MO_COLOR | ||
| + | MO_LEVEL | ||
| + | |||
| + | An instance keeps two things: '' | ||
| + | change, and '' | ||
| + | read back out of the table from that kind, so an instance costs no more than | ||
| + | it did. | ||
| + | |||
| + | === What is wired === | ||
| + | |||
| + | * **glyph, colour, name.** Colour is not decoration: 28 monsters share 6 | ||
| + | glyphs, and without it a dog and a wolf are the same '' | ||
| + | * **level** — hit points | ||
| + | '' | ||
| + | '' | ||
| + | so the level term is what is left. | ||
| + | * **armour class**, added to the defence, as '' | ||
| + | * **probability**, | ||
| + | |||
| + | Every monster used to rate a flat 10 — which is exactly what '' | ||
| + | comes to at level 1. So **the first floor plays as it always did** and the | ||
| + | deeper ones get harder, which is what importing the levels was for. | ||
| + | |||
| + | Spawning follows '' | ||
| + | '' | ||
| + | it. Floors 1 and 2 hold level-1 monsters only; by floor 9 it is anything up to | ||
| + | level 5. There is no experience system yet, so the player' | ||
| + | depth widens the window alone. | ||
| + | |||
| + | === What was left behind === | ||
| + | |||
| + | All of it is **imported**; | ||
| + | |||
| + | * '' | ||
| + | 1500 and a phase rat at 400 currently move at the same rate. This is the | ||
| + | big one, and it is a scheduler, not a data problem — which is the point of | ||
| + | importing the numbers now. | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | random** from the list, which this does, so a wererat' | ||
| + | salamander' | ||
| + | distinction in the message. | ||
| + | |||
| + | Row 0 is the player. Its probability is 0 so it can never be picked; it is | ||
| + | kept so an index here is an index into '' | ||
| + | |||
| + | === Verification === | ||
| + | |||
| + | A wrong offset in a 29-byte record reads as a monster that is subtly too | ||
| + | strong, not as a crash, so '' | ||
| + | |||
| + | | ||
| + | depth 10 level window | ||
| + | salamander hit points | ||
| + | sewer rat damage | ||
| + | battle orc damage, highest | ||
| + | salamander attack / defence | ||
| + | wererat attacks in the table 2 | ||
| + | orc damage over 200 real swings | ||
| + | |||
| + | The last one goes through '' | ||
| + | directly, because that is where a register clobbered across a call shows up. | ||
| + | And one did: **'' | ||
| + | and using '' | ||
| + | sixteen bits of the pointer. Every field then read as zero, the code took its | ||
| + | "no attacks" | ||
| + | all** — with no crash and no assembler warning. '' | ||
| ==== Step 19. Pathfinding ==== | ==== Step 19. Pathfinding ==== | ||
| Line 300: | Line 850: | ||
| '' | '' | ||
| A high score table after that. | A high score table after that. | ||
| + | |||
| + | ===== Part VII — saying it properly ===== | ||
| + | |||
| + | ==== Step 21. Item names in messages - DONE ==== | ||
| + | |||
| + | Everything says //"You wield it."// The item knows its name; the message does | ||
| + | not ask. NetWhack has a small naming layer that every message goes through: | ||
| + | |||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | |||
| + | so '' | ||
| + | build names for potions and scrolls; this generalises that and routes the | ||
| + | messages through it. | ||
| + | |||
| + | **Do this first.** It is small, it is the difference between a prototype and a | ||
| + | game every single turn, and everything below it wants to say something. | ||
| + | |||
| + | The message window is 19 columns and wraps mid-word, so this also wants a | ||
| + | word-wrapping '' | ||
| + | being spliced into the middle of them. | ||
| + | |||
| + | ===== Part VIII — things that do something ===== | ||
| + | |||
| + | ==== Step 22. Effects, as three things instead of one - DONE ==== | ||
| + | |||
| + | === Why not NetWhack' | ||
| + | |||
| + | '' | ||
| + | There is an '' | ||
| + | a '' | ||
| + | twelve fields -- | ||
| + | |||
| + | ticks uses trigger | ||
| + | parent | ||
| + | |||
| + | -- of which three ('' | ||
| + | whose meaning changes with the type, and **four separate '' | ||
| + | statements** in the same file: '' | ||
| + | '' | ||
| + | adding one means editing all four. | ||
| + | |||
| + | It is also inconsistent with itself about the same job. '' | ||
| + | computed on read -- its '' | ||
| + | comment saying it is handled in '' | ||
| + | three cases away, caches: '' | ||
| + | remove. **Two opposite strategies for the same job in one switch.** | ||
| + | |||
| + | A table row tells you when you are finished: the row is full. An object with | ||
| + | three general-purpose registers and no schema never does. That is what makes | ||
| + | the design feel open-ended -- there is no point at which it says //done//, so | ||
| + | every new effect reopens the whole question. | ||
| + | |||
| + | === It was doing three unrelated jobs === | ||
| + | |||
| + | ^ NetWhack' | ||
| + | | '' | ||
| + | | '' | ||
| + | | '' | ||
| + | |||
| + | The pie is the tell. It became an Effect because Effect was the only hook | ||
| + | available; the system attracted things that did not belong in it. | ||
| + | |||
| + | So the three are split, and each goes where the game already keeps that kind | ||
| + | of thing. | ||
| + | |||
| + | === 1. Continuous modifiers: computed, never stored === | ||
| + | |||
| + | '' | ||
| + | adding terms up. A condition is **one more term in that walk**. Nothing is | ||
| + | applied and nothing has to be un-applied. | ||
| + | |||
| + | That deletes a whole class of bug. '' | ||
| + | wrong for ever if the effect is removed twice, or if the value changes while | ||
| + | it is worn, or if a save reorders things. A number computed on read cannot | ||
| + | desynchronise. NetHack computes AC from worn armour every time for exactly | ||
| + | this reason. | ||
| + | |||
| + | === 2. Timed conditions: a flat array of counters === | ||
| + | |||
| + | '' | ||
| + | '' | ||
| + | '' | ||
| + | no '' | ||
| + | |||
| + | .equ CD_NAME | ||
| + | .equ CD_START | ||
| + | .equ CD_END | ||
| + | .equ CD_SIZE | ||
| + | |||
| + | .equ C_BLIND | ||
| + | .equ C_FAST | ||
| + | |||
| + | That is the schema the Effect class never had, and **a row is finished when | ||
| + | those three pointers are filled in**. | ||
| + | |||
| + | The API is four routines: '' | ||
| + | '' | ||
| + | message when the condition was not already true, so a second potion of speed | ||
| + | lengthens the haste rather than announcing it twice. | ||
| + | |||
| + | === Read where it matters, applied nowhere === | ||
| + | |||
| + | This is the part worth keeping hold of. A condition is never pushed into | ||
| + | anything -- the one place that cares **asks**: | ||
| + | |||
| + | * **Blindness** is asked about in '' | ||
| + | square you stand on. NetWhack does '' | ||
| + | '' | ||
| + | * **Haste** is asked about in '' | ||
| + | the clock. NetWhack' | ||
| + | every turn //from inside the effect// -- the same idea pushed the other | ||
| + | way round, the effect reaching into the mobile rather than the mobile | ||
| + | asking the effect. | ||
| + | |||
| + | === 3. One-shot moments: the item's own routine === | ||
| + | |||
| + | '' | ||
| + | '' | ||
| + | you drank it, and does not need to become an object with a lifecycle first. | ||
| + | Each potion' | ||
| + | |||
| + | Nutrition is not in there -- every potion has some and '' | ||
| + | already applied it, which is why a potion of water does nothing else at all. | ||
| + | |||
| + | === Verification === | ||
| + | |||
| + | blind on after cond_set | ||
| + | blind on after 5 ticks | ||
| + | action, hasted | ||
| + | poison, 2d6 from 20 | ||
| + | speed from potion | ||
| + | |||
| + | and in the real game, poked blind and stepped: lit floor **194 to 0**, all 195 | ||
| + | squares still remembered in dark grey, items drawn **2 to 0**. You keep the | ||
| + | map you know and see none of it. | ||
| + | |||
| + | === What is left === | ||
| + | |||
| + | '' | ||
| + | case when they are wanted. The nine NetWhack effect types are otherwise | ||
| + | covered, with less machinery than the Effect class alone. | ||
| + | |||
| + | ==== Step 23. The speed system - DONE ==== | ||
| + | |||
| + | '' | ||
| + | mole is 1500, a phase rat 400, the player 1000 — and **nothing reads either**. | ||
| + | Every mobile moves once per turn, so a phase rat is exactly as quick as a | ||
| + | rock mole. | ||
| + | |||
| + | NetWhack' | ||
| + | added by increasing a speed variable and only allowing movement when it | ||
| + | reaches a certain value (then resetting it)"// | ||
| + | topped up each turn by its speed, and it acts while it can afford to. That is | ||
| + | also what turns '' | ||
| + | '' | ||
| + | finer. | ||
| + | |||
| + | This is the one imported field that changes how the game //plays// rather than | ||
| + | how it reads, and the potion of speed has nothing to do without it. | ||
| + | |||
| + | **Taken before Step 22**, because half of the '' | ||
| + | '' | ||
| + | until a clock exists. | ||
| + | |||
| + | === There is no event queue === | ||
| + | |||
| + | Worth writing down, because it is easy to misremember: | ||
| + | global event list at all.** No event class, nothing scheduled centrally. | ||
| + | '' | ||
| + | |||
| + | m.per_tick(); | ||
| + | m.action_time--; | ||
| + | if (m.action_time > 0) return; | ||
| + | ...act... | ||
| + | m.action_time += m.movespeed; | ||
| + | |||
| + | per mobile per gametick, and effects live in '' | ||
| + | owns them, processed by trigger and swept by '' | ||
| + | an item into the engine. So the counter-per-mobile is kept exactly as it is. | ||
| + | |||
| + | === What is not kept: the ticking === | ||
| + | |||
| + | '' | ||
| + | Java that is free. Twenty mobiles x a thousand ticks x ten instructions is | ||
| + | **200,000 instructions a turn** -- about 154ms at the 1.30M instructions/ | ||
| + | this machine measures at, on top of the 48ms Step 11 already costs, and it | ||
| + | grows with the monster count. | ||
| + | |||
| + | So time does not advance one unit at a time. The **smallest** counter is | ||
| + | found, that much is taken off every counter at once, and whoever reaches zero | ||
| + | acts. Identical arithmetic -- 900 still acts more often than 1000 -- for one | ||
| + | pass per action instead of a thousand passes per turn. The 1000 scale is kept; | ||
| + | the resolution lives in the arithmetic, not in the loop count. | ||
| + | |||
| + | === A field, not a list === | ||
| + | |||
| + | '' | ||
| + | list, and that is the important choice. A list needs entries removed when a | ||
| + | monster dies, a level changes, or a node is recycled -- and a missed removal | ||
| + | is a stale event pointing at whatever now occupies that node. **A counter in | ||
| + | the record dies with the record.** It is also nearly free: the world queue is | ||
| + | already walked every turn by '' | ||
| + | '' | ||
| + | anyway. Only '' | ||
| + | |||
| + | '' | ||
| + | routine has been paid for. Otherwise one wedged against a wall never advances | ||
| + | its counter and the scheduler hands it every turn for ever. | ||
| + | |||
| + | A turn is still '' | ||
| + | wandering-monster roll fire exactly as often as they did -- a loop rather than | ||
| + | an if, since one slow action can cross two turns. | ||
| + | |||
| + | === Verification === | ||
| + | |||
| + | The claim is a ratio, so it is counted rather than looked at. 400 scheduler | ||
| + | steps with three actors: | ||
| + | |||
| + | player | ||
| + | phase rat | ||
| + | rock mole (1500) | ||
| + | |||
| + | and in the real loop with twenty monsters running, 97 turns left the energy | ||
| + | meter at exactly 5000 - 97. | ||
| + | |||
| + | '' | ||
| + | rather than 110. | ||
| + | |||
| + | ==== Step 24. Potions that do what they say - DONE ==== | ||
| + | |||
| + | Healing, poison, blindness and speed, all four landed with Step 22, since | ||
| + | '' | ||
| + | case per potion, each readable in one piece. | ||
| + | |||
| + | healing | ||
| + | poison | ||
| + | blindness | ||
| + | speed C_FAST for 100 turns | ||
| + | water nothing at all -- its 100 nutrition is the whole of it | ||
| + | |||
| + | Blindness did **not** want '' | ||
| + | before it was written. '' | ||
| + | the square you stand on -- a temporarily-modified global would have to be put | ||
| + | back, and putting things back is the failure mode Step 22 exists to avoid. | ||
| + | |||
| + | ==== Step 25. Scrolls, and '' | ||
| + | |||
| + | '' | ||
| + | one case per scroll, each readable in one piece. | ||
| + | |||
| + | ^ scroll ^ what it does ^ | ||
| + | | identify | learn what one thing in your pack is | | ||
| + | | teleport | somewhere else on this level | | ||
| + | | town portal | says so -- there is no town until Step 30 | | ||
| + | | crumpled note | reads it, and does **not** vanish | | ||
| + | | temporal suspension | everything else waits 5 to 20 turns | | ||
| + | |||
| + | Reading identifies the scroll, so the message uses the name it had **before** | ||
| + | you read it: //"You read the scroll of gibberish."// | ||
| + | self-identifies the teleport one, inside '' | ||
| + | omission rather than a decision -- you plainly learn what a scroll was by | ||
| + | watching what it did. | ||
| + | |||
| + | === Temporal suspension is not a condition === | ||
| + | |||
| + | NetWhack does '' | ||
| + | credit so that everyone else has to wait. Our counters are unsigned and count | ||
| + | **down**, so the same thing is expressed from the other side: everybody else | ||
| + | is pushed back by that much. Identical in effect, and it cannot go negative. | ||
| + | |||
| + | It also **must not** be a condition that makes the player' | ||
| + | '' | ||
| + | advance, so '' | ||
| + | -- and the suspension would never end. A condition has to be something the | ||
| + | clock can outlive. | ||
| + | |||
| + | === A bug inherited and not copied === | ||
| + | |||
| + | '' | ||
| + | its list of candidates with | ||
| + | |||
| + | if (i.identified == false); | ||
| + | a_list.add(i); | ||
| + | |||
| + | -- a stray semicolon, so the '' | ||
| + | itself telling you about something you already knew. | ||
| + | |||
| + | === Verification === | ||
| + | |||
| + | unknown kinds in the pack 2 -> 1 | ||
| + | teleport moved the player | ||
| + | clock added by suspension | ||
| + | |||
| + | === Two hazards of the assembler, found the hard way === | ||
| + | |||
| + | '' | ||
| + | takes both without a word. The first made identify choose nothing; the second | ||
| + | stored garbage into '' | ||
| + | wall -- and only showed up because the test asked whether the destination was | ||
| + | walkable rather than only whether he had moved. '' | ||
| + | '' | ||
| + | |||
| + | ==== Step 26. Blessed, cursed, uncursed ==== | ||
| + | |||
| + | Three bits per item and '' | ||
| + | that will not come off is the first thing in the game that can go **wrong** | ||
| + | in an interesting way. | ||
| + | |||
| + | ===== Part IX — a world ===== | ||
| + | |||
| + | ==== Step 27. Tiles store their kind - DONE ==== | ||
| + | |||
| + | A square used to store **the character it looked like**. It stores its | ||
| + | **kind** now, and the glyph is one column of a generated table along with the | ||
| + | colour, the flags, the name and the description. Same two bytes per square. | ||
| + | |||
| + | === Why this had to come before the village === | ||
| + | |||
| + | '' | ||
| + | and wall, secret door and altar **all as ''#'''' | ||
| + | cannot tell a bridge from a chair -- so it cannot say whether you may walk on | ||
| + | it, what colour to draw it, or what it is called when you look at it. | ||
| + | |||
| + | The map SOURCE alphabet is a different thing and is unambiguous: | ||
| + | '' | ||
| + | '' | ||
| + | as text at all. '' | ||
| + | one kind; only the DISPLAY collides. | ||
| + | |||
| + | === The table === | ||
| + | |||
| + | '' | ||
| + | |||
| + | .equ TI_GLYPH | ||
| + | .equ TI_COLOR | ||
| + | .equ TI_FLAGS | ||
| + | .equ TI_TNAME | ||
| + | .equ TI_DESC | ||
| + | .equ TI_TSIZE | ||
| + | |||
| + | The glyph, colour, name and description come from '' | ||
| + | not** -- walkable and vblock are set by a switch in '' | ||
| + | are not in the table at all. The generator reads both files, so the two cannot | ||
| + | drift apart. | ||
| + | |||
| + | Two deliberate departures, both made in the generator where they are visible | ||
| + | rather than in the data where they would look like the source: | ||
| + | |||
| + | * Four kinds are drawn with **box-drawing characters** and two with a | ||
| + | **space**. Neither survives an ASCII renderer, and a space is what an | ||
| + | unseen square looks like -- a shop floor drawn as one would be invisible. | ||
| + | Those six get stated ASCII stand-ins. | ||
| + | * A **wall is light grey**, not '' | ||
| + | exactly the colour a remembered square is drawn in. A secret door matches | ||
| + | the wall it is pretending to be. | ||
| + | |||
| + | === What it cost === | ||
| + | |||
| + | '' | ||
| + | floor -- as far as two glyphs could take it. '' | ||
| + | '' | ||
| + | '' | ||
| + | kinds. '' | ||
| + | '' | ||
| + | |||
| + | === And Brynn === | ||
| + | |||
| + | The village is the first level: '' | ||
| + | trimming each row to the 84 columns it declares -- the Java rows are one | ||
| + | character longer. '' | ||
| + | |||
| + | Its well is **not in the map text**; NetWhack adds it in code. Read the code | ||
| + | and not the comment above it: the comment says //"Add the well at 24, | ||
| + | the three lines below say '' | ||
| + | the level' | ||
| + | |||
| + | === Where does NetWhack put the player? Nowhere === | ||
| + | |||
| + | This is worth writing down because it cannot be found by looking for it. | ||
| + | '' | ||
| + | |||
| + | public int px_last = 0, py_last = 0; | ||
| + | |||
| + | and line 519 does '' | ||
| + | answer is the field initialiser: | ||
| + | Ours starts on the road at **2,6**, which is a decision rather than an | ||
| + | accident. | ||
| + | |||
| + | That needed a new field. '' | ||
| + | are, and where you appear -- and Brynn has no stairs up, so the two had to | ||
| + | come apart. '' | ||
| + | |||
| + | **0,0 means "no such staircase." | ||
| + | coordinates are 0,0, that being the one square no real staircase can occupy -- | ||
| + | the map corner on any level with a border. This replaced a test on | ||
| + | '' | ||
| + | '' | ||
| + | stale value. (It does now.) | ||
| + | |||
| + | Checked on the tile array rather than by eye: exactly one staircase on Brynn, | ||
| + | kind 7 at (4,28), and no kind 6 anywhere. | ||
| + | |||
| + | The 80 x 40 scratch room is gone, and so are the starting dagger, armour and | ||
| + | ration -- scaffolding for testing the item system, which the shops will | ||
| + | replace. '' | ||
| + | |||
| + | Trees draw **green**, water **blue**, roads **brown**, flowers **grey**, each | ||
| + | in a lit and a remembered shade. None of that was expressible before. | ||
| + | |||
| + | === What Brynn still has not got === | ||
| + | |||
| + | The map only. The '' | ||
| + | Java are Step 28, and the people they describe are Steps 31 and 32. The | ||
| + | village is a place; it is not yet inhabited. | ||
| + | |||
| + | ==== Step 28. A level script, and people who answer questions - DONE ==== | ||
| + | |||
| + | A level' | ||
| + | |||
| + | DENIZEN Farmer_Jim 19 17 | ||
| + | CHAT The_apples_look_lovely_this_year. | ||
| + | REPLY job | ||
| + | REPLY name I' | ||
| + | |||
| + | '' | ||
| + | '' | ||
| + | inside a token -- so **Brynn' | ||
| + | and '' | ||
| + | |||
| + | === Why a directive and not punctuation === | ||
| + | |||
| + | '' | ||
| + | and the answer can contain as many more as it likes, so no escape is needed | ||
| + | for the colon at all -- a keyword is one word and cannot contain one. | ||
| + | |||
| + | Two things argued against it: | ||
| + | |||
| + | * The format already has **exactly one escape**, '' | ||
| + | backslash rule would be a second one to learn, and every directive added | ||
| + | later would inherit both. | ||
| + | * Inferring the //kind// of speech from a punctuation mark cannot tell a | ||
| + | keyword from a random line that happens to contain a colon. | ||
| + | |||
| + | A directive says which it is instead of leaving it to be guessed, and the next | ||
| + | thing we want -- '' | ||
| + | another mark. | ||
| + | |||
| + | === Talking: Ultima IV's three cases === | ||
| + | |||
| + | NetWhack has only the middle one. An NPC there holds a list of lines and says | ||
| + | one at random; there is no interactive speech in it anywhere. The other two | ||
| + | are Rogueima' | ||
| + | |||
| + | ^ case ^ what happens ^ | ||
| + | | nothing to say | //" | ||
| + | | random speech | Farmer Jim says, "The apples look lovely this year." | | ||
| + | | keyword replies | a page of its own, and you type words at it | | ||
| + | |||
| + | Farmer Jim -- and you are talking to them. | ||
| + | |||
| + | I work the orchard. Apples, mostly. It's a living. | ||
| + | |||
| + | Ask about a subject -- try NAME, or JOB. BYE to stop. | ||
| + | Say: | ||
| + | |||
| + | An unknown word gets //" | ||
| + | an empty line ends it. Matching folds case, so '' | ||
| + | same question. | ||
| + | |||
| + | '' | ||
| + | code**, so it can be checked without reading a screen. Replies win over chats: | ||
| + | somebody who will hold a conversation should not be reduced to muttering one | ||
| + | line at you. | ||
| + | |||
| + | === Nothing copies text === | ||
| + | |||
| + | The script stays in the program image and an NPC holds **pointers into it**. | ||
| + | '' | ||
| + | than rewriting the source the way '' | ||
| + | be built twice without its script having been consumed. A keyword ends at the | ||
| + | space that follows it in the script, since it has no terminator of its own. | ||
| + | |||
| + | === Verification === | ||
| + | |||
| + | denizens created | ||
| + | keyword replies | ||
| + | " | ||
| + | |||
| + | The three modes are read off one denizen stripped in stages: with both, it | ||
| + | converses; with its replies taken away it falls back to random; with its chats | ||
| + | gone too it has nothing to say. | ||
| + | |||
| + | === An ordering bug === | ||
| + | |||
| + | '' | ||
| + | script put four denizens into the node array and '' | ||
| + | emptied it again -- '' | ||
| + | Everything a level writes into has to exist before the level is built. | ||
| + | |||
| + | ==== Step 29. Branches, and stairs that know where they go ==== | ||
| + | |||
| + | The dungeon is not one stack. A staircase carries a **destination branch**: | ||
| + | |||
| + | Brynn --(the well)--> BrynnWell --> LCave | ||
| + | --> BDungeon --(depth 9)--> Croky Castle | ||
| + | |||
| + | '' | ||
| + | it; '' | ||
| + | as you left them. Our '' | ||
| + | become that. | ||
| + | |||
| + | ==== Step 30. The village of Brynn ==== | ||
| + | |||
| + | Depth 0, '' | ||
| + | The well in the middle is a staircase into '' | ||
| + | and 29 first, and then it is mostly data. | ||
| + | |||
| + | ==== Step 31. NPCs and chat ==== | ||
| + | |||
| + | '' | ||
| + | say; '' | ||
| + | says '' | ||
| + | neither the player nor an enemy. | ||
| + | |||
| + | The four denizens of Brynn are how the sunsword quest is told: Farmer Jim, the | ||
| + | Mayor, Edna and Father Monoly each know a piece of it. | ||
| + | |||
| + | ==== Step 32. Shopkeepers ==== | ||
| + | |||
| + | '' | ||
| + | with something you have not paid for. The paged list it uses is | ||
| + | '' | ||
| + | this is the shop logic and not the shop interface. '' | ||
| + | in the item table unread since the import, waiting for exactly this. | ||
| + | |||
| + | ===== Part X — the quest ===== | ||
| + | |||
| + | ==== Step 33. Croky Castle ==== | ||
| + | |||
| + | The goal level, and the only one with no stairs down. Reached from '' | ||
| + | depth 9, but only while the sunsword has not been found. | ||
| + | |||
| + | ==== Step 34. The sunsword ==== | ||
| + | |||
| + | '' | ||
| + | **placed**. Wielding it sets '' | ||
| + | seems to glow and gleam with an unearthly light!"//, | ||
| + | **attracts monsters**: '' | ||
| + | turns with //" | ||
| + | |||
| + | The first unique object in the game, and the first item whose being carried | ||
| + | changes the rules. | ||
| + | |||
| + | ==== Step 35. The endgame ==== | ||
| + | |||
| + | Carry it back to the surface. '' | ||
| + | staircase, whether you have the sunsword and are standing in '' | ||
| + | if so, +1000 and //"You have escaped the dungeons of doom!"// | ||
| + | condition, and it is four lines. It needs Step 17's death screen to exist, | ||
| + | because winning and dying print the same tombstone. | ||
| + | |||
| + | ===== Part XI — depth, once it is a game ===== | ||
| + | |||
| + | ==== Step 36. Traps ==== | ||
| + | |||
| + | Six: trapdoor, bear trap, teleport, dart, sleeping gas, rust. A tile flag, a | ||
| + | kind, and a hidden bit. | ||
| + | |||
| + | ==== Step 37. Search, and secret doors ==== | ||
| + | |||
| + | '' | ||
| + | doors turned into ordinary ones back in Step 8 //because there was no search | ||
| + | command//; this is the step that lets them go back. | ||
| + | |||
| + | ==== Step 38. Experience, and the stat block ==== | ||
| + | |||
| + | '' | ||
| + | have the terms missing — '' | ||
| + | + bonuses'', | ||
| + | things should raise a level, and the monster spawn window already reads | ||
| + | '' | ||
| + | |||
| + | ==== Step 39. LCave, and a third kind of level ==== | ||
| + | |||
| + | The cave generator, skipped in Step 9. Gives '' | ||
| + | to that is not more of the same. | ||
| + | |||
| + | ==== Step 40. Saving ==== | ||
| + | |||
| + | '' | ||
| + | services already exist behind '' | ||
| + | looks: what is saved is a graph of pointers into a fixed node array, so it | ||
| + | saves as indices or not at all. | ||
| + | |||
| + | ==== Step 41. The rest of the flavour ==== | ||
| + | |||
| + | '' | ||
| + | throne. None of it is systems work; all of it is what makes the place feel | ||
| + | like somewhere rather than a grid. | ||
| ===== If only three steps get done ===== | ===== If only three steps get done ===== | ||
| Line 306: | Line 1459: | ||
| of sight. That is the difference between a demo and a roguelike, and none of | of sight. That is the difference between a demo and a roguelike, and none of | ||
| the three is large. | the three is large. | ||
| + | |||
| + | ===== And if only three MORE get done ===== | ||
| + | |||
| + | **Steps 21, 22 and 29** — names in messages, the effects hook, and branching | ||
| + | stairs. The first makes every turn read like a game; the second is what every | ||
| + | consumable in the table is waiting for; the third is the shape the whole rest | ||
| + | of the world hangs off. | ||
sd/rogueima_plan.1788818654.txt.gz · Last modified: by 127.0.0.1
