Retained values

How does one make a flow that combines things together? For instance, suppose at sunset I want to brighten one light as much as another one... I can make a node that injects an event at sunset and two nodes that receive the appropriate MQTT data for how right the two lights currently are. But then I need to compute the maximum of the two brightness, and turn it into an outgoing message only when the sunset trigger fires. I was expecting to find that Node-RED lets you have a wire that holds a "retained value" (like, the last message that was received from an MQTT topic), not just wires that pass along momentary events. Something like this:

but so far I haven't found it, just discussions about setting the topic of each input beforehand so multiple inputs can be distinguished from each other, and coupling that to logic inside a function node that saves the values into the node's context so that it can work around the fact that it only has access to one input at a time. And a discussion about how multiple inputs would somehow be more confusing than this :). Is there an expressive/succinct way to do this that I'm missing?

Welcome to Node-red Atticus.

As you say, in Node-red a message is a transitory thing. Generally speaking a node only knows about the message it is currently processing.
It can seem very unwieldy if you are new to flow based programming. It's probably worth watching the series of introductory videos at https://www.youtube.com/playlist?list=PLyNBB9VCLmo1hyO-4fIZ08gqFcXBkHy-6.

How can you compare two messages?
a) Use a join node to combine them into a single message or
b) Store the values in variables.

[{"id":"22b4fc280e22ebb5","type":"inject","z":"b190f6b08d42b688","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"family_room_light","payload":"245","payloadType":"num","x":380,"y":40,"wires":[["abce2105a6019368"]]},{"id":"bc45d7dfc3ed990a","type":"inject","z":"b190f6b08d42b688","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"shelf_light","payload":"199","payloadType":"num","x":350,"y":80,"wires":[["abce2105a6019368"]]},{"id":"abce2105a6019368","type":"join","z":"b190f6b08d42b688","name":"","mode":"custom","build":"object","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":true,"timeout":"","count":"2","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":670,"y":60,"wires":[["21c12edcf07ae8b9","6ee69d03c3122718"]]},{"id":"6ee69d03c3122718","type":"debug","z":"b190f6b08d42b688","name":"Joined Message","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":840,"y":40,"wires":[]},{"id":"48f6638999d59b33","type":"inject","z":"b190f6b08d42b688","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"family_room_light","payload":"245","payloadType":"num","x":380,"y":160,"wires":[["7d5a2be6850ff866"]]},{"id":"299d2e0156336e1d","type":"inject","z":"b190f6b08d42b688","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"shelf_light","payload":"199","payloadType":"num","x":350,"y":200,"wires":[["06d2e9cf3f1b60c1"]]},{"id":"7d5a2be6850ff866","type":"change","z":"b190f6b08d42b688","name":"","rules":[{"t":"set","p":"family_room_light","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":610,"y":160,"wires":[["cba4d7b71c322cd3"]]},{"id":"06d2e9cf3f1b60c1","type":"change","z":"b190f6b08d42b688","name":"","rules":[{"t":"set","p":"shelf_light","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":630,"y":200,"wires":[["cba4d7b71c322cd3"]]},{"id":"21c12edcf07ae8b9","type":"function","z":"b190f6b08d42b688","name":"Compare values in message","func":"\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":880,"y":80,"wires":[[]]},{"id":"cba4d7b71c322cd3","type":"function","z":"b190f6b08d42b688","name":"Compare values in variables","func":"var family_room_light = flow.get('family_room_light') || 0;\nvar shelf_light = flow.get('shelf_light') || 0;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":880,"y":180,"wires":[[]]}]