Dialog Wishlist

From your description, the veneer code seems in the scope of the rationale of the GLPL (but IANAL &c. &c. :wink: aside that I’m unsure to having explained well my reasoning… :smiley: )

Best regards from Italy,
dott. Piergiorgio.

Unfortunately, to my understanding, the exceptions in the LGPL specifically require the two components (i.e. the program and the library) to be separable, so that you can swap out the LGPL-licensed component with a replacement without having to recompile them both. And that’s just not possible with the Z-machine.

That also wouldn’t necessarily help us here, because you can’t use GPL-licensed code in a LGPL-licensed project.

I’m open to discussing the merits of different licenses, but as the de facto project leader (and someone who doesn’t really want to be a manager) I’m hesitant to do anything that requires me coordinating all the contributors or maintaining different licenses on different source files, as well as firmly unwilling to do anything that limits how users of the compiler can license their games.

2 Likes

On a separate topic, though—

I’ve fixed a couple pesky bugs in the ifcomp2025 branch (mostly arising in weird edge cases, but entirely my fault) and ensured it passes the entire test suite. Now, barring any last-minute emergencies, I’m not going to touch this branch again. I don’t want to accidentally break something right before the deadline!

2 Likes

I’ve also tracked down one long-standing bug in the compiler.

Constructions like this are fine:

@($Obj is $Rel $Parent)
    *($Obj has parent $Parent)
    *($Obj has relation $Rel)

And constructions like this are fine:

@($Obj is at $Parent)
    ($Obj has parent $Parent)

But this is not:

@($Obj is at $Parent)
    *($Obj has parent $Parent)

It mostly works, but if you ever query it with a ~ in front, the compiler will crash.

This means you can’t easily define an alias for the built-in ($ has parent $) relation, which I wanted to do both in the Scott Adams project and in the Mini-Cluedo project.

Here’s a toy example:

@($Obj is at $Parent)
	*($Obj has parent $Parent)

#apple
(* is at #room)

#banana
(* is at #room)

#cherry
(* is at #garden)

(program entry point)
	(collect $Fruit)
		*($Fruit is at #room)
	(into $AtRoom)
	At room: $AtRoom (line)
	(collect $Fruit)
		*(object $Fruit)
		~($Fruit is at #room)
	(into $NotAtRoom)
	Not at room: $NotAtRoom

This should work fine, right? But no:

dialogc: compile.c:1940: comp_body: Assertion an->subkind != RULE_MULTI failed.
Aborted (core dumped)

This assertion is in the part of the compiler that handles inverted queries. You can invert single queries, and you can invert blocks, but you can’t invert multi-queries—~(A) and ~{ (A) (B) } are fine, but not ~*(A), and that’s what this access predicate is being transformed into.

But semantically, ~*(A) (if it were allowed) would mean exactly the same thing as ~(A), so I don’t think there’s any danger in just removing this assert. I’ll try this out after the IFComp deadline passes and see if anything falls apart.

(Why do I call this weird edge case a compiler bug? Because imo, no program should ever make an assert fail in the compiler, especially not a program that’s both syntactically and semantically valid. If something is meant to be forbidden, there should be an informative error message, not a SIGABORT.)

1 Like

Yes, if that veneer were licensed under the LGPL, the author would have to either provide their source files, or provide some other kind of file that could be linked with a modified veneer.

1 Like

(As a side note, I’ve been using the term “veneer” because that’s what the Inform compiler calls it, but the more standard term—and the one used in the repository itself—is “runtime”, which is probably more useful when comparing to precedents.)

I’ve changed the licensing on zabbrev to MIT.

3 Likes

Of course, I immediately have to eat my words on this, because I did find a last-minute emergency: one of my other bugfixes accidentally reverted the fix to the bug that crashed old versions of Lectrote. My bad!

New push fixes that again.

1 Like

Have you ever been frustrated by the fact that you can’t make a negative query to an access predicate consisting of a single multi-query? No? Well, you can now! A bug in the access predicate optimizer that led to invalid ASTs has been fixed!

This means this code will no longer crash the compiler:

@($Obj is at $Parent)
	*($Obj has parent $Parent)

#apple
(* is at #room)

#banana
(* is at #room)

#cherry
(* is at #garden)

(program entry point)
	(collect $Fruit)
		*($Fruit is at #room)
	(into $AtRoom)
	At room: $AtRoom (line)
	(collect $Fruit)
		*(object $Fruit)
		~($Fruit is at #room)
	(into $NotAtRoom)
	Not at room: $NotAtRoom

Previously, ~($Fruit is at #room) would get compiled into ~*($Fruit has parent #room), which is not valid. Now it’s compiled into ~($Fruit has parent #room), as it should be.

3 Likes

I’ve updated the rules on the Å-machine repository to match the Dialog one: you have to make a pull request, and pass a series of automated tests, but no longer need to wait for approval. I’m going to go through and approve some older PRs now. With any luck, we’ll have version 1.0 out within a month, and people can start using its shiny new features in their games!

(Which shiny new features? Mostly support for audio and ARIA roles, which I added for Wise-Woman’s Dog. And colored text in the status bar. But they’re nice to have.)

The automated tests aren’t very extensive right now: they only test the JavaScript interpreter with its Node frontend, playing through the entirety of The Impossible Stairs and Miss Gosling’s Last Case and comparing the output against a gold standard. But it’s better than nothing: the JavaScript interpreter is the most widely-used one, and two full games hopefully test a wide variety of instructions and code paths.

I’m going to see about setting up Selenium to test the web frontend as well, but I have no experience with this, so I’m kind of fumbling in the dark. If someone with more front-end testing experience wants to take the lead, feel free! Similarly if anyone knows how to test the 6502 engine, which I’ve never used.

Also, a new feature from my personal wishlist: a (clear status bars) builtin to go along with (clear) (clears only the main window) and (clear all) (clears the main window and all status areas).

The only thing I’m unsure of is the name. What do you all think? Currently it’s (clear status bars) because you create them with (status bar $) and (inline status bar $), but if we want to change it, now’s the time!

  • (clear status)
  • (clear status bar)
  • (clear status bars)
  • (clear status area)
  • (clear status areas)
0 voters

And another from my personal wishlist (I finished a major thesis milestone yesterday so I’ve spent the time since working on Dialog and the Å-machine as a break from writing!):

All versions of the Z-machine have 240 registers that can be accessed easily from anywhere in the program. Dialog uses them for various purposes: some of them hold the arguments every time you query a predicate, for example, while others hold (global variable $)s, or the head of a linked list tracking objects with a particular flag, or even a useful constant like $3FFF (the internal representation of the empty list [])—referencing a register takes only a single byte, while a literal $3FFF needs two. They’re also used to hold global flags, 16 per register; since the registers are so fast to access, testing, setting, or resetting bits in them takes only a single instruction each.

And yet…Dialog has never actually checked if more than 240 are used. If you somehow manage to exhaust the Z-machine’s supply, the compiler won’t notice; it’ll happily wander into function arguments, local variables, and the stack, then loop around and start overwriting earlier registers with later ones.

So I added a check for that, and an appropriate error message. I also made the compile-time resource tracking tell you how they’re used. Here’s what the three full games we use in regression testing say.

Cloak of Darkness:

Debug: Registers used: 94 of 240 (39%)
Debug:                 61 internal, 16 temp, 17 global

Miss Gosling’s Last Case:

Debug: Registers used: 113 of 240 (47%)
Debug:                 61 internal, 19 temp, 33 global

The Impossible Stairs:

Debug: Registers used: 122 of 240 (50%)
Debug:                 61 internal, 32 temp, 29 global

So hopefully nobody runs into that limit for a long time! If they do, I can shift some (global variable $)s into a separate block of RAM, but it’ll be a decent amount of work, and right now it doesn’t seem necessary.

(For comparison, any Inform 6 program will reserve 5 registers for the compiler’s internal use, and the standard library uses a bit over 100 of what remains.)

3 Likes

This seems to have become the “discuss dev updates for Dialog” thread, so I suppose I’ll put this here. If people want these posts split off to a separate thread, just lmk. But I probably have one or two days of furious coding energy left before I switch back to thesis work, so I’m making the most of them!

This PR overhauls the way the GO TO action is reported. It produced (imo) way too much text, which meant players had to scroll through it—and would often miss the actually-important stuff in the process.

Previously:

> GO TO ZETA
(attempting to go northeast)
You walk northeast.

Delta
This is room delta. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

(attempting to go east)
You walk east.

Alpha
This is room alpha. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

(attempting to go north)
You walk north.

Beta
This is room beta. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

(attempting to go north)
You walk north.

Zeta
This is room zeta. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

Now:

> GO TO ZETA
(northeast)

Delta

(east)

Alpha

(north)

Beta

(north)

Zeta
This is room zeta. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

And if something interrupts:

> GO TO ZETA
(northeast)

Delta

(east)

Alpha

(north)
An invisible force field blocks your way!

Because something dramatic happened, your movement was cut short.

A vast improvement in my opinion! But, this is an observable change in behavior, so I want to give people a chance to object if they want.

Is anyone strongly attached to the old behavior? If you want to revert it for a particular project, that only takes a one-line change to the library (comment out one line of the new code), but if enough people want that it could potentially be made a default option as well.

EDIT: I should also say, this isn’t just personal preference; this is based on feedback testers gave on Miss Gosling and Wise-Woman’s Dog. Having the clickable map meant a lot of GO TO commands in the latter!

  • This looks great! The new version should be standard
  • I don’t like the new version; give me the option to turn it off
  • The new version shouldn’t be included, even as an option
0 voters

(The PR also adds an option to let the (default actions enabled) setting apply to rooms as well as things, because that ties into the GO TO implementation, but that one’s already locked behind an option switch.)

Attached? Not at all. And I’m usually all in favour of less text being output… It’s just… What if something important happens the second time you pass through a room, which is shown in the room description?

Probably a player wouldn’t notice it in the lengthy GO TO output, but at least they’d have a chance. If the room description isn’t output at all, then they don’t even have that.

For that reason I think there should be a simple hook to change this behaviour back.

4 Likes

I agree with this. A simple hook, as in a predicate that can be added to situationally turn this behavior off rather than

… which would presumably turn it off entirely.

My vote in the poll above would be:

I like the new version, but give me a simple way to turn it off.

2 Likes

It feels like this steps on the player’s choice of verbosity level. The old behaviour resembles most games’ VERBOSE level of brevity; the new behaviour seems to enforce BRIEF or SUPERBRIEF. Perhaps tie the behaviour to the selected verbosity level, rather than overriding it?

Ah! Yes, that makes a lot of sense.

The way this is currently implemented, it sets a (moving long distances) flag for the majority of the travel. The default implementations of (perform [look]) and (narrate leaving $ $) check this flag, and print a reduced version of their output in that case.

Which means if you provide your own implementation of either of those—or a predicate that’s not covered by those, like (on every tick) or (after [look])—it’ll still print fully. You can also just turn off the flag in specific cases:

(before [look])
    (current room #secretroom)
    (now) ~(moving long distances)

The library will turn it back on for the next step of the movement if it’s not interrupted (e.g. with (stop)).

Does that work as a way to situationally turn it off?

(To turn it off completely, of course, just go in the library and comment out the line that sets the flag.)

1 Like

Amusingly, while Dialog does support the BRIEF command…

(understand command [verbose])
(understand [brief/superbrief] as [verbose])
(perform [verbose])
	(div @meta) { The verbosity level of this story is not adjustable. }
	(stop)

…it’s not very useful. Unless you add your own implementation, Dialog games are always locked into VERBOSE mode. That’s part of what inspired this change: by default: there’s no way to make less of a flood of output.

That sounds like a very useful feature to add. BRIEF just operates off the ($ is visited) flag, and SUPERBRIEF ignores it, like VERBOSE does; it should be a fairly small feature to implement.

Unfortunately, the ($ is visited) flag gets set well before the initial LOOK action. It’s doable, but it would take some more fundamental changes to how that flag is handled, and consensus seems to be that the current system is better.

Do many players use BRIEF or SUPERBRIEF mode any more? I get the sense it was mostly used in the era of teletypes, where each room description cost precious time and paper, and you might replay the same game dozens of times.

Given concerns like this:

I think modern authors prefer not to give players that flexibility. Personally, I think it’s best to always show room descriptions while travelling short distances (a single direction), but not when travelling long ones (GO TO), except in exceptional circumstances.