This isn't just legacy hardware; 96dpi monitors and notebooks are still being produced today.
422 karma · joined June 12, 2020
This isn't just legacy hardware; 96dpi monitors and notebooks are still being produced today.
You'd almost end up with two languages in one. It would be interesting to see a language fully embrace that, with fast/slow language dialects which have very good interoperability. The complexity cost would be high, but if the alternative is learning two languages rather than one...
There are lots of ways for management to go wrong, but if you feel like this article describes your small business, here are some low-hanging fruit:
- Have you sought out management coaching, or are you trying to become a good manager by trial and error?
- Do you treat your employees as trusted, respected professionals (meaning: people who might know better than you)? Do your employees treat one another that way? Do they give you that same respect?
- When you've made a mistake, big or small, how do you discover it? Do your employees and co-founders feel safe and secure enough to give you frequent negative feedback? Do they trust that you'll act on it?
- Do you have the time and resources to provide good management for all of the employees that you're directly responsible for? Have you been properly hiring, promoting and delegating to spread out that workload as the team grows?
- Leaders are just team members whose job is to produce decisions, in the same way that a software engineer's job is to produce code. Are you actually doing your job, by consistently producing high-quality decisions? Who's keeping track of that?
- Your most impactful responsibilities are hiring, firing, promotions, setting salaries, and choosing how to balance quality against speed. Are you giving all of those decisions the care and effort which they deserve?
https://en.wikipedia.org/wiki/List_of_writing_systems#/media...
Variable fonts would help, but I don't think it would be enough, especially if the goal is to provide a wide selection of fonts.
Even just focusing on Latin scripts, a few hundred fonts would roughly double the compressed download size for browsers and Electron apps.
It might be possible to come up with a better compression scheme which exploits redundancy between different typefaces, but as far as I know, that tech doesn't exist yet.
Some open questions:
- How do you reduce the detail of a toon-rendered 3D model as the camera zooms out? How do you seamlessly transition between its more-stylised and less-stylised appearance?
- Hand-drawn 2D animations often have watercolour backgrounds. Can we convincingly render 3D scenery as a watercolour painting? How can we smoothly animate things like brush-strokes and paper texture in screen space?
- How should a stylised 3D game portray smoke, flames, trees, grass, mud, rainfall, fur, water...?
- Hand-drawn 2D animations (and some recent 3D animations) can be physically incorrect: the artist may subtly reshape the "model" to make it look better from the current camera angle. In a game with a freely-moving camera, could we automate that?
- When dealing with a stylised 3D renderer, what would the ideal "mesh editor" and "scenery editor" programs look like? Do those assets need to have a physically-correct 3D surface and 3D armature, or could they be defined in a more vague, abstract way?
- Would it be possible to render retro pixel art from a simple 3D model? If so, could we use this to make a procedurally-generated 2D game?
- Could we use stylisation to make a 3D game world feel more physically correct? For example, when two meshes accidentally intersect, could we make that intersection less obvious to the viewer?
There are probably enough questions there to fill ten careers, but I suppose that's a good thing!
The Swords and Ravens blog post recommends resolving actions on the client when they don't require secret information, but resolving other actions on the server. You'd also need to resolve actions on the server when they involve RNG.
Although that strategy enables you to store and recover the state of a game, it doesn't give you the ability to inspect a snapshot of that state. How can you print the card which has just been played, if that data only exists as an argument in the call stack of a suspended async function? In the same way that you can't inspect the local variables captured by a closure, mainstream languages also provide no way to inspect a suspended stack frame.
This problem interferes with debugging, consistency checks (e.g. hashing the game state to check that two clients are in sync), and unit testing.
This design makes it easy to implement optimistic updates, rollback, replays, automated testing, and recovery after a disconnection. It's a surprisingly good fit for UI, too; you can render simple games as a React component which takes the current State as one of its props.
However, a stream of context-free actions can be a really inconvenient representation for some games. The rules of a board game are often like the control flow of a computer program: you'll see branching, iteration, local variables, function calls, structured concurrency, and sometimes even race conditions and reentrancy. When you try to represent all of this logic as a State object, you're basically maintaining a snapshot of a "call stack" as plain data, and manually resuming that "program" whenever you handle an action. It doesn't seem ideal.
I've been sketching a board game engine which would represent the game logic as normal code instead. It seems promising, but it really needs a couple of language features which don't exist in the mainstream yet, like serialisation of suspended async functions.
React solves this problem by building a virtual DOM, and then conservatively updating the actual DOM (sometimes just mutating individual HTML attributes!) so that this state is preserved.
"A pure function which transforms the entire input into the entire output" is obviously the simplest possible architecture for many programs, but people hesitate to use that architecture because of performance concerns. In practice, the baseline performance is often faster than they expect, and it can be made much, much faster using strategies like memoisation and fine-grained reactivity.
The killer feature is that you can shadow a dynamic variable, changing its value for the duration of a dynamic scope, in the same way that an inner `catch` handler overrides an outer one.
For example, you could have a dynamic variable which tells the `print` function where to send its text output. The variable's value would probably default to stdout. By temporarily assigning a string-stream to that variable, you could collect an arbitrary function's text output into a string instead. Regardless of where the text is going, any function which produces text could still use the convenient global `print` function.
It isn't a very common language feature, but you could take a look at React's `Context` or Common Lisp's `defparameter`.
It's ambient state, just like a `catch` block, and so you can make it ambiently available to a whole tree of function calls. The outcome is the same as passing around a context argument by hand, and it can have equally strong type-checking, but the compiler handles all of the boilerplate for you.
To answer your second question: I agree that, in a dynamic language like JavaScript, making all of these language features implicit could be too costly. I'm more optimistic about effect systems in statically-typed languages.
- They're sometimes much too explicit. When writing a complicated generator, I don't necessarily want to annotate every call to a sub-generator with `yield*`, especially if I need to drill that annotation through wrapper functions which aren't really generators themselves.
- Colours show up everywhere. If you have several functions with a `context: GodObject` parameter, then that's a function colour. Most third-party code will be unable to forward a `context` argument to a callback, so you'll have to manually smuggle it in using closures instead.
- Different "colour channels" don't compose nicely with one another. Even though JavaScript ES2017 provided both async functions and generators, ES2018 had to add multiple new pieces of syntax to permit async generators.
- It's normally impossible to write code which is generic over a function's colours. For example, if you have `function callTwice(f)` in JavaScript, you'd need a separate `async function callTwiceAsync(f)`, and a `function* iterateTwice(iterable)`, and an `async function* iterateTwiceAsync(iterable)`, despite the fact that all of those functions are doing the same thing.
Several small languages are experimenting with algebraic effect systems [1], which would make all functions colourless. If JavaScript had this feature, the syntax for defining and calling functions would be the same, no matter whether you're dealing with a normal function, a generator, or an async function. No more `async`, `await`, `function*`, `yield`, `yield*`, `for-of`, `for await`...
This can make everything too implicit and vague. The state of the art is for algebraic effect systems to be implemented in a statically-typed language, which then uses type inference to quietly tag all function types with their colours. This means that the compiler can enforce requirements like "you can only call generators within a scope which is prepared to collect their results", but that rule wouldn't prevent you from wrapping a generator in `callTwice`.
[1]: https://github.com/ocaml-multicore/ocaml-effects-tutorial
Good UI architecture needs some convenient and efficient way to propagate state changes between different parts of the UI framework [0]. This requirement sits in an awkward place, halfway between imperative programming and functional programming. It just isn't in the day-to-day vocabulary of any mainstream language, not even modern imperative languages which have a bit of functional programming mixed in.
I don't think OOP is any better at fulfilling this requirement. Being able to offload half of your program into a visual editor is nice, but it's cold comfort if the other half of your program ends up being a tangled mess of callbacks and data binding.
[0]: https://raphlinus.github.io/ui/druid/2019/11/22/reactive-ui....
I'm starting to suspect that general-purpose languages just aren't flexible enough to keep up with changing fashions in UI development. Whenever somebody comes up with a new UI paradigm, you're not going to be able to take full advantage of it until somebody designs a new programming language with that paradigm in mind.
If so, this might explain why the Windows team keeps pushing new desktop UI libraries, only to abandon them within a few years - they just don't have the willingness or resources to migrate away from C# and C++, and so the quality of their UI libraries is stuck in 2006.
- Code-based UIs tend to work better with existing tooling, like comments and source control. You can freely customise your development environment, rather than being at the mercy of a single GUI app.
- Most serious UIs need code-like features, such as "for-each" to render a list of objects, or "if" to reveal a form when a checkbox is ticked. The easiest way to access code-like features is to write your UIs in code.
- You'll need to write some backing code in any case. Defining the UI tree and its "code-behind" in the same file could be considered more DRY.
- Live preview (or alternatively, hot reloading) will give you very quick iteration when writing a UI in code. It's not quite as good as drag-to-resize, but it's close.
- Automatic layout (which is non-optional nowadays) isn't necessarily a great fit for WYSIWYG editing.
- As React has demonstrated, the ability to quickly throw together custom components is extremely useful. Visual editors tend to make it more complicated and bureaucratic to define custom components.
The reason this is counterintuitive is that UI frameworks insulate you from some types of state. When you write your own `fn()`, it's only receiving part of the state, and it's only defining part of the view. In the browser, animations are a good example of this; other examples include keyboard focus, the size of the browser window, and most of the behaviour of native form controls.
For any given part of your UI, you'll have n events to handle ("UI started up for the first time", "x changed", "y changed", "checkbox toggled", "undo", "redo", "file loaded"), and m invariants to uphold ("while the checkbox is checked, the user cannot edit y", "while x is less than y, the UI should display a warning", "this label displays the current value of y as a localised string"). If you try to manually maintain each invariant when handling each event, you'll find it works for simple cases, but it falls apart for larger values of n and m.
Every interview stage which is designed to reject bad candidates (true negatives) will also accidentally reject some good candidates (false negatives). The more eagerly you filter out bad candidates, the more you'll also filter out good candidates; sensitivity decreases as specificity increases.
This means that building more pass/fail tests into your interview pipeline may produce worse results - even if you have an enormous number of applicants, all with infinite time and patience! At each interview stage, there's a risk of accidentally rejecting the best remaining applicant, the one who would have outperformed all others if they were hired. For example, the article mentions "values mismatch" as a particularly good reason to hard-reject a candidate, but several of the company's values seem like they ought to be optional. Surely some truly excellent software developers lack courage, humour, and thriftiness? Could those qualities just as well be taught on-the-job? Could a non-courageous hire have useful things to teach you about caution?
Good hiring processes should draw a clear distinction between "must-have" qualities and "nice-to-have" qualities. Beyond a certain point, the problem stops being "filter out bad candidates", and starts being "out of several great candidates, make sure we choose the best one"; to solve those two problems, different tools are required.
[0] https://en.wikipedia.org/wiki/Sensitivity_and_specificity
Artistic preferences are subjective, but I expect most players would find that rotated sprite unappealing. This is especially true when the rotation is animated - the aliasing causes a distracting, staticky, random-noise effect which gives the impression that the sprite's fine details are chaotically changing. It's almost the aesthetic opposite of what most pixel artists are aiming for.
I surveyed the indie-game landscape a couple of years back, specifically looking for games in your first category, but struggled to find any recent examples. 2D transforms, alpha-blending and lighting effects seem to be the standard now.
I've worked on a game which aimed for authentically retro pixel art, but in hindsight I think it was a silly misallocation of resources. There's a reason it's so uncommon nowadays. If I were to try again, I'd at least include high-resolution 2D transforms in the engine, and possibly dynamic lighting too.
I briefly explored the possibility of developing a style-preserving realtime rotation algorithm for sprites, but it's a hard problem.
I'd like to see more open-world games experiment with a flat power curve: simply throw away the idea that the player character's numerical power should increase as the game goes on. If Super Mario World had increased the player's jump height or hit points based on the number of levels completed, it would have made the game much worse - so why do we tolerate the same thing from Castlevania and Final Fantasy?
Players might miss the (artificial) feeling of mastery, but I don't think it's worth the cost. Finding better ways to achieve that same feeling would be an interesting design challenge!